Отображение маршрута на карте

Работа с маршрутами в MapLibre GL JS строится вокруг концепции источников данных (sources) и слоёв (layers), где геометрия пути представляется в формате GeoJSON, чаще всего как LineString или MultiLineString. Отображение маршрута включает несколько этапов: получение координат маршрута, преобразование данных в GeoJSON, добавление источника на карту и визуализация слоя линии с необходимыми стилями.


Маршрут в веб-картографии почти всегда приходит из внешнего routing-сервиса. Наиболее распространённый вариант — использование OSRM, GraphHopper или аналогичных сервисов, возвращающих координаты пути.

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

{
  "routes": [
    {
      "geometry": {
        "coordinates": [
          [71.4304, 51.1282],
          [71.4321, 51.1295],
          [71.4350, 51.1310]
        ],
        "type": "LineString"
      }
    }
  ]
}

Данные уже готовы к использованию в MapLibre, поскольку GeoJSON LineString является нативным форматом для отображения линий.


Подготовка GeoJSON-структуры

Перед добавлением на карту маршрут преобразуется в полноценный GeoJSON Feature:

function buildRouteFeature(coordinates) {
  return {
    type: "Feature",
    geometry: {
      type: "LineString",
      coordinates
    },
    properties: {}
  };
}

Такая обёртка позволяет в дальнейшем расширять маршрут дополнительными атрибутами: длиной, временем, типом дороги или состоянием.


Добавление источника данных в карту

MapLibre использует систему источников, где маршрут регистрируется как geojson source:

map.on("load", () => {
  map.addSource("route", {
    type: "geojson",
    data: {
      type: "Feature",
      geometry: {
        type: "LineString",
        coordinates: []
      }
    }
  });
});

Пустой массив координат позволяет инициализировать структуру заранее, а затем динамически обновлять маршрут после получения данных от API.


Отображение линии маршрута

После добавления источника создаётся слой линии. Именно он отвечает за визуализацию маршрута:

map.addLayer({
  id: "route-line",
  type: "line",
  source: "route",
  layout: {
    "line-join": "round",
    "line-cap": "round"
  },
  paint: {
    "line-color": "#2E86FF",
    "line-width": 5,
    "line-opacity": 0.9
  }
});

Ключевые параметры:

  • line-join: round — сглаживает углы поворотов
  • line-cap: round — делает окончания линии мягкими
  • line-color — цвет маршрута
  • line-width — толщина линии

Обновление маршрута динамически

После получения данных от сервиса маршрутизации слой обновляется через setData:

fetch("https://router.project-osrm.org/route/v1/driving/71.4304,51.1282;71.4350,51.1310?overview=full&geometries=geojson")
  .then(res => res.json())
  .then(data => {
    const route = data.routes[0].geometry;

    map.getSource("route").setData({
      type: "Feature",
      geometry: route
    });
  });

MapLibre моментально перерисовывает слой без необходимости пересоздания источника или слоя.


Масштабирование карты под маршрут

После построения маршрута часто требуется автоматически подогнать видимую область:

function fitRouteBounds(coordinates) {
  const bounds = coordinates.reduce((b, coord) => {
    return b.extend(coord);
  }, new maplibregl.LngLatBounds(coordinates[0], coordinates[0]));

  map.fitBounds(bounds, {
    padding: 40
  });
}

Такой подход обеспечивает полное отображение маршрута на экране независимо от его длины и географии.


Разделение маршрута на сегменты

Для сложных сценариев маршрут может быть разделён на части: активный участок, пройденный и оставшийся.

map.addLayer({
  id: "route-completed",
  type: "line",
  source: "route",
  paint: {
    "line-color": "#00C853",
    "line-width": 5
  },
  filter: ["<=", ["get", "progress"], 0.5]
});

Для этого в properties каждого сегмента добавляются дополнительные метки прогресса или состояния.


Анимация движения по маршруту

Для визуализации движения объекта по линии используется интерполяция координат:

let step = 0;

function animate() {
  const coordinates = routeCoordinates.slice(0, step);

  map.getSource("route").setData({
    type: "Feature",
    geometry: {
      type: "LineString",
      coordinates
    }
  });

  step++;

  if (step < routeCoordinates.length) {
    requestAnimationFrame(animate);
  }
}

Такой метод создаёт эффект «прорисовки» пути в реальном времени.


Пунктирный маршрут и стилизация

Для отображения альтернативных маршрутов или режима навигации используется пунктирная линия:

map.addLayer({
  id: "route-dashed",
  type: "line",
  source: "route",
  layout: {
    "line-cap": "round"
  },
  paint: {
    "line-color": "#FF6D00",
    "line-width": 4,
    "line-dasharray": [2, 2]
  }
});

Параметр line-dasharray управляет длиной штрихов и пробелов, создавая визуально отличимые маршруты.


Использование нескольких маршрутов одновременно

В навигационных системах часто требуется отображать альтернативные пути:

map.addSource("routes", {
  type: "geojson",
  data: {
    type: "FeatureCollection",
    features: []
  }
});

Каждый маршрут добавляется как отдельный Feature с уникальными стилями:

map.addLayer({
  id: "route-primary",
  type: "line",
  source: "routes",
  paint: {
    "line-color": "#1E88E5",
    "line-width": 5
  },
  filter: ["==", ["get", "type"], "primary"]
});
map.addLayer({
  id: "route-secondary",
  type: "line",
  source: "routes",
  paint: {
    "line-color": "#90CAF9",
    "line-width": 4
  },
  filter: ["==", ["get", "type"], "secondary"]
});

Работа с интерактивностью маршрута

MapLibre позволяет привязывать события к маршруту, включая hover и click:

map.on("click", "route-line", (e) => {
  const coordinates = e.lngLat;

  new maplibregl.Popup()
    .setLngLat(coordinates)
    .setHTML("Точка маршрута")
    .addTo(map);
});

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


Оптимизация отображения длинных маршрутов

При работе с протяжёнными маршрутами важно учитывать производительность:

  • использование line-simplify на стороне сервера
  • уменьшение количества точек через Douglas-Peucker
  • ограничение maxzoom для слоя маршрута
  • отключение избыточной прозрачности

Пример ограничения зума:

map.addLayer({
  id: "route-line",
  type: "line",
  source: "route",
  maxzoom: 18,
  paint: {
    "line-color": "#3F51B5",
    "line-width": 4
  }
});

Комбинирование маршрута с маркерами

Маршрут редко отображается сам по себе — обычно он сопровождается точками начала и конца:

new maplibregl.Marker({ color: "green" })
  .setLngLat(start)
  .addTo(map);

new maplibregl.Marker({ color: "red" })
  .setLngLat(end)
  .addTo(map);

Эти элементы усиливают читаемость маршрута и позволяют быстро интерпретировать направление движения.


Обновление маршрута в реальном времени

В системах трекинга маршрут может изменяться динамически. В таком случае источник данных регулярно пересчитывается:

setInterval(async () => {
  const data = await getUpdatedRoute();
  map.getSource("route").setData(data);
}, 5000);

Это позволяет синхронизировать карту с движением транспорта или пользователя без перезагрузки слоя.


Стилизация маршрута в зависимости от условий

Маршрут может менять внешний вид в зависимости от свойств:

paint: {
  "line-color": [
    "case",
    ["==", ["get", "traffic"], "heavy"],
    "#D32F2F",
    "#1976D2"
  ],
  "line-width": 5
}

Такой подход позволяет визуализировать дорожную обстановку, тип покрытия или приоритет маршрута без создания дополнительных слоёв.