Простейший редактор для Google Maps
В предыдущей заметке про Google Maps я рассказывал, как выполнять основные действия с картой и маркерами. Теперь же попробуем создать полноценный редактор, в котором можно будет добавлять, редактировать и удалять маркеры, а также выводить их список рядом с картой и перемещаться к обозначенному маркером месту по щелчку. Кроме того, редактор будет сохранять созданные маркеры между сеансами работы с картой (но сохранять в localStorage, иначе пришлось бы писать серверную часть).
Общая инициализация в целом остается такой же, как в прошлом примере. В HTML кроме divа для самой карты создадим еще список, в котором будем выводить названия и координаты маркеров и используем flex-верстку, чтобы разместить его рядом с картой, а карту сделать расширяемой:
JavaScript-код инициализации остается без изменений:
Первое, что нам потребуется — это функция создания нового маркера makeMarker. Она будет вызываться в двух случаях: при двойном щелчке по карте или при ее загрузке и получении списка маркеров из сохраненных данных и получать в качестве параметров координаты и название маркера. Маркеры можно будет свободно переносить по карте в любой момент, для чего сразу ставим свойство draggble в true. Но перенос маркера нужно будет отслежвать, а кроме того, предусмотреть возможность его редактирования и удаления. Поэтому с помощью google.maps.event.addDomListener добавим два обработчика событий: dragend — окончание перетаскивания (в нем будем обновлять координаты в списке слева), dblclick — двойное нажатие, по которому будем редактировать название. Особенность этой функции в том, что она может принимать как обычные DOM-элементы, так и объекты Google Maps, такие как маркеры или даже саму карту.
Далее создадим элемент li для списка слева, в котором выводятся все маркеры, и сохраним ссылку на него в самом маркере, в поле list_item. Эта ссылка будет использоваться в обработчиках выше, чтобы изменять координаты и название при редактировании маркера. Кроме того, на событие onclick элемента списка повесим функцию, которая будет передвигать карту к маркеру, который связан с этим элементом. В общем случае можно было бы добавить в элемент li ссылку на маркер, и использвать ее в обработчике для определения координат и перемещения карты. Но в данном случае оказалось проще воспользоваться уже готовой функцией moveMapTo. Эта функция использует прием под названием замыкание: возвращает функцию, внутри которой «запомнены» координаты точки, на которую нужно передвинуть карту.
Далее запоминаем созданный маркер в масив маркеров и вызываем функцию onUpdate, которая отвечает за сохранение всех созданных элементов. На этом функция makeMarker готова!
Теперь нам нужно создать обработчик, который будет вызывать ее при двойном щелчке по карте. Он создается так же, как любой другой обработчик. Основное отличие — в параметре e приходит существенно больше информации, чем для простых DOM-элементов. В частности, в свойстве latLng приходят географические координаты точки на карте, по которой был сделан щелчок. Далее вешаем этот обработчик на событие dblclick на самой карте с помощью уже упоминавшейся google.maps.event.addDomListener:
Далее нужно создать обработчик двойного щелчка по маркеру. Он будет запрашивать новое его название, если оно не пустое, запоминать его в маркере и списке, если пустое — удалять соответствующий маркер. Именно в этом обработчике нам и пригодится ссылка на элемент списка слева: по ней мы будем обращаться к нему для изменения или удаления.
Теперь обработчик перемещения маркера. Тут ничего сложного или интересного: выводим новые координаты в списке и заменяем обработчик-замыкание для перемещения на новые координаты. В конце всех обработчиков вызываемonUpdate, чтобы сохранить сделанные изменения.
И вот теперь пришло время объявить саму функцию onUpdate. По идее, в ней должна производиться отправка данных через AJAX на сервер, чтобы сохранить их там (самый простой вариант — пересылать их в AJAX). Но в данном примере серверной части не предусмотрено, поэтому сохраняем их в локальное хранилище броузера — localStorage. Но объект marker содержит большое количество разной информации, в то время как нам для восстановления состояния в дальнейшем нужны только координаты маркера и его название. Поэтому сначала мы создадим вспомогательный массив из хешей, в которых отберем только долготу, широту и название маркера. Если бы у нас было поле data с дополнительными данными (см. makeMarker), его тоже имело бы смысл добавить в этот хеш. В принципе, заполнение массива можно было бы сделать обычным циклом, но более красивым решением является использование функционального стиля, в частности функции map. Ей передается функция обработчик, которой поочередно передается каждый элемент массива, а из возвращенных ею значений собирается новый массив, который и является результатом работы функции map.
Далее следует учесть, что в localStorage можно сохранять только строки, поэтому получившийся массив сначала преобразуем в JSON. Получаем такой код:
Теперь остается добавить только код загрузки уже сохраненных данных. Я решил сделать так: если данных нет, создается три новых маркера с фиксированными точками. Выглядит этот код так:
Таким образом, получился редактор карты, в котором есть все базовые операции: создание, перемещение, редактирование, удаление, сохранение и загрузка объектов. Для его дальнейшего развития нужно заменить вызов prompt на показ формы для редактирования данных и обеспечить их сохранение в поле data. (В этом случае обработчики editMarker и onMapDblClick потребуется разделить на две части: одна будет показывать форму по двойному щелчку, вторая — создавать/изменять данные по нажатию кнопки «Сохранить».) Работающий пример можно посмотреть по ссылке. Остальные заметки серии про работу с Картами Google доступны по тегу Google Maps.
Общая инициализация в целом остается такой же, как в прошлом примере. В 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.