[Разработка веб-сайтов, JavaScript, Программирование] JavaScript за 60 секунд: работаем с картой (Geolocation API, Leaflet.js, Nominatim)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
14-Дек-2020 14:32


Доброго времени суток, друзья!
В этом небольшом туториале мы вместе с вами выполним три простых задания:
  • С помощью 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. При нажатии первой кнопки сразу получаем позицию и название театра. При нажатии второй и последующих кнопок плавно перемещаемся между театрами.

Круто. Все работает, как ожидается.
На этом позвольте откланяться. Надеюсь, вы нашли для себя что-нибудь интересное. Благодарю за внимание и хорошего дня.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_programmirovanie (Программирование), #_javascript, #_programmirovanie (программирование), #_razrabotka (разработка), #_map, #_karta (карта), #_geolocation_api, #_geolocation, #_geolokatsija (геолокация), #_opredelenie_mestopolozhenija (определение местоположения), #_leaflet.js, #_google_maps, #_openstreetmap, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_javascript, #_programmirovanie (
Программирование
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 03:44
Часовой пояс: UTC + 5