Google Maps API

  • Tutorial

Картографический сервис – зачем это? Ну например, я 10 лет жил в нашей маленькой провинции, а потом взял и понаехал в Москву, и всё для меня так ново. А где магазины, боулинг, кафешки, парки отдыха – надо знать же, где тратить московскую зарплату. Но вот беда, как узнать? Раньше был справочник «Желтые страницы» и там была карта и всё по адресам. Чтобы найти что-то уходило масса времени. Сейчас стало всё в разы проще. Вот прекрасный пример: http://www.pushkino.org/. Но это далеко не всё.
Я могу отслеживать погоду, пожары, пробки (кстати!) в реальном времени.
Мой заказчик может не вводить свой адрес, а попросту отметить его на карте и я буду знать куда доставить ему товар – какое классное решение, не надо всего этого – «Проспект маршала Блюхера, 43, г. Санкт-Петербург, Россия».



Задача для примера


Всё лучше узнавать практически, так что сделаем задачу для примера, чтобы обрести навыки. Вот примерный план работ:
  1. Вывести карту (надо же!)
  2. Задать город
  3. Переместить карту к городу
  4. Маркером указать адрес
  5. Добавить информации
  6. Вывести карту (надо же!)
  7. Сохранить маркер с иноформацией (при клике на него вывести ее)
  8. Избежать нагромождения (т.е. сделать кластеризацию) маркеров.


Как делать?



Ключ API

Ключ API нужен для использования работы с картой, т.е. при запросе всех их скриптов и сервисов в параметры нужно добавлять &key=[тут наш ключ]. Впрочем для http://localhost он не нужен. Получить его надо тут: http://code.google.com/apis/maps/signup.html. Кстати, работает и без него на сайте, но может это временно.
Для v.3 не нужен

Map\Marker\InfoWindow



Для работы нам понадобится 3 основных объекта. Первое – это карта.
Карта создается очень просто. У нас есть какой-то определенный контейнер:

<div id="map_canvas"></div>


Подключаем скрипт:
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>


Инициализируем карту:

function initialize() {     
	var myLatlng = new google.maps.LatLng(-34.397, 150.644);
	var myOptions = {
		zoom: 8,
		center: myLatlng,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	}
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); 
}


center: myLatlng – это координаты центра карты
zoom – это увеличение при инициализации
mapTypeId – тип (политическая, физическая, гибрид)
Карта готова!

Второе — это метки:

var marker = new google.maps.Marker({
	position: myLatlng,
	map: map,
	title:"Hello World!" 
});


position – собственно координаты метки
map – на какую карту метку поместить
title – при наведении мыши будет писать “Hello World!”.

InfoWindow

var contentString = '<div id="content">Тут всё то про что должно быть рассказано</div>';
var infowindow = new google.maps.InfoWindow({
	content: contentString
});
var marker = new google.maps.Marker({
	position: myLatlng,
	map: map,
	title: 'Uluru (Ayers Rock)'
});
google.maps.event.addListener(marker, 'click', function() {
	infowindow.open(map,marker);
});

content – содержимое в метке

google.maps.event.addListener(marker, 'click', function() {   infowindow.open(map,marker); });
— при клике на метку, показать окно с информацией, на карте map с привязкой к marker.

Geocoding

Geocoding – это просто отличная библиотека, которая позволяет делать всего 2 вещи:
  • По наименованию чего-то, найти это на карте и сообщить координаты
  • По координатам, сообщить всё что находится на этих координатах.


Запрос выглядит так. Например, мы хотим узнать где находится Иваново. Пишем запрос:
http://maps.googleapis.com/maps/api/geocode/json?address=Иваново&sensor=false&language=ru

И в ответе приходит:
{  
	"status": "OK",
	"results": [
	{   
		"types": [ "locality", "political" ],
		"formatted_address": "город Иваново, Ивановская область, Россия", - полный адрес 
		"address_components": [ 
			{ - составляющие адреса    
				"long_name": "город Иваново",    
				"short_name": "город Иваново",    
				"types": [ "locality", "political" ]   
			}, 
			{    
				"long_name": "Ивановский район",
				"short_name": "Ивановский район",
				"types": [ "administrative_area_level_2", "political" ]
			}, {
				"long_name": "Ивановская область",
				"short_name": "Ивановская область",
				"types": [ "administrative_area_level_1", "political" ]
			}, {
				"long_name": "Россия",
				"short_name": "RU",
				"types": [ "country", "political" ]
			} ],
			"geometry": {    
					"location": { - местонахождение
					"lat": 56.9924086,
					"lng": 40.9677888
				},    
				"location_type": "APPROXIMATE",    "viewport": { - размеры
				"southwest": {
					"lat": 56.9699256,
					"lng": 40.9265167
				},
				"northeast": {
					"lat": 57.0148916,
					"lng": 41.0090609
				}
			},
			"bounds": { - границы
				"southwest": {
					"lat": 56.9699256,
					"lng": 40.9265167
				}, 
				"northeast": {
					"lat": 57.0148916,
					"lng": 41.0090609
				}
			}
		}
	} 
] }


Вся прелесть в том, что можно в address параметре передавать значение на любом языке(Ivanovo, Іваново, <тут была арабская вязь>), еще лучше, что для Санкт-Петербурга прокатывает «Спб» и «Питер». Правда есть и недочеты: мой родной город Ивано-Франковск упорно называет Ивано-Франковськ, на украинский манер.
Вторая возможность, это по координатам узнать адрес:

http://maps.googleapis.com/maps/api/geocode/json?latlng=55.75320193022759,37.61922086773683&sensor=false&language=ru
получаем:

{  
	"status": "OK",
	"results": [ {
		"types": [ "street_address" ],
		"formatted_address": "Красная пл., 3, город Москва, Россия, 109012",
		"address_components": [ 
		{
			"long_name": "3",
			"short_name": "3",
			"types": [ "street_address" ]
		}, {
			"long_name": "Красная пл.",
			"short_name": "Красная пл.",
			"types": [ "route" ]   
		}, {
			"long_name": "Тверской",
			"short_name": "Тверской",
			"types": [ "sublocality", "political" ]
		}, {
			"long_name": "город Москва",
			"short_name": "город Москва",
			"types": [ "locality", "political" ]
		}, {    
			"long_name": "АО Центральный",
			"short_name": "АО Центральный",
			"types": [ "administrative_area_level_2", "political" ]
		}, {    
			"long_name": "Москва",
			"short_name": "Москва",
			"types": [ "administrative_area_level_1", "political" ]
		}, {
			"long_name": "Россия",
			"short_name": "RU",
			"types": [ "country", "political" ] 
		}, {
			"long_name": "109012",
			"short_name": "109012",
			"types": [ "postal_code" ]
		} ], 
		"geometry": {    
			"location": {
				"lat": 55.7546971,
				"lng": 37.6215214  
			},    
			"location_type": "ROOFTOP",
			"viewport": {     
				"southwest": { 
					"lat": 55.7515495,
					"lng": 37.6183738  
				},     
				"northeast": {
					"lat": 55.7578447,
					"lng": 37.6246690
				}
			}
		} 
	},
	{ ...



Супер! Для того чтобы указать свой адрес, можно просто кликнуть на свой дом, добавить квартиру – и всё. Иногда это не срабатывает, например, если дома стоят вплотную друг к другу и считываются как 1 объект, а не 2-3, адрес у них будет один. Особенно плохо, когда они находятся на пересечении улиц, и один дом относится к одной улице, а второй – к перпендикулярной, но думаю по необходимости – можно указать улицу, а дом и квартиру уже вбить. Очень удобное для смартфонов решение.
Кстати, не используйте jquery $.getJSON для получения данных, используйте класс Geocoder (http://code.google.com/apis/maps/documentation/javascript/reference.html#Geocoder), он работает лучше (т.е. это означает что getJSON у меня не работает).

А теперь о не очень хорошем. Geocoder – насколько клевая функция, что пользоваться ею можно только 2500 запросов в день. Google предлагает Google API Key Premier от 10000$ в год, и тогда ограничение будет в 100 тыс. запросов в день, причем куча всяких «клевых» дополнений, но я их не могу себе позволить.

Markercluster


Когда слишком много маркеров — это выглядит конечно ужасно. Поэтому хорошо бы делать кластеризацию всех этих маркеров. Тут на Хабре я видел уже обсуждение по этому поводу: http://habrahabr.ru/blogs/google/28621/
В общем, есть отличный инструмент (а тут их целый набор http://code.google.com/apis/maps/articles/toomanymarkers.html) который помогает сделать так, чтобы толпы маркеров не пугали нас.
До:

После:

Это именно то что нам надо.
Эту библиотеку можно скачать тут: http://google-maps-utility-library-v3.googlecode.com/svn/trunk/
Как использовать.
Добавляем библиотеку

<script type="text/javascript" src="/Media/script/map/markerclusterer_packed.js"></script>


Составляем массив маркеров, не добавляя в карту:

var markers = []; 
var marker = new google.maps.Marker({    
	position: latlng
});
markers.push(marker);
markerClusterer = new MarkerClusterer(_this.map, markers, 
{ 
	maxZoom: 13,
	gridSize: 50,
	styles: null 
});


maxZoom – максимальный зум при котором мы еще группируем маркеры, дальше – уже нет.
gridSize – размер ячеек сетки, чем меньше значение, тем меньше сетка группировки
styles – дополнительные стили

Код из примера


Я не буду тут расписывать что как собрать, собственно все инструменты готовы, дам ссылки на исходники, и прокомментирую некоторые вещи.
Cерверного кода (asp.net mvc) там очень мало, всего 4 запроса:
  • собственно страница
  • получить все маркеры (в json)
  • загрузить файл (через ajaxUploader) и получить ссылку для картинки
  • сохранить в базу данных маркер (на выходе json result = ok)

Основной код jquery ( тут полностью: cocosanka.ru/media/script/map/map.js ) Там есть комментарии, и всё такое.
Некоторые функции требующие пояснения:

Вычисление значения Zoom по границам
(взято отсюда: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/43958790eafe037f/66e889029c555bee?fwc=2)

this.getZoom = function (bounds) {
     var width = $(".map").width();
     var height = $(".map").height();
     var dlat = Math.abs(bounds.getNorthEast().lat() - bounds.getSouthWest().lat());
     var dlon = Math.abs(bounds.getNorthEast().lng() - bounds.getSouthWest().lng());
     var max = 0;
     if (dlat > dlon) {
		max = dlat;
	} else {
		max = dlon;
	}
	var clat = Math.PI * Math.abs(bounds.getSouthWest().lat() + bounds.getNorthEast().lat()) / 360.;
	var C = 0.0000107288;
	var z0 = Math.ceil(Math.log(dlat / (C * height)) / Math.LN2);
	var z1 = Math.ceil(Math.log(dlon / (C * width * Math.cos(clat))) / Math.LN2);
	//18 – это максимальный zoom для google.maps
	return 18 - ((z1 > z0) ? z1 : z0);
}


Функция для «прыжка» маркера:

this.toggleBounceMarker = function()
{
	if (_this.setMarker.getAnimation() != null) {
		_this.setMarker.setAnimation(null);
	} else {
		_this.setMarker.setAnimation(google.maps.Animation.BOUNCE);
	}   
}


Получение адреса:
this.SetAddresses = function (results) 
{
     $(".address_list").show();
     $(".address_list").empty();
     var addressText = _this.ComposeAddress(results[0]); ...  
}   

//Составить строку адреса по первому результату   
this.ComposeAddress = function (item) {
     retAddress = "";     
	 $.each(item.address_components, function (i, address_item) {
		var isOk = false;
		$.each(address_item.types, function (j, typeName) { 
			//не будем брать значения адреса улицы и локали (города) - город потом будет в administrative_level_2
			if (typeName != "street_address" && typeName != "locality") {
				isOk = true;
			}
		});
		if (isOk) { 
			if (retAddress == "") {
				retAddress = address_item.long_name; 
			} else { 
				retAddress = retAddress + ", " + address_item.long_name;
			}  
		} 
	});
	return retAddress;  
}

Итого



Google Maps API – очень классная и удобная штука, которая легка в использовании и понимании. Единственно, что плохо – так это слабое покрытие регионов в России, так что сервисам, которые предполагается использовать в глубинке google.maps пока мало интересен, а вот для больших городов (особенно Москва и Питер), а также для Украины – всё отлично.
Geocoding – очень полезная вещь и при правильном использовании может стоить тех денег, что за нее просят (ну или Microsoft или Яндекс подоспеет с аналогом уже есть. Хотя насколько я знаю, картографическая информация стоит бешеных вложений.)

Пример\исходники


На живой пример можно глянуть тут: http://cocosanka.ru/map (может перестать работать если будет достигнут лимит в Geocoding). Вводите город, потом перетаскиваете маркер, потом загружаете картинку и сохранить. При клике на маркеры выводятся картинки.

Исходники: https://bitbucket.org/chernikov/citylocator
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 47
  • 0
    Некоторые вещи не актуальны для API 3.x.
  • +1
    Для третьей версии API ключ вроде бы не нужен уже.
    • 0
      Да, это было очень существенным плюсом, помнится:)
      • 0
        О да. У нас еще на тот момент у каждого разработчика был свой домен. Не поддомен на девелоперском сервере, а домен:)
      • 0
        не нужен
      • 0
        Спасибо за статью. А не появилось ли еще бесплатной cms, реализующей подобный функционал из коробки?
        • +2
          Задам простой вопрос на который (лично я) не могу найти ответ уже пару лет
          Зачем? Это же в любом случае пара строчек + данные из cms которым на АПИ положить
        • –3
          Блин, увидел pushkino.org на главной Хабра с пометочкой Гугл.Мапс.Апи и сразу паранойя — думал, что меня нашли (я ж в Пушкино живу)…
          • +1
            Зачем паранойя когда сам рассказываешь все :)
        • 0
          Правильно! Регионы во власти OpenStreetMap!
          • +3
            геокодер у яндекса есть — http://api.yandex.ru/maps/geocoder/
            • 0
              У яндекса вроде на геокодинг лимиты гораздо выше.
              • +1
                В 10 раз.
                Количество обращений к функции геокодирования ограничено 25000 запросов для одного API-Ключа в сутки. Отсюда: api.yandex.ru/maps/form.xml
                • 0
                  Тогда в два раза. В гугле 16к в день.
                  И в обоих случаях — «для одного API ключа+IP»
                  Есть это не сайт может дать пользователям 20к раз что-то найти, а лично ВЫ лично на этом сайте… клава развалиться?
            • 0
              ещё одна деталь — в третьей версии GmapAPI инфоокна ( InfoWindow ) перестали автоматически закрываться при открытии другого инфоокна… чтобы избежать этого нужно использовать одно инфоокно и обновлять контент и позицию соответственно по клику на маркере :)
              • 0
                Можно и так :-)
                • 0
                  ну а как более экономно по части ресурсов сделать и чтобы последнее инфоокно закрывалось при открытии нового? :)
                  • 0
                    Можно ли получать данные об изменении информации на маркерах? Т.е. кто-то поменял инфу, а вы хотите об этом знать.
                    • 0
                      смотря какую инфу о маркерах… :)
                      вообще есть обработчики событий, и там есть например событие title_changed…
                      полный список тут…
                      code.google.com/apis/maps/documentation/javascript/reference.html#Marker

                      то есть при обновлении инфы о маркере можно менять тайтл и уже по этому событию что-то делать
                    • 0
                      Открытие инфоокна вешается на событие клика по маркеру, нужно повесить еще один обработчик, чтобы сначала показанное ранее окно закрывалось.
                      • 0
                        для этого каждый маркер должен знать о других маркерах и их инфоокнах… в любом случае придётся выносить инфу об инфоокне в глобальную область — получаем моё решение с общим инфоокном для всех маркеров… клик по маркеру закрывает текущее инфоокно, меняет контент и позицию и показывает на новом месте :)
                        если у вас есть предложения интереснее — с удовольствием выслушаю ;)
                        • 0
                          У карты есть метод closeInfoWindow(), вызываем его перед показом нового инфоокна и все открытые до этого окна закроются. Так как это метод класса карты, то нам не нужно знать то знать о других маркерах и окнах нам не нужно.
                          • 0
                            Вы хотели сказать «был метод» :) во второй версии… в третьей его нет, о том и разговор
                            • 0
                              Черт, вы правы :) Не обращал, что в третьей версии реализовано по-другому. Интереснее тут предложения думаю нет, потому что даже у самого гугла в примерах реализовано примерно также, как описываете вы
                  • 0
                    А можете подсказать куда рыть чтобы сделать что-то похожее на то что в ссылочке в плане открытия окошек и их натсройки
                    www.booking.com/city/it/rome.html?sid=4389551beee6c27cd1ec26b1417c2f67;city=-126693
                    • 0
                      а чего ж не могу — могу :)
                      есть в гугломапсах кастомные оверлеи — их и будем использовать…
                      pastebin.com/hvBJ6NLu
                      вот простой код — там класс Label ( вот он наш произвольный html для маркеров ) и пару строчек вверху как им пользоваться…
                      событие «мышка проплывает над маркером» добавите сами, я думаю :)
                      • 0
                        Спасибки, буду разбираться, хотя конечно наделся чт оест ьвариант како-нибудь чтобы все это не перерисовывалось, но так тоже думаю ок
                • 0
                  Но самый главный прикол в том что оригинальный maps.google.com работает на версии АПИ 2!
                  • 0
                    А кластеризация в примере работает под третью версию api?
                    Когда есть необходимость отображать большое количество маркеров на карте одновременно и кластеризация не подходит, есть вариант использовать «облегченные маркеры» (Marker Light). Это обычные overlay слои с заданным изображением. Они едят в разы меньше ресурсов и вполне прилично работают когда из около 1000 штук на карте. Если хабраюзерам интересно, могу написать статью по этому поводу, пока в рунете не много материала на эту тему.
                    Для отображения всплывающих окон есть отличный класс ExtInfoWindow. Правда он тоже под вторую версию, но возможность визуального оформления окна в нем намного больше чем у стандартного, можно попробовать переписать под v3.
                    • 0
                      Пардон, про кластеры невнимательно прочитал.
                      • +1
                        Для себя ExtInfoWindow подправил, но в публичный доступ выкладывать стыдновато. Но поделиться не жалко, пишите в личку.
                        • 0
                          Спасибо за предложение, но я пока вторую использую )
                      • 0
                        Посмотрел ваш код, для вычисления центра можно использовать bounds.getCenter(), а не считать его руками.
                        • 0
                          Собственно, всё то же реализуется при помощи OpenLayers, только у OpenLayers API более продуманное и универсальное, и работает оно не только с Google Maps, но и с любым другим провайдером как растровых, так и векторных данных.
                          • 0
                            Спасибо! Очень полезным оказалось!
                            • 0
                              Забавно, что гугл требует в яваскриптах указывать координаты через точку, а сам отдает

                              [Point] => Array
                              (
                              [coordinates] => Array
                              (
                              [0] => 4,8951679
                              [1] => 52,3702157
                              [2] => 0
                              )

                              )
                              • 0
                                если кому до сих пор требуется API 2.0
                                вот инструкция на получения ключа
                                yermak.by/blog/design/item/28-googlemaps_api
                                • 0
                                  А есть технология, когда в базе маркеров под 1 миллион, а отображать на карте нужно те меркеры, которые видит пользователю и с зумом более 10
                                  И при передвижении картой добавлять маркеры которые стали «видимы»
                                  • 0
                                    Должно быть.

                                    В данный момент статья не особо актуальна, так как гугл-мапс выпустили новую версию API. Исследуйте в примерах.
                                  • 0
                                    Какой клевый и простой апи у Яндекса. Но вот печалька: соглашение запрещает юзать его для мониторинга автотранспорта, а именно это и хочется, с геокодингом. Но цены доя "очень малого бизнеса" неадекватны.

                                    Кто читал гугловское соглашение, там тоже запрещено юзать это? В закрытых системах?

                                    Есть открытые для коммерческих целей сервисы со схожим функционалом?
                                    Чтобы геокодирование работало если набирать адрес задней пяткой левой ноги..

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое