Простейший редактор для Google Maps
В предыдущей заметке про Google Maps я рассказывал, как выполнять основные действия с картой и маркерами. Теперь же попробуем создать полноценный редактор, в котором можно будет добавлять, редактировать и удалять маркеры, а также выводить их список рядом с картой и перемещаться к обозначенному маркером месту по щелчку. Кроме того, редактор будет сохранять созданные маркеры между сеансами работы с картой (но сохранять в localStorage, иначе пришлось бы писать серверную часть).
Общая инициализация в целом остается такой же, как в прошлом примере. В HTML кроме divа для самой карты создадим еще список, в котором будем выводить названия и координаты маркеров и используем flex-верстку, чтобы разместить его рядом с картой, а карту сделать расширяемой:
<div id="all" style="display: flex">
<div style="flex-grow: 1; min-width: 210px">
<ul id="point_list">
</ul>
</div>
<div id="map_canvas" style="flex-grow: 4;min-width:600px; height:500px"></div>
</div>
JavaScript-код инициализации остается без изменений:
<script type="text/javascript"><!--
MyMap = function () {
self = this; // запоминаем this, указывающий на MyMap в self,
// это нам пригодится, чтобы обращаться к содержимому MyMap из всяких обработчиков событий, у которых this указывает на другие объекты
this.map = new google.maps.Map(document.getElementById("map_canvas"), {
zoom: 10,
center: { lat: 55.763585, lng: 37.560883 }, // коордианты можно задавать и в таком виде
mapTypeId: google.maps.MapTypeId.ROADMAP
});
this.markers = new Array; // здесь будут храниться все созданные маркеры
// функция moveMapTo порождает замыкание, которое запоминает переданные координаты и возвращает функцию для сдвига карты на них
this.moveMapTo = function(lat,lng) {
return function(e) {
self.map.panTo(new google.maps.LatLng(lat,lng));
}
};
// сюда будем вставлять функции, описанные дальше
};
myMap = new MyMap;
google.maps.event.addDomListener(window, 'load', myMap);
--></script>
Первое, что нам потребуется — это функция создания нового маркера makeMarker. Она будет вызываться в двух случаях: при двойном щелчке по карте или при ее загрузке и получении списка маркеров из сохраненных данных и получать в качестве параметров координаты и название маркера. Маркеры можно будет свободно переносить по карте в любой момент, для чего сразу ставим свойство draggble в true. Но перенос маркера нужно будет отслежвать, а кроме того, предусмотреть возможность его редактирования и удаления. Поэтому с помощью google.maps.event.addDomListener добавим два обработчика событий: dragend — окончание перетаскивания (в нем будем обновлять координаты в списке слева), dblclick — двойное нажатие, по которому будем редактировать название. Особенность этой функции в том, что она может принимать как обычные DOM-элементы, так и объекты Google Maps, такие как маркеры или даже саму карту.
this.makeMarker=function (coords,m_title) {
var marker = new google.maps.Marker({
position: coords, map: self.map, title: m_title, draggable:true
});
google.maps.event.addDomListener(marker,'dblclick',self.editMarker);
google.maps.event.addDomListener(marker,'dragend',self.onMarkerMove);
Далее создадим элемент li для списка слева, в котором выводятся все маркеры, и сохраним ссылку на него в самом маркере, в поле list_item. Эта ссылка будет использоваться в обработчиках выше, чтобы изменять координаты и название при редактировании маркера. Кроме того, на событие onclick элемента списка повесим функцию, которая будет передвигать карту к маркеру, который связан с этим элементом. В общем случае можно было бы добавить в элемент li ссылку на маркер, и использвать ее в обработчике для определения координат и перемещения карты. Но в данном случае оказалось проще воспользоваться уже готовой функцией moveMapTo. Эта функция использует прием под названием замыкание: возвращает функцию, внутри которой «запомнены» координаты точки, на которую нужно передвинуть карту.
var elm=document.createElement('li'); // создаем элемент для списка
marker.list_item = elm; // запоминаем ссылку на него в маркере
// здесь же можно было бы присвоить в marker дополнительные данные об объекте, если бы они у нас были:
// marker.data = { field1 : "значение1", field2: "значение2" };
elm.innerHTML=m_title+" <span>("+coords.lat.toFixed(2)+" "+coords.lng.toFixed(2)+")</span>"; // задаем выводимый в элементе текст
elm.onclick=self.moveMapTo(coords.lat,coords.lng); // задаем обработчик для перехода к маркеру на карте
document.getElementById('point_list').appendChild(elm); // добавляем элемент в список слева
Далее запоминаем созданный маркер в масив маркеров и вызываем функцию onUpdate, которая отвечает за сохранение всех созданных элементов. На этом функция makeMarker готова!
self.markers.push(marker);
self.onUpdate();
Теперь нам нужно создать обработчик, который будет вызывать ее при двойном щелчке по карте. Он создается так же, как любой другой обработчик. Основное отличие — в параметре e приходит существенно больше информации, чем для простых DOM-элементов. В частности, в свойстве latLng приходят географические координаты точки на карте, по которой был сделан щелчок. Далее вешаем этот обработчик на событие dblclick на самой карте с помощью уже упоминавшейся google.maps.event.addDomListener:
this.onMapDblClick=function (e) {
var title = window.prompt("Введите название точки"); // запрашиваем у пользователя название
self.makeMarker({lat:e.latLng.lat(),lng:e.latLng.lng()},title); // и передаем название и координаты, преобразовав их в хеш, содержащий обычные number
};
google.maps.event.addDomListener(this.map,'dblclick', self.onMapDblClick); // вешаем обработчик на двойной щелчок
Далее нужно создать обработчик двойного щелчка по маркеру. Он будет запрашивать новое его название, если оно не пустое, запоминать его в маркере и списке, если пустое — удалять соответствующий маркер. Именно в этом обработчике нам и пригодится ссылка на элемент списка слева: по ней мы будем обращаться к нему для изменения или удаления.
this.editMarker=function(e) {
var pos=self.markers.indexOf(this); // ищем номер текущего маркера в массиве всех созданных маркеров
var new_title = window.prompt("Введите новое название точки или оставьте пустым для удаления",this.title); // запрашиваем новое название
if (new_title=="") { // если оно пустое, удаляем маркер
document.getElementById('point_list').removeChild(this.list_item); // сначала удаляем элемент из списка слева
this.setMap(null); // чтобы убрать маркер с карты, нужно сообщить ему, что теперь он принадлежит несуществующей карте
delete self.markers[pos]; // удаляем маркер из массива запомненных
}
else { // иначе запоминаем новое название и обновляем в списке слева
this.title=new_title;
this.list_item.innerText = new_title+" <span>"+e.latLng.lat().toFixed(2)+", "+e.latLng.lng().toFixed(2)+")</span>"; // вот и пригодилась ссылка на li!
}
self.onUpdate();
};
Теперь обработчик перемещения маркера. Тут ничего сложного или интересного: выводим новые координаты в списке и заменяем обработчик-замыкание для перемещения на новые координаты. В конце всех обработчиков вызываемonUpdate, чтобы сохранить сделанные изменения.
this.onMarkerMove=function(e) {
this.list_item.innerHTML = this.title+" <span>"+e.latLng.lat().toFixed(2)+", "+e.latLng.lng().toFixed(2)+")</span>"; // обновляем координаты в списке
elm.onclick=self.moveMapTo(e.latLng.lat(),e.latLng.lng()); // вешаем новый обработчик для перемещения карты
self.onUpdate(); // сохраняем изменения
};
И вот теперь пришло время объявить саму функцию onUpdate. По идее, в ней должна производиться отправка данных через AJAX на сервер, чтобы сохранить их там (самый простой вариант — пересылать их в AJAX). Но в данном примере серверной части не предусмотрено, поэтому сохраняем их в локальное хранилище броузера — localStorage. Но объект marker содержит большое количество разной информации, в то время как нам для восстановления состояния в дальнейшем нужны только координаты маркера и его название. Поэтому сначала мы создадим вспомогательный массив из хешей, в которых отберем только долготу, широту и название маркера. Если бы у нас было поле data с дополнительными данными (см. makeMarker), его тоже имело бы смысл добавить в этот хеш. В принципе, заполнение массива можно было бы сделать обычным циклом, но более красивым решением является использование функционального стиля, в частности функции map. Ей передается функция обработчик, которой поочередно передается каждый элемент массива, а из возвращенных ею значений собирается новый массив, который и является результатом работы функции map.
Далее следует учесть, что в localStorage можно сохранять только строки, поэтому получившийся массив сначала преобразуем в JSON. Получаем такой код:
this.onUpdate=function() {
var data = self.markers.map(function (item) {
return { lat: item.position.lat(), lng: item.position.lng(),title: item.title /*, сюда добавить data: item.data, если бы у нас были дополнительные данные об объекте */ }
});
// тут должен был бы быть XMLHttpRequest для отправки на сервер
window.localStorage['XDemoMap']=JSON.stringify(data); // преобразуем данные в JSON-строку и отправляем в хранилище
};
Теперь остается добавить только код загрузки уже сохраненных данных. Я решил сделать так: если данных нет, создается три новых маркера с фиксированными точками. Выглядит этот код так:
// если в хранилище нет данных, заполняем карту начальными маркерами
if (window.localStorage['XDemoMap']==undefined) {
self.makeMarker({lat:55.763525,lng:37.560893},"Москва");
self.makeMarker({lat:59.939956,lng:30.344911},"Санкт-Петербург");
self.makeMarker({lat:56.325418,lng:43.935667},"Нижний Новгород");
}
else { // иначе декодируем данные из JSON и для каждого элемента там создаем маркер
JSON.parse(window.localStorage['XDemoMap']).forEach(item=>self.makeMarker(item,item.title));
// используем стрелочную запись функции для экономии места, forEach вызывает ее для каждого элемента массива
}
Таким образом, получился редактор карты, в котором есть все базовые операции: создание, перемещение, редактирование, удаление, сохранение и загрузка объектов. Для его дальнейшего развития нужно заменить вызов prompt на показ формы для редактирования данных и обеспечить их сохранение в поле data. (В этом случае обработчики editMarker и onMapDblClick потребуется разделить на две части: одна будет показывать форму по двойному щелчку, вторая — создавать/изменять данные по нажатию кнопки «Сохранить».) Работающий пример можно посмотреть по ссылке. Остальные заметки серии про работу с Картами Google доступны по тегу Google Maps.