Обратное геокодирование

Обратное геокодирование (Reverse Geocoding) — процесс преобразования географических координат в человекочитаемый адрес или описание объекта местности. Если прямое геокодирование отвечает на вопрос «какие координаты соответствуют этому адресу?», то обратное геокодирование решает противоположную задачу: «какой адрес находится по этим координатам?».

В веб-картографии обратное геокодирование используется для:

  • определения адреса по клику на карте;
  • отображения названия улицы или населённого пункта под маркером;
  • заполнения адресных форм автоматически;
  • работы мобильных приложений с GPS-координатами;
  • определения ближайших объектов инфраструктуры;
  • построения сервисов доставки и навигации.

В экосистеме Mapbox обратное геокодирование выполняется через Geocoding API, а Mapbox GL JS используется для получения координат пользователя или точки на карте и отображения результата.


Принцип работы обратного геокодирования

Последовательность обработки данных обычно выглядит следующим образом:

  1. Пользователь выбирает точку на карте.
  2. Mapbox GL JS получает координаты точки.
  3. Выполняется запрос к Geocoding API.
  4. API возвращает сведения о местоположении.
  5. Результат отображается в интерфейсе.

Схема взаимодействия:

Клик по карте
       ↓
Получение longitude и latitude
       ↓
Запрос в Geocoding API
       ↓
JSON-ответ с адресом
       ↓
Отображение информации

Подключение карты

Базовая настройка карты:

mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [37.6176, 55.7558],
    zoom: 10
});

HTML-контейнер:

<div id="map"></div>

CSS:

#map {
    width: 100%;
    height: 600px;
}

Получение координат при клике

Для обратного геокодирования необходимо получить координаты выбранной точки.

Mapbox GL JS предоставляет объект события клика:

map.on('click', (event) => {

    const lng = event.lngLat.lng;
    const lat = event.lngLat.lat;

    console.log(lng, lat);

});

Свойство lngLat содержит:

{
    lng: 37.6176,
    lat: 55.7558
}

Полученные координаты становятся входными данными для Geocoding API.


Выполнение запроса к Geocoding API

Формат URL обратного геокодирования:

https://api.mapbox.com/search/geocode/v6/reverse

Параметры:

Параметр Назначение
longitude Долгота
latitude Широта
access_token Токен доступа
limit Максимальное количество результатов
language Язык ответа
types Типы объектов

Пример запроса:

async function reverseGeocode(lng, lat) {

    const url =
        `https://api.mapbox.com/search/geocode/v6/reverse?` +
        `longitude=${lng}&latitude=${lat}` +
        `&access_token=${mapboxgl.accessToken}`;

    const response = await fetch(url);

    const data = await response.json();

    console.log(data);
}

Вызов:

map.on('click', (e) => {

    reverseGeocode(
        e.lngLat.lng,
        e.lngLat.lat
    );

});

Структура ответа API

Типичный ответ содержит массив объектов:

{
  "features": [
    {
      "properties": {
        "full_address": "Красная площадь, Москва"
      },
      "geometry": {
        "coordinates": [
          37.620393,
          55.75396
        ]
      }
    }
  ]
}

Чаще всего используются:

data.features[0]

или

const place = data.features[0];

Основные данные:

place.properties.full_address
place.properties.name
place.properties.context

Получение полного адреса

Наиболее востребованная информация — полный адрес.

async function reverseGeocode(lng, lat) {

    const url =
        `https://api.mapbox.com/search/geocode/v6/reverse?` +
        `longitude=${lng}&latitude=${lat}` +
        `&access_token=${mapboxgl.accessToken}`;

    const response = await fetch(url);
    const data = await response.json();

    const address =
        data.features[0].properties.full_address;

    console.log(address);
}

Результат:

Красная площадь, Москва, Россия

Отображение адреса во всплывающем окне

Частый сценарий — показ адреса непосредственно на карте.

map.on('click', async (e) => {

    const lng = e.lngLat.lng;
    const lat = e.lngLat.lat;

    const url =
        `https://api.mapbox.com/search/geocode/v6/reverse?` +
        `longitude=${lng}&latitude=${lat}` +
        `&access_token=${mapboxgl.accessToken}`;

    const response = await fetch(url);
    const data = await response.json();

    const address =
        data.features[0].properties.full_address;

    new mapboxgl.Popup()
        .setLngLat([lng, lat])
        .setHTML(`<strong>${address}</strong>`)
        .addTo(map);

});

После клика пользователь увидит адрес выбранной точки.


Добавление маркера

Для визуального обозначения найденной точки удобно использовать маркер.

map.on('click', async (e) => {

    const lng = e.lngLat.lng;
    const lat = e.lngLat.lat;

    new mapboxgl.Marker()
        .setLngLat([lng, lat])
        .addTo(map);

});

Комбинация маркера и всплывающего окна:

const marker = new mapboxgl.Marker()
    .setLngLat([lng, lat]);

const popup = new mapboxgl.Popup()
    .setHTML(address);

marker
    .setPopup(popup)
    .addTo(map);

Ограничение количества результатов

Иногда требуется получить только один наиболее релевантный адрес.

Используется параметр:

limit=1

Пример:

const url =
    `https://api.mapbox.com/search/geocode/v6/reverse` +
    `?longitude=${lng}` +
    `&latitude=${lat}` +
    `&limit=1` +
    `&access_token=${mapboxgl.accessToken}`;

Это уменьшает объём ответа и ускоряет обработку.


Локализация результата

API поддерживает множество языков.

Русский язык:

language=ru

Пример:

const url =
    `https://api.mapbox.com/search/geocode/v6/reverse` +
    `?longitude=${lng}` +
    `&latitude=${lat}` +
    `&language=ru` +
    `&access_token=${mapboxgl.accessToken}`;

Английский язык:

language=en`

Несколько языков:

language=ru,en

Фильтрация типов объектов

Обратное геокодирование может возвращать различные сущности:

  • страны;
  • регионы;
  • города;
  • районы;
  • улицы;
  • адреса;
  • достопримечательности.

Для фильтрации используется параметр:

types=

Пример только адресов:

types=address

Только населённые пункты:

types=place

Несколько типов:

types=address,street

Полный пример:

const url =
    `https://api.mapbox.com/search/geocode/v6/reverse` +
    `?longitude=${lng}` +
    `&latitude=${lat}` +
    `&types=address` +
    `&access_token=${mapboxgl.accessToken}`;

Получение ближайшего объекта

Координаты могут находиться далеко от конкретного адреса. В этом случае сервис возвращает ближайший известный объект.

Например:

{
    "name": "Красная площадь"
}

или

{
    "name": "Московский Кремль"
}

Такой подход особенно полезен для:

  • туристических приложений;
  • навигационных сервисов;
  • мобильных карт.

Работа с несколькими результатами

Обратное геокодирование может возвращать несколько вариантов.

Пример обхода результатов:

data.features.forEach(feature => {

    console.log(
        feature.properties.full_address
    );

});

Формирование списка:

const addresses =
    data.features.map(feature =>
        feature.properties.full_address
    );

console.log(addresses);

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

Mapbox GL JS поддерживает геолокацию браузера.

Добавление элемента управления:

const geolocate =
    new mapboxgl.GeolocateControl({
        trackUserLocation: true
    });

map.addControl(geolocate);

Получение координат:

geolocate.on('geolocate', async (position) => {

    const lng =
        position.coords.longitude;

    const lat =
        position.coords.latitude;

    reverseGeocode(lng, lat);

});

Функция обратного геокодирования:

async function reverseGeocode(lng, lat) {

    const response = await fetch(
        `https://api.mapbox.com/search/geocode/v6/reverse` +
        `?longitude=${lng}` +
        `&latitude=${lat}` +
        `&access_token=${mapboxgl.accessToken}`
    );

    const data = await response.json();

    console.log(
        data.features[0].properties.full_address
    );
}

Обработка отсутствия результатов

Не каждая точка имеет адрес.

Например:

  • океан;
  • пустыня;
  • ледники;
  • малонаселённые территории.

Проверка:

if (!data.features.length) {

    console.log(
        'Адрес не найден'
    );

    return;
}

Без такой проверки возможна ошибка:

Cannot read properties of undefined

Обработка сетевых ошибок

Работа с удалённым API всегда требует обработки исключений.

async function reverseGeocode(lng, lat) {

    try {

        const response = await fetch(url);

        if (!response.ok) {
            throw new Error('Ошибка API');
        }

        const data = await response.json();

        return data;

    } catch (error) {

        console.error(error);

    }
}

Повторное использование маркера

Создание нового маркера после каждого клика может приводить к накоплению объектов.

Более эффективный вариант:

const marker =
    new mapboxgl.Marker();

map.on('click', (e) => {

    marker
        .setLngLat(e.lngLat)
        .addTo(map);

});

Теперь один и тот же маркер просто перемещается.


Асинхронная архитектура приложения

Распространённая структура:

async function reverseGeocode(lng, lat) {

    const response =
        await fetch(createUrl(lng, lat));

    return response.json();
}

function createUrl(lng, lat) {

    return (
        `https://api.mapbox.com/search/geocode/v6/reverse` +
        `?longitude=${lng}` +
        `&latitude=${lat}` +
        `&access_token=${mapboxgl.accessToken}`
    );
}

function showAddress(address) {

    document
        .querySelector('#address')
        .textContent = address;
}

Использование:

map.on('click', async (e) => {

    const data =
        await reverseGeocode(
            e.lngLat.lng,
            e.lngLat.lat
        );

    const address =
        data.features[0]
            .properties.full_address;

    showAddress(address);

});

Практический пример

Полноценная реализация обратного геокодирования по клику:

mapboxgl.accessToken = 'YOUR_TOKEN';

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [37.6176, 55.7558],
    zoom: 11
});

const marker = new mapboxgl.Marker();

map.on('click', async (e) => {

    const lng = e.lngLat.lng;
    const lat = e.lngLat.lat;

    const response = await fetch(
        `https://api.mapbox.com/search/geocode/v6/reverse` +
        `?longitude=${lng}` +
        `&latitude=${lat}` +
        `&limit=1` +
        `&language=ru` +
        `&access_token=${mapboxgl.accessToken}`
    );

    const data = await response.json();

    if (!data.features.length) {
        return;
    }

    const address =
        data.features[0]
            .properties.full_address;

    marker
        .setLngLat([lng, lat])
        .addTo(map);

    new mapboxgl.Popup()
        .setLngLat([lng, lat])
        .setHTML(address)
        .addTo(map);

});

Такая схема охватывает основные сценарии работы с обратным геокодированием в Mapbox GL JS: получение координат, обращение к Geocoding API, извлечение адресных данных, локализацию результатов, фильтрацию типов объектов, обработку ошибок и отображение найденной информации непосредственно на карте.