JavaScript за 60 секунд: работаем с картой (Geolocation API, Leaflet.js, Nominatim)

от автора

Доброго времени суток, друзья!

В этом небольшом туториале мы вместе с вами выполним три простых задания:

  • С помощью Geolocation API и Leaflet.js определим текущее местоположение пользователя и отобразим его на карте
  • Реализуем анимированный переход между городами
  • Реализуем переключение между адресами с предварительным получением названия объекта и его координат

Код проекта находится здесь.

Поиграть с кодом можно здесь:

Определяем текущее местоположение пользователя

Geolocation API позволяет пользователю предоставлять веб-приложению данные о своем местоположении. В приложении для запроса этих данных используется метод Geolocation.getCurrentPosition(). Данный метод принимает один обязательный и два опциональных параметра: success — функция обратного вызова, получающая объект Position при предоставлении разрешения, error — функция обратного вызова, получающая объект PositionError при отказе в доступе, и options — объект с настройками. Вот как это выглядит в коде:

navigator.geolocation.getCurrentPosition(success, error, {   // высокая точность   enableHighAccuracy: true })  function success({ coords }) {   // получаем широту и долготу   const { latitude, longitude } = coords   const position = [latitude, longitude]   console.log(position) // [широта, долгота] }  function error({ message }) {   console.log(message) // при отказе в доступе получаем PositionError: User denied Geolocation } 

Отображаем местоположение пользователя на карте

В качестве карты мы будем использовать Leaflet.js. Данный сервис является альтернативой Google Maps и OpenStreetMap, уступает им по функционалу, но подкупает простотой интерфейса. Создаем разметку, в которой подключаем стили и скрипт карты:

<head>   <!-- стили карты -->   <link       rel="stylesheet"       href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"       integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="       crossorigin=""     />     <!-- скрипт карты -->     <script       src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"       integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="       crossorigin=""     ></script>     <!-- наши стили -->     <link rel="stylesheet" href="style.css" /> </head> <body>   <!-- контейнер для карты -->   <div id="map"></div>   <!-- кнопка для вызова функции -->   <button id="my_position">My Position</button>   <!-- наш скрипт-модуль -->   <script src="script.js" type="module"></script> </body> 

Добавляем минимальные стили (style.css):

* {   margin: 0;   padding: 0;   box-sizing: border-box; }  body {   min-height: 100vh;   display: grid;   place-content: center;   place-items: center;   background-color: rgb(241, 241, 241); }  #map {   width: 480px;   height: 320px;   border-radius: 4px;   box-shadow: 0 0 1px #222; }  button {   padding: 0.25em 0.75em;   margin: 1em 0.5em;   cursor: pointer;   user-select: none; } 

Создаем модуль map.js следующего содержания:

// создаем локальные переменные для карты и маркера // каждый модуль имеет собственное пространство имен let map = null let marker = null  // функция принимает позицию - массив с широтой и долготой // и сообщение, отображаемое над маркером (tooltip) export function getMap(position, tooltip) {   // если карта не была инициализирована   if (map === null) {     // второй аргумент, принимаемый методом setView - это масштаб (zoom)     map = L.map('map').setView(position, 15)   } else return    // что-то типа рекламы   // без этого карта работать не будет   L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {     attribution:       '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'   }).addTo(map)    // добавляем маркер с сообщением   L.marker(position).addTo(map).bindPopup(tooltip).openPopup() } 

Наконец, создаем script.js:

// импортируем функцию import { getMap } from './map.js'  // находим кнопку и добавляем к ней обработчик document.getElementById('my_position').onclick = () => {   navigator.geolocation.getCurrentPosition(success, error, {     enableHighAccuracy: true   }) }  function success({ coords }) {   const { latitude, longitude } = coords   const currentPosition = [latitude, longitude]   // вызываем функцию, передавая ей текущую позицию и сообщение   getMap(currentPosition, 'You are here') }  function error({ message }) {   console.log(message) } 

Открываем index.html в браузере, нажимаем на кнопку, предоставляем разрешение на получение данных о местоположении, видим нашу позицию на карте.

Отлично. Двигаемся дальше.

Анимированный переход между городами

Предположим, что у нас имеется объект с тремя городами (Москва, Санкт-Петербург, Екатеринбург) и их координатами (db/cities.json):

{   "Moscow": {     "lat": "55.7522200",     "lon": "37.6155600"   },   "Saint-Petersburg": {     "lat": "59.9386300",     "lon": "30.3141300"   },   "Ekaterinburg": {     "lat": "56.8519000",     "lon": "60.6122000"   } } 

Нам необходимо реализовать плавное переключение между этими городами на карте.

Добавляем в разметку контейнер для городов:

<div id="cities"></div> 

Переписываем script.js:

import { getMap } from './map.js'  // получаем контейнер для городов const $cities = document.getElementById('cities')  ;(async () => {   // получаем объект с городами   const response = await fetch('./db/cities.json')   const cities = await response.json()   // перебираем города   for (const city in cities) {     // создаем кнопку     const $button = document.createElement('button')      // текстовое содержимое кнопки - название города     $button.textContent = city      // получаем широту и долготу     const { lat, lon } = cities[city]      // записываем название города, широту и долготу     // в соответствующие data-атрибуты     $button.dataset.city = city     $button.dataset.lat = lat     $button.dataset.lon = lon      // добавляем кнопку в контейнер     $cities.append($button)   } })()  // обрабатываем нажатие кнопки $cities.addEventListener('click', ({ target }) => {   // нас интересует только нажатие кнопки   if (target.tagName !== 'BUTTON') return    // получаем название города, широту и долготу из data-атрибутов   const { city, lat, lon } = target.dataset   const position = [lat, lon]   // вызываем функцию, передавая ей позицию и название города   getMap(position, city) }) 

Также немного изменим map.js:

let map = null let marker = null  export function getMap(position, tooltip) {   if (map === null) {     map = L.map('map').setView(position, 15)   } else {     // перемещение к следующей позиции     map.flyTo(position)   }    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {     attribution:       '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'   }).addTo(map)    // удаление предыдущего маркера   if (marker) {     map.removeLayer(marker)   }   marker = new L.Marker(position).addTo(map).bindPopup(tooltip).openPopup() } 

Открываем index.html. При нажатии первой кнопки сразу получаем позицию и название города. При нажатии второй и последующих кнопок плавно перемещаемся между городами.

Плавное переключением между адресами

Предположим, что у нас имеются три объекта с названиями и адресами (db/addresses.json):

{   "Театр драмы": "Октябрьская площадь, 2",   "Театр оперы и балета": "Проспект Ленина, 46А",   "Коляда-Театр": "Проспект Ленина, 97" } 

Нам необходимо реализовать переключение между этими объектами на карте. Но как нам это сделать без координат? Никак. Следовательно, нам каким-то образом нужно эти координаты получить. Для этого воспользуемся сервисом Nominatim от OpenStreetMap. О том, как правильно сформировать строку запроса, смотрите здесь. Я продемонстрирую лишь один из возможных вариантов.

Итак, создаем в разметке контейнер для адресов:

<div id="addresses"></div> 

Переписываем script.js:

// получаем контейнер для адресов const $addresses = document.getElementById('addresses')  ;(async () => {   // названия и адреса объектов   const response = await fetch('./db/addresses.json')   const addresses = await response.json()    // для каждого места   for (const place in addresses) {     // создаем кнопку     const $button = document.createElement('button')      $button.textContent = place      // получаем адрес     const address = addresses[place]      // формируем строку запроса     const query = address.replace(       /([А-ЯЁа-яё]+)\s([А-ЯЁа-яё]+),\s([0-9А-ЯЁа-яё]+)/,       '$3+$1+$2,+Екатеринбург'     )     // получаем, например, 2+Октябрьская+площадь,+Екатеринбург      // записываем данные в соответствующие data-атрибуты     $button.dataset.address = address     $button.dataset.query = query      $addresses.append($button)   } })()  // обрабатываем нажатие кнопки $addresses.addEventListener('click', async ({ target }) => {   if (target.tagName !== 'BUTTON') return    // получаем данные из data-атрибутов   const { address, query } = target.dataset    // получаем ответ от сервиса   const response = await fetch(     `https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`   )   // format - формат данных, limit - количество объектов с данными    // парсим ответ, извлекая нужные сведения   const { display_name, lat, lon } = (await response.json())[0]    // редактриуем название объекта   const name = display_name.match(/[А-ЯЁа-яё\s(«\-»)]+/)[0]    const position = [lat, lon]    // формируем сообщение   const tooltip = `${name}<br>${address}`    // вызываем функцию   getMap(position, tooltip) }) 

Открываем index.html. При нажатии первой кнопки сразу получаем позицию и название театра. При нажатии второй и последующих кнопок плавно перемещаемся между театрами.

Круто. Все работает, как ожидается.

На этом позвольте откланяться. Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание и хорошего дня.

ссылка на оригинал статьи https://habr.com/ru/post/532902/