Пользователь
0,0
рейтинг
21 декабря 2010 в 21:53

Разработка → 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
chernikov @chernikov
карма
177,9
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (47)

  • 0
    Некоторые вещи не актуальны для API 3.x.
    • +1
      В частности не надо получать свой ключ
  • +1
    Для третьей версии API ключ вроде бы не нужен уже.
    • 0
      Да, это было очень существенным плюсом, помнится:)
      • 0
        О да. У нас еще на тот момент у каждого разработчика был свой домен. Не поддомен на девелоперском сервере, а домен:)
    • 0
      не нужен
  • 0
    Спасибо за статью. А не появилось ли еще бесплатной cms, реализующей подобный функционал из коробки?
    • +2
      Задам простой вопрос на который (лично я) не могу найти ответ уже пару лет
      Зачем? Это же в любом случае пара строчек + данные из cms которым на АПИ положить
  • –3
    Блин, увидел pushkino.org на главной Хабра с пометочкой Гугл.Мапс.Апи и сразу паранойя — думал, что меня нашли (я ж в Пушкино живу)…
    • +1
      Зачем паранойя когда сам рассказываешь все :)
      • 0
        Позор на мои седины!
  • 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
          а в сумме это 41000!
  • 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
            да незачто :)
  • 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
    Какой клевый и простой апи у Яндекса. Но вот печалька: соглашение запрещает юзать его для мониторинга автотранспорта, а именно это и хочется, с геокодингом. Но цены доя "очень малого бизнеса" неадекватны.

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

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

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