Маршрутизация в веб-картах на базе MapLibre GL JS строится вокруг связки внешнего routing-сервиса и визуализации геометрии маршрута в виде GeoJSON-слоя. Библиотека не предоставляет собственного движка построения маршрутов, поэтому вся логика расчёта пути выносится в специализированные API, а MapLibre GL JS отвечает за отображение, интерактивность и обновление данных на карте.
Типичная схема включает три уровня:
Ключевой принцип — маршрут всегда приходит как геометрия (LineString или encoded polyline), которая преобразуется в GeoJSON.
Для отображения маршрута используется динамический
GeoJSON source:
map.on('load', () => {
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: []
}
}
});
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
paint: {
'line-color': '#3b82f6',
'line-width': 5,
'line-opacity': 0.85
}
});
});
Пустая геометрия инициализируется заранее, чтобы позже обновлять маршрут без пересоздания слоя.
Одним из самых распространённых решений является OSRM (Open Source Routing Machine).
Запрос формируется по координатам:
async function fetchRoute(start, end) {
const query = `https://router.project-osrm.org/route/v1/driving/` +
`${start[0]},${start[1]};${end[0]},${end[1]}` +
`?overview=full&geometries=geojson`;
const response = await fetch(query);
const data = await response.json();
return data.routes[0].geometry;
}
Ключевые параметры:
overview=full — полная геометрия маршрутаgeometries=geojson — возврат в формате GeoJSON вместо
polylineПосле получения геометрии выполняется обновление источника:
async function updateRoute(start, end) {
const geometry = await fetchRoute(start, end);
map.getSource('route').setData({
type: 'Feature',
geometry: geometry
});
}
Обновление через setData позволяет мгновенно
перерисовывать линию без пересоздания слоя.
Некоторые API возвращают несколько вариантов пути. Структура обычно
содержит массив routes.
const routeGeo = data.routes.map(r => ({
geometry: r.geometry,
distance: r.distance,
duration: r.duration
}));
Отображение альтернатив реализуется через несколько источников:
map.addSource('route-alt', {
type: 'geojson',
data: { type: 'FeatureCollection', features: [] }
});
Каждый маршрут может быть оформлен разной прозрачностью:
Часто используется сценарий выбора точек кликом по карте:
let start = null;
let end = null;
map.on('click', (e) => {
if (!start) {
start = [e.lngLat.lng, e.lngLat.lat];
} else {
end = [e.lngLat.lng, e.lngLat.lat];
updateRoute(start, end);
start = null;
end = null;
}
});
Такая модель применяется в минималистичных навигационных интерфейсах.
Для более сложного UX используется draggable markers:
const markerStart = new maplibregl.Marker({ draggable: true })
.setLngLat([30.3, 59.9])
.addTo(map);
const markerEnd = new maplibregl.Marker({ draggable: true })
.setLngLat([30.4, 59.8])
.addTo(map);
function refresh() {
const start = markerStart.getLngLat();
const end = markerEnd.getLngLat();
updateRoute([start.lng, start.lat], [end.lng, end.lat]);
}
markerStart.on('dragend', refresh);
markerEnd.on('dragend', refresh);
Пересчёт маршрута происходит при каждом завершении drag-события.
Некоторые сервисы используют encoded polyline. В этом случае требуется декодирование:
function decodePolyline(str) {
let index = 0;
let lat = 0;
let lng = 0;
const coordinates = [];
while (index < str.length) {
let b, shift = 0, result = 0;
do {
b = str.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
const dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = str.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
const dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
lng += dlng;
coordinates.push([lng / 1e5, lat / 1e5]);
}
return coordinates;
}
После декодирования данные преобразуются в GeoJSON LineString.
Визуальное оформление маршрута критично для читаемости карты.
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
paint: {
'line-color': [
'interpolate',
['linear'],
['line-progress'],
0, '#22c55e',
1, '#3b82f6'
],
'line-width': 6,
'line-blur': 0.5
}
});
Возможные приёмы:
line-dasharrayЧтобы избежать визуальных артефактов:
setData, а не удаление слояlet cachedRoute = null;
function safeUpdate(geometry) {
if (JSON.stringify(cachedRoute) === JSON.stringify(geometry)) return;
cachedRoute = geometry;
map.getSource('route').setData({
type: 'Feature',
geometry
});
}
Routing API часто возвращают legs и
steps:
const steps = data.routes[0].legs[0].steps;
Каждый шаг содержит:
Эти данные используются для построения UI навигации и синхронизации с позицией на карте.
Для анимации движения по маршруту применяется интерполяция координат:
function animateAlongRoute(coords, progress) {
const index = Math.floor(progress * (coords.length - 1));
return coords[index];
}
Marker обновляется через requestAnimationFrame, создавая
эффект движения по линии.
При интерактивных сценариях (drag, live routing) важны ограничения:
Типовые случаи:
Обработка строится через fallback:
if (!data.routes || data.routes.length === 0) {
map.getSource('route').setData({
type: 'Feature',
geometry: { type: 'LineString', coordinates: [] }
});
}
В MapLibre GL JS маршруты часто визуализируются поверх 3D-ландшафта:
fill-extrusion для зданийpaint: {
'line-translate': [0, 0],
'line-z-offset': 1
}
Это предотвращает «проваливание» линии в поверхность карты.
При расширенных данных можно окрашивать маршрут по типу:
Используется feature-state или разделение LineString на
сегменты с разными стилями.
Автоматическое центрирование:
const bounds = new maplibregl.LngLatBounds();
coords.forEach(c => bounds.extend(c));
map.fitBounds(bounds, {
padding: 50
});
Это обеспечивает корректное отображение всего маршрута на экране.