Работа с поиском географических объектов в приложениях поверх MapLibre GL JS почти всегда требует интеграции с геокодингом — сервисом преобразования текстовых запросов в координаты и обратно. В современных интерфейсах карта без поиска воспринимается как статичная визуализация, тогда как связка карты и геокодинга формирует полноценный инструмент навигации и анализа данных.
Геокодинг делится на два основных типа:
[lng, lat]Типичная схема взаимодействия:
Пользователь вводит запрос в поисковую строку
Приложение отправляет запрос в геокодинг API
API возвращает список совпадений с координатами
Выбранный результат используется для управления картой:
map.flyTo)MapLibre GL JS не предоставляет встроенного геокодера, поэтому интеграция всегда внешняя.
На практике используются несколько популярных решений:
Mapbox предоставляет мощный коммерческий API геокодинга с высокой точностью и автодополнением.
Преимущества:
Ограничения:
Nominatim — бесплатный геокодер на базе OpenStreetMap.
Преимущества:
Ограничения:
Пример реализации поиска через fetch и последующего обновления карты.
async function geocode(query) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(query)}.json` +
`?access_token=${MAPBOX_TOKEN}&limit=5`;
const response = await fetch(url);
if (!response.ok) {
throw new Error('Geocoding request failed');
}
const data = await response.json();
return data.features;
}
После получения координат выполняется обновление состояния карты:
function flyToResult(map, feature) {
const [lng, lat] = feature.center;
map.flyTo({
center: [lng, lat],
zoom: 14,
essential: true
});
}
Добавление маркера:
const marker = new maplibregl.Marker()
.setLngLat([lng, lat])
.addTo(map);
Popup с информацией:
new maplibregl.Popup()
.setLngLat([lng, lat])
.setHTML(`<strong>${feature.place_name}</strong>`)
.addTo(map);
Одной из ключевых задач является минимизация количества запросов к API. Для этого используется debounce.
function debounce(fn, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение:
const searchInput = document.getElementById('search');
searchInput.addEventListener(
'input',
debounce(async (e) => {
const results = await geocode(e.target.value);
renderSuggestions(results);
}, 300)
);
UI-слой обычно строится отдельно от карты:
function renderSuggestions(features) {
const container = document.getElementById('suggestions');
container.innerHTML = '';
features.forEach(feature => {
const item = document.createElement('div');
item.className = 'suggestion-item';
item.textContent = feature.place_name;
item.oncl ick = () => {
flyToResult(map, feature);
container.innerHTML = '';
};
container.appendChild(item);
});
}
Reverse geocoding применяется при клике по карте или перемещении курсора.
async function reverseGeocode(lng, lat) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json` +
`?access_token=${MAPBOX_TOKEN}`;
const response = await fetch(url);
const data = await response.json();
return data.features[0];
}
Интеграция с событием клика:
map.on('click', async (e) => {
const feature = await reverseGeocode(e.lngLat.lng, e.lngLat.lat);
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(feature.place_name)
.addTo(map);
});
Геокодинг часто используется совместно с динамическими слоями:
setData при выборе результатаmap.addSource('search-result', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
Обновление:
function updateSource(feature) {
map.getSource('search-result').setData({
type: 'FeatureCollection',
features: [feature]
});
}
При активном использовании поиска важно снижать нагрузку на API.
Подходы:
Пример кеша:
const cache = new Map();
async function cachedGeocode(query) {
if (cache.has(query)) {
return cache.get(query);
}
const results = await geocode(query);
cache.set(query, results);
return results;
}
При быстром вводе необходимо отменять предыдущие запросы:
let controller;
async function geocodeCancelable(query) {
if (controller) controller.abort();
controller = new AbortController();
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${query}.json?access_token=${MAPBOX_TOKEN}`;
const response = await fetch(url, {
signal: controller.signal
});
return response.json();
}
Типичные проблемы:
Пример обработки:
try {
const results = await geocode(query);
if (!results.length) {
showMessage('Ничего не найдено');
}
} catch (err) {
showMessage('Ошибка поиска');
}
При использовании OpenStreetMap возможно прямое обращение к сервису:
async function nominatimGeocode(query) {
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}`;
const response = await fetch(url, {
headers: {
'User-Agent': 'Map Application'
}
});
return response.json();
}
Reverse geocoding:
async function nominatimReverse(lng, lat) {
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`;
const response = await fetch(url);
return response.json();
}
Геокодинг влияет на несколько уровней приложения:
Пример синхронизации URL:
function updateUrl(lng, lat, zoom) {
const url = new URL(window.location);
url.searchParams.set('lng', lng);
url.searchParams.set('lat', lat);
url.searchParams.set('zoom', zoom);
window.history.replaceState({}, '', url);
}
Дополнительные сценарии:
При этом геокодинг становится не отдельной функцией, а частью общей гео-логики приложения поверх MapLibre GL JS.