Отображение маршрута на карте является одной из наиболее востребованных задач при разработке геоинформационных веб-приложений. Маршрут может представлять собой автомобильный путь, велосипедную трассу, пешеходное направление, логистическую цепочку доставки или произвольную линию движения объекта.
В Mapbox GL JS маршрут обычно отображается как геометрия типа LineString, которая визуализируется через отдельный слой карты. Источником данных может выступать:
Mapbox GL JS использует формат GeoJSON в качестве основного механизма передачи пространственных данных.
Простейший маршрут состоит из массива координат, объединённых в
объект типа LineString.
const route = {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [
[37.6176, 55.7558],
[37.6200, 55.7575],
[37.6250, 55.7600],
[37.6300, 55.7625]
]
}
};
Каждая координата записывается в формате:
[долгота, широта]
Важно помнить, что порядок координат в GeoJSON отличается от привычного представления географических координат.
Правильно:
[37.6176, 55.7558]
Неправильно:
[55.7558, 37.6176]
Перед отображением маршрута необходимо создать экземпляр карты.
mapboxgl.accessToken = 'YOUR_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6176, 55.7558],
zoom: 12
});
После загрузки карты можно добавлять источники и слои.
map.on('load', () => {
});
Событие load гарантирует, что стиль карты полностью
загружен и готов к работе.
Сначала создаётся источник GeoJSON.
map.addSource('route', {
type: 'geojson',
data: route
});
Источник представляет собой контейнер данных, который затем используется одним или несколькими слоями.
Структура выглядит следующим образом:
GeoJSON
↓
Source
↓
Layer
↓
Отображение на карте
После регистрации источника необходимо создать слой визуализации.
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
paint: {
'line-color': '#0078ff',
'line-width': 5
}
});
Результатом станет синяя линия, соединяющая все точки маршрута.
Цвет задаётся через свойство:
'line-color'
Примеры:
'line-color': '#ff0000'
'line-color': '#00aa55'
'line-color': 'orange'
Также допускается использование выражений Mapbox Expression.
Например, выбор цвета по свойствам объекта:
'line-color': [
'match',
['get', 'status'],
'active',
'#00cc66',
'#ff4444'
]
Толщина маршрута определяется свойством:
'line-width'
Пример:
'line-width': 8
Для адаптации толщины к масштабу карты удобно использовать интерполяцию.
'line-width': [
'interpolate',
['linear'],
['zoom'],
5, 2,
10, 5,
15, 10
]
При увеличении масштаба линия будет становиться толще.
По умолчанию соединения сегментов могут выглядеть достаточно жёстко.
Для более плавного отображения используются настройки:
layout: {
'line-join': 'round',
'line-cap': 'round'
}
Полный пример:
map.addLayer({
id: 'route-line',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#0078ff',
'line-width': 6
}
});
Параметры:
| Свойство | Назначение |
|---|---|
| line-join | Соединение сегментов |
| line-cap | Окончания линии |
Для обозначения альтернативного пути часто используются пунктирные линии.
paint: {
'line-color': '#ff5500',
'line-width': 4,
'line-dasharray': [2, 2]
}
Возможные варианты:
[1, 1]
[2, 4]
[5, 2]
Прозрачность задаётся через:
'line-opacity'
Пример:
paint: {
'line-color': '#0066ff',
'line-width': 6,
'line-opacity': 0.5
}
Значение находится в диапазоне:
0.0 — полностью прозрачный
1.0 — полностью видимый
Маршрут обычно сопровождается отображением его начала и конца.
Источник данных:
const points = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {
type: "start"
},
geometry: {
type: "Point",
coordinates: [37.6176, 55.7558]
}
},
{
type: "Feature",
properties: {
type: "finish"
},
geometry: {
type: "Point",
coordinates: [37.6300, 55.7625]
}
}
]
};
Добавление источника:
map.addSource('points', {
type: 'geojson',
data: points
});
Слой отображения:
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points',
paint: {
'circle-radius': 8,
'circle-color': [
'match',
['get', 'type'],
'start',
'#00cc66',
'#ff3333'
]
}
});
После загрузки маршрута желательно показать его целиком.
Для этого используется объект LngLatBounds.
const bounds = new mapboxgl.LngLatBounds();
route.geometry.coordinates.forEach(coord => {
bounds.extend(coord);
});
map.fitBounds(bounds, {
padding: 50
});
Параметр:
padding: 50
создаёт внутренний отступ от границ окна карты.
В реальных проектах координаты маршрута обычно не задаются вручную.
Маршрут строится через сервис маршрутизации Mapbox.
Пример URL-запроса:
const url =
`https://api.mapbox.com/directions/v5/mapbox/driving/
37.6176,55.7558;
37.6300,55.7625
?geometries=geojson
&access_token=${mapboxgl.accessToken}`;
Получение данных:
const response = await fetch(url);
const data = await response.json();
Извлечение маршрута:
const route = data.routes[0].geometry;
Полученная геометрия уже имеет формат GeoJSON и может быть сразу отображена на карте.
После получения ответа маршрут помещается в источник.
map.addSource('route', {
type: 'geojson',
data: {
type: 'Feature',
geometry: route
}
});
Далее создаётся стандартный линейный слой.
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
paint: {
'line-color': '#3887be',
'line-width': 6
}
});
Часто требуется перестраивать маршрут после изменения точек.
Вместо удаления слоя можно обновить данные источника.
map.getSource('route').setData({
type: 'Feature',
geometry: newRoute
});
Преимущества подхода:
Иногда требуется эффект постепенного прорисовывания пути.
Создаётся массив координат.
const fullRoute = route.geometry.coordinates;
Начальное состояние:
const animatedRoute = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: []
}
};
Постепенное добавление точек:
let index = 0;
function animateRoute() {
if (index >= fullRoute.length) {
return;
}
animatedRoute.geometry.coordinates.push(
fullRoute[index]
);
map.getSource('route').setData(animatedRoute);
index++;
requestAnimationFrame(animateRoute);
}
animateRoute();
Линия будет постепенно удлиняться вдоль маршрута.
Иногда необходимо показывать несколько вариантов пути.
Источник:
const routes = {
type: "FeatureCollection",
features: [
route1,
route2,
route3
]
};
Добавление:
map.addSource('routes', {
type: 'geojson',
data: routes
});
Слой:
map.addLayer({
id: 'routes',
type: 'line',
source: 'routes',
paint: {
'line-width': 5,
'line-color': [
'match',
['get', 'selected'],
true,
'#0078ff',
'#bbbbbb'
]
}
});
Выбранный маршрут выделяется цветом, остальные отображаются приглушённо.
Маршрут может содержать дополнительные свойства.
{
type: "Feature",
properties: {
active: true
},
geometry: {
...
}
}
Использование свойства в стиле:
'line-width': [
'case',
['get', 'active'],
8,
4
]
или
'line-opacity': [
'case',
['get', 'active'],
1,
0.4
]
Такой подход позволяет динамически менять внешний вид маршрутов без создания дополнительных слоёв.
После получения ответа от Directions API доступна информация о расстоянии.
const distance =
data.routes[0].distance;
Значение возвращается в метрах.
Перевод в километры:
const km = (distance / 1000).toFixed(1);
Создание всплывающей подсказки:
new mapboxgl.Popup()
.setLngLat(
route.coordinates[
Math.floor(
route.coordinates.length / 2
)
]
)
.setHTML(`
<b>Маршрут</b><br>
${km} км
`)
.addTo(map);
Маршрут может использоваться как траектория перемещения транспорта.
Создаётся маркер.
const marker = new mapboxgl.Marker()
.setLngLat(
route.geometry.coordinates[0]
)
.addTo(map);
Перемещение:
let step = 0;
function move() {
if (step >= route.geometry.coordinates.length) {
return;
}
marker.setLngLat(
route.geometry.coordinates[step]
);
step++;
requestAnimationFrame(move);
}
move();
Подобный механизм применяется для:
При работе с маршрутами, содержащими десятки тысяч точек, возможно снижение производительности.
Основные способы оптимизации:
Перед отправкой данных на клиент маршрут можно сократить алгоритмами:
Вместо непрерывного вызова:
setData(...)
целесообразно обновлять данные через интервалы времени.
setInterval(updateRoute, 500);
Предпочтительно хранить все маршруты в одном
GeoJSON Source.
FeatureCollection
работает эффективнее, чем большое количество отдельных источников.
Лучше использовать выражения Mapbox Style Expressions для изменения внешнего вида объектов внутри одного слоя, чем создавать множество однотипных слоёв.
В большинстве приложений процесс построения маршрута выглядит следующим образом:
Пользователь выбирает точки
↓
Запрос к Directions API
↓
Получение GeoJSON маршрута
↓
Создание или обновление Source
↓
Отрисовка Line Layer
↓
Масштабирование fitBounds()
↓
Отображение маркеров начала и конца
↓
Дополнительная анимация и интерактивность
Такая схема обеспечивает высокую производительность, удобство сопровождения кода и полную интеграцию возможностей маршрутизации с механизмами визуализации Mapbox GL JS.