Картографический сервис – зачем это? Ну например, я 10 лет жил в нашей маленькой провинции, а потом взял и понаехал в Москву, и всё для меня так ново. А где магазины, боулинг, кафешки, парки отдыха – надо знать же, где тратить московскую зарплату. Но вот беда, как узнать? Раньше был справочник «Желтые страницы» и там была карта и всё по адресам. Чтобы найти что-то уходило масса времени. Сейчас стало всё в разы проще. Вот прекрасный пример: http://www.pushkino.org/. Но это далеко не всё.
Я могу отслеживать погоду, пожары, пробки (кстати!) в реальном времени.
Мой заказчик может не вводить свой адрес, а попросту отметить его на карте и я буду знать куда доставить ему товар – какое классное решение, не надо всего этого – «Проспект маршала Блюхера, 43, г. Санкт-Петербург, Россия».
Задача для примера
Всё лучше узнавать практически, так что сделаем задачу для примера, чтобы обрести навыки. Вот примерный план работ:
- Вывести карту (надо же!)
- Задать город
- Переместить карту к городу
- Маркером указать адрес
- Добавить информации
- Вывести карту (надо же!)
- Сохранить маркер с иноформацией (при клике на него вывести ее)
- Избежать нагромождения (т.е. сделать кластеризацию) маркеров.
Как делать?
Ключ API
Ключ API нужен для использования работы с картой, т.е. при запросе всех их скриптов и сервисов в параметры нужно добавлять &key=[тут наш ключ]. Впрочем для http://localhost он не нужен. Получить его надо тут: http://code.google.com/apis/maps/signup.html. Кстати, работает и без него на сайте, но может это временно.
Для v.3 не нужен
Map\Marker\InfoWindow
Для работы нам понадобится 3 основных объекта. Первое – это карта.
Карта создается очень просто. У нас есть какой-то определенный контейнер:
1 2 |
<span class="tag"><<span class="title">div</span> <span class="attribute">id</span>=<span class="value">"map_canvas"</span>></span><span class="tag"></<span class="title">div</span>></span> |
Подключаем скрипт:
1 2 |
<span class="tag"><<span class="title">script</span> <span class="attribute">type</span>=<span class="value">"text/javascript"</span> <span class="attribute">src</span>=<span class="value">"http://maps.google.com/maps/api/js?sensor=false"</span>></span><span class="tag"></<span class="title">script</span>></span> |
Инициализируем карту:
1 2 3 4 5 6 7 8 9 10 |
<span class="function"><span class="keyword">function</span> <span class="title">initialize</span><span class="params">()</span> {</span> <span class="keyword">var</span> myLatlng = <span class="keyword">new</span> google.maps.LatLng(-<span class="number">34.397</span>, <span class="number">150.644</span>); <span class="keyword">var</span> myOptions = { zoom: <span class="number">8</span>, center: myLatlng, mapTypeId: google.maps.MapTypeId.ROADMAP } <span class="keyword">var</span> map = <span class="keyword">new</span> google.maps.Map(document.getElementById(<span class="string">"map_canvas"</span>), myOptions); } |
center: myLatlng – это координаты центра карты
zoom – это увеличение при инициализации
mapTypeId – тип (политическая, физическая, гибрид)
Карта готова!
Второе — это метки:
1 2 3 4 5 6 |
<span class="keyword">var</span> marker = <span class="keyword">new</span> google.maps.Marker({ position: myLatlng, map: map, title:<span class="string">"Hello World!"</span> }); |
position – собственно координаты метки
map – на какую карту метку поместить
title – при наведении мыши будет писать “Hello World!”.
InfoWindow
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="keyword">var</span> contentString = <span class="string">'<div id="content">Тут всё то про что должно быть рассказано</div>'</span>; <span class="keyword">var</span> infowindow = <span class="keyword">new</span> google.maps.InfoWindow({ content: contentString }); <span class="keyword">var</span> marker = <span class="keyword">new</span> google.maps.Marker({ position: myLatlng, map: map, title: <span class="string">'Uluru (Ayers Rock)'</span> }); google.maps.event.addListener(marker, <span class="string">'click'</span>, <span class="keyword">function</span>() { infowindow.open(map,marker); }); |
content – содержимое в метке
1 2 |
google.maps.event.addListener(marker, <span class="string">'click'</span>, <span class="keyword">function</span>() { infowindow.open(map,marker); }); |
— при клике на метку, показать окно с информацией, на карте map с привязкой к marker.
Geocoding
Geocoding – это просто отличная библиотека, которая позволяет делать всего 2 вещи:
- По наименованию чего-то, найти это на карте и сообщить координаты
- По координатам, сообщить всё что находится на этих координатах.
Запрос выглядит так. Например, мы хотим узнать где находится Иваново. Пишем запрос:
http://maps.googleapis.com/maps/api/geocode/json?address=Иваново&sensor=false&language=ru
И в ответе приходит:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
{ <span class="string">"status"</span>: <span class="string">"OK"</span>, <span class="string">"results"</span>: [ { <span class="string">"types"</span>: [ <span class="string">"locality"</span>, <span class="string">"political"</span> ], <span class="string">"formatted_address"</span>: <span class="string">"город Иваново, Ивановская область, Россия"</span>, - полный адрес <span class="string">"address_components"</span>: [ { - составляющие адреса <span class="string">"long_name"</span>: <span class="string">"город Иваново"</span>, <span class="string">"short_name"</span>: <span class="string">"город Иваново"</span>, <span class="string">"types"</span>: [ <span class="string">"locality"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Ивановский район"</span>, <span class="string">"short_name"</span>: <span class="string">"Ивановский район"</span>, <span class="string">"types"</span>: [ <span class="string">"administrative_area_level_2"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Ивановская область"</span>, <span class="string">"short_name"</span>: <span class="string">"Ивановская область"</span>, <span class="string">"types"</span>: [ <span class="string">"administrative_area_level_1"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Россия"</span>, <span class="string">"short_name"</span>: <span class="string">"RU"</span>, <span class="string">"types"</span>: [ <span class="string">"country"</span>, <span class="string">"political"</span> ] } ], <span class="string">"geometry"</span>: { <span class="string">"location"</span>: { - местонахождение <span class="string">"lat"</span>: <span class="number">56.9924086</span>, <span class="string">"lng"</span>: <span class="number">40.9677888</span> }, <span class="string">"location_type"</span>: <span class="string">"APPROXIMATE"</span>, <span class="string">"viewport"</span>: { - размеры <span class="string">"southwest"</span>: { <span class="string">"lat"</span>: <span class="number">56.9699256</span>, <span class="string">"lng"</span>: <span class="number">40.9265167</span> }, <span class="string">"northeast"</span>: { <span class="string">"lat"</span>: <span class="number">57.0148916</span>, <span class="string">"lng"</span>: <span class="number">41.0090609</span> } }, <span class="string">"bounds"</span>: { - границы <span class="string">"southwest"</span>: { <span class="string">"lat"</span>: <span class="number">56.9699256</span>, <span class="string">"lng"</span>: <span class="number">40.9265167</span> }, <span class="string">"northeast"</span>: { <span class="string">"lat"</span>: <span class="number">57.0148916</span>, <span class="string">"lng"</span>: <span class="number">41.0090609</span> } } } } ] } |
Вся прелесть в том, что можно в address параметре передавать значение на любом языке(Ivanovo, Іваново, <тут была арабская вязь>), еще лучше, что для Санкт-Петербурга прокатывает «Спб» и «Питер». Правда есть и недочеты: мой родной город Ивано-Франковск упорно называет Ивано-Франковськ, на украинский манер.
Вторая возможность, это по координатам узнать адрес:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
{ <span class="string">"status"</span>: <span class="string">"OK"</span>, <span class="string">"results"</span>: [ { <span class="string">"types"</span>: [ <span class="string">"street_address"</span> ], <span class="string">"formatted_address"</span>: <span class="string">"Красная пл., 3, город Москва, Россия, 109012"</span>, <span class="string">"address_components"</span>: [ { <span class="string">"long_name"</span>: <span class="string">"3"</span>, <span class="string">"short_name"</span>: <span class="string">"3"</span>, <span class="string">"types"</span>: [ <span class="string">"street_address"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Красная пл."</span>, <span class="string">"short_name"</span>: <span class="string">"Красная пл."</span>, <span class="string">"types"</span>: [ <span class="string">"route"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Тверской"</span>, <span class="string">"short_name"</span>: <span class="string">"Тверской"</span>, <span class="string">"types"</span>: [ <span class="string">"sublocality"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"город Москва"</span>, <span class="string">"short_name"</span>: <span class="string">"город Москва"</span>, <span class="string">"types"</span>: [ <span class="string">"locality"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"АО Центральный"</span>, <span class="string">"short_name"</span>: <span class="string">"АО Центральный"</span>, <span class="string">"types"</span>: [ <span class="string">"administrative_area_level_2"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Москва"</span>, <span class="string">"short_name"</span>: <span class="string">"Москва"</span>, <span class="string">"types"</span>: [ <span class="string">"administrative_area_level_1"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"Россия"</span>, <span class="string">"short_name"</span>: <span class="string">"RU"</span>, <span class="string">"types"</span>: [ <span class="string">"country"</span>, <span class="string">"political"</span> ] }, { <span class="string">"long_name"</span>: <span class="string">"109012"</span>, <span class="string">"short_name"</span>: <span class="string">"109012"</span>, <span class="string">"types"</span>: [ <span class="string">"postal_code"</span> ] } ], <span class="string">"geometry"</span>: { <span class="string">"location"</span>: { <span class="string">"lat"</span>: <span class="number">55.7546971</span>, <span class="string">"lng"</span>: <span class="number">37.6215214</span> }, <span class="string">"location_type"</span>: <span class="string">"ROOFTOP"</span>, <span class="string">"viewport"</span>: { <span class="string">"southwest"</span>: { <span class="string">"lat"</span>: <span class="number">55.7515495</span>, <span class="string">"lng"</span>: <span class="number">37.6183738</span> }, <span class="string">"northeast"</span>: { <span class="string">"lat"</span>: <span class="number">55.7578447</span>, <span class="string">"lng"</span>: <span class="number">37.6246690</span> } } } }, { ... |
Супер! Для того чтобы указать свой адрес, можно просто кликнуть на свой дом, добавить квартиру – и всё. Иногда это не срабатывает, например, если дома стоят вплотную друг к другу и считываются как 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/
Как использовать.
Добавляем библиотеку
1 2 |
<span class="tag"><<span class="title">script</span> <span class="attribute">type</span>=<span class="value">"text/javascript"</span> <span class="attribute">src</span>=<span class="value">"/Media/script/map/markerclusterer_packed.js"</span>></span><span class="tag"></<span class="title">script</span>></span> |
Составляем массив маркеров, не добавляя в карту:
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="keyword">var</span> markers = []; <span class="keyword">var</span> marker = <span class="keyword">new</span> google.maps.Marker({ position: latlng }); markers.push(marker); markerClusterer = <span class="keyword">new</span> MarkerClusterer(_<span class="keyword">this</span>.map, markers, { maxZoom: <span class="number">13</span>, gridSize: <span class="number">50</span>, styles: <span class="literal">null</span> }); |
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="keyword">this</span>.getZoom = <span class="function"><span class="keyword">function</span> <span class="params">(bounds)</span> {</span> <span class="keyword">var</span> width = $(<span class="string">".map"</span>).width(); <span class="keyword">var</span> height = $(<span class="string">".map"</span>).height(); <span class="keyword">var</span> dlat = Math.abs(bounds.getNorthEast().lat() - bounds.getSouthWest().lat()); <span class="keyword">var</span> dlon = Math.abs(bounds.getNorthEast().lng() - bounds.getSouthWest().lng()); <span class="keyword">var</span> max = <span class="number">0</span>; <span class="keyword">if</span> (dlat > dlon) { max = dlat; } <span class="keyword">else</span> { max = dlon; } <span class="keyword">var</span> clat = Math.PI * Math.abs(bounds.getSouthWest().lat() + bounds.getNorthEast().lat()) / <span class="number">360.</span>; <span class="keyword">var</span> C = <span class="number">0.0000107288</span>; <span class="keyword">var</span> z0 = Math.ceil(Math.log(dlat / (C * height)) / Math.LN2); <span class="keyword">var</span> z1 = Math.ceil(Math.log(dlon / (C * width * Math.cos(clat))) / Math.LN2); <span class="comment">//18 – это максимальный zoom для google.maps</span> <span class="keyword">return</span> <span class="number">18</span> - ((z1 > z0) ? z1 : z0); } |
Функция для «прыжка» маркера:
1 2 3 4 5 6 7 8 9 |
<span class="keyword">this</span>.toggleBounceMarker = <span class="keyword">function</span>() { <span class="keyword">if</span> (_<span class="keyword">this</span>.setMarker.getAnimation() != <span class="literal">null</span>) { _<span class="keyword">this</span>.setMarker.setAnimation(<span class="literal">null</span>); } <span class="keyword">else</span> { _<span class="keyword">this</span>.setMarker.setAnimation(google.maps.Animation.BOUNCE); } } |
Получение адреса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<span class="keyword">this</span>.SetAddresses = <span class="function"><span class="keyword">function</span> <span class="params">(results)</span> {</span> $(<span class="string">".address_list"</span>).show(); $(<span class="string">".address_list"</span>).empty(); <span class="keyword">var</span> addressText = _<span class="keyword">this</span>.ComposeAddress(results[<span class="number">0</span>]); ... } <span class="comment">//Составить строку адреса по первому результату </span> <span class="keyword">this</span>.ComposeAddress = <span class="function"><span class="keyword">function</span> <span class="params">(item)</span> {</span> retAddress = <span class="string">""</span>; $.each(item.address_components, <span class="function"><span class="keyword">function</span> <span class="params">(i, address_item)</span> {</span> <span class="keyword">var</span> isOk = <span class="literal">false</span>; $.each(address_item.types, <span class="function"><span class="keyword">function</span> <span class="params">(j, typeName)</span> {</span> <span class="comment">//не будем брать значения адреса улицы и локали (города) - город потом будет в administrative_level_2</span> <span class="keyword">if</span> (typeName != <span class="string">"street_address"</span> && typeName != <span class="string">"locality"</span>) { isOk = <span class="literal">true</span>; } }); <span class="keyword">if</span> (isOk) { <span class="keyword">if</span> (retAddress == <span class="string">""</span>) { retAddress = address_item.long_name; } <span class="keyword">else</span> { retAddress = retAddress + <span class="string">", "</span> + address_item.long_name; } } }); <span class="keyword">return</span> retAddress; } |
Итого
Google Maps API – очень классная и удобная штука, которая легка в использовании и понимании. Единственно, что плохо – так это слабое покрытие регионов в России, так что сервисам, которые предполагается использовать в глубинке google.maps пока мало интересен, а вот для больших городов (особенно Москва и Питер), а также для Украины – всё отлично.
Geocoding – очень полезная вещь и при правильном использовании может стоить тех денег, что за нее просят (ну или Microsoft или Яндекс подоспеет с аналогом уже есть. Хотя насколько я знаю, картографическая информация стоит бешеных вложений.)
Пример\исходники
На живой пример можно глянуть тут: http://cocosanka.ru/map (может перестать работать если будет достигнут лимит в Geocoding). Вводите город, потом перетаскиваете маркер, потом загружаете картинку и сохранить. При клике на маркеры выводятся картинки.
Исходники: https://bitbucket.org/chernikov/citylocator
Источник: http://habrahabr.ru/post/110460/
https://corp2.info/razrabotka-i-sozdanie-sajtov-internet-magazinov-veb-proektov-kiev-1038.html
Leave a Reply