Построение маршрутов с пересадками

Построение маршрутов с пересадками представляет собой задачу поиска оптимального пути между несколькими точками с учетом порядка посещения, транспортных ограничений, времени в пути и дополнительных параметров маршрутизации. В экосистеме HERE Maps эта функциональность реализуется через сервис Routing API, который позволяет рассчитывать маршруты между произвольным количеством промежуточных точек.

Маршрут с пересадками может включать:

  • начальную точку;
  • одну или несколько промежуточных остановок (waypoints);
  • конечную точку назначения;
  • различные типы транспорта;
  • альтернативные варианты движения;
  • временные ограничения и предпочтения.

Типичная схема маршрута:

Точка A → Точка B → Точка C → Точка D

Каждый сегмент между двумя соседними точками называется участком маршрута (section).


Подключение Routing API

Для построения маршрутов необходимо подключить платформу HERE и создать экземпляр сервиса маршрутизации.

const platform = new H.service.Platform({
    apikey: 'YOUR_API_KEY'
});

const router = platform.getRoutingService(null, 8);

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


Базовое построение маршрута через несколько точек

Простейший маршрут с промежуточными остановками формируется через параметр via.

Пример:

router.calculateRoute({
    transportMode: 'car',
    origin: '50.4501,30.5234',
    via: [
        '49.8397,24.0297',
        '48.9226,24.7111'
    ],
    destination: '48.4647,35.0462',
    return: 'polyline,summary'
}, onSuccess, onError);

Здесь:

  • Киев — стартовая точка;
  • Львов — первая остановка;
  • Ивано-Франковск — вторая остановка;
  • Днепр — конечная точка.

Сервис автоматически рассчитает маршрут через все заданные координаты в указанном порядке.


Структура ответа маршрута

После успешного расчета API возвращает объект маршрута.

Пример обработки:

function onSuccess(result) {
    const route = result.routes[0];

    console.log(route);
}

Структура данных:

{
    routes: [
        {
            sections: [
                {...},
                {...},
                {...}
            ]
        }
    ]
}

Каждая секция соответствует отдельному участку пути между двумя соседними точками.

Например:

Секция Откуда Куда
1 A B
2 B C
3 C D

Получение общей информации о маршруте

Каждая секция содержит сводную статистику.

route.sections.forEach(section => {
    console.log(section.summary.length);
    console.log(section.summary.duration);
});

Основные показатели:

Параметр Описание
length Расстояние в метрах
duration Время движения
baseDuration Время без учета трафика
trafficDelay Задержка из-за пробок

Пример суммирования длины всего маршрута:

let totalDistance = 0;

route.sections.forEach(section => {
    totalDistance += section.summary.length;
});

console.log(totalDistance);

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

Маршрут возвращается в виде полилинии.

Получение геометрии:

const linestring =
    H.geo.LineString.fromFlexiblePolyline(
        route.sections[0].polyline
    );

Создание объекта карты:

const routeLine = new H.map.Polyline(
    linestring,
    {
        style: {
            lineWidth: 5
        }
    }
);

map.addObject(routeLine);

Для маршрутов с несколькими пересадками необходимо отображать каждую секцию.

route.sections.forEach(section => {

    const linestring =
        H.geo.LineString.fromFlexiblePolyline(
            section.polyline
        );

    const polyline = new H.map.Polyline(
        linestring,
        {
            style: {
                lineWidth: 6
            }
        }
    );

    map.addObject(polyline);
});

Отображение промежуточных точек

Для визуализации пересадок удобно размещать маркеры.

const marker = new H.map.Marker({
    lat: 49.8397,
    lng: 24.0297
});

map.addObject(marker);

Создание нескольких остановок:

const stops = [
    { lat: 49.8397, lng: 24.0297 },
    { lat: 48.9226, lng: 24.7111 }
];

stops.forEach(stop => {
    map.addObject(
        new H.map.Marker(stop)
    );
});

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

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

router.calculateRoute({
    transportMode: 'car',
    origin: start,
    destination: finish,
    alternatives: 3,
    return: 'polyline,summary'
}, onSuccess, onError);

Результат:

result.routes.forEach(route => {
    console.log(route);
});

Каждый вариант может отличаться:

  • длиной;
  • временем;
  • стоимостью проезда;
  • загруженностью дорог.

Оптимизация порядка посещения точек

В некоторых сценариях известен набор остановок, но порядок их посещения неизвестен.

Пример:

  • склад;
  • клиент №1;
  • клиент №2;
  • клиент №3.

Для решения подобных задач используется Waypoint Sequence API.

Сервис анализирует расположение точек и определяет оптимальный порядок посещения.

Пример структуры запроса:

{
    start: '50.4501,30.5234',
    destination: '50.4501,30.5234',
    intermediates: [
        '49.8397,24.0297',
        '48.4647,35.0462',
        '46.4825,30.7233'
    ]
}

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

Подобная функциональность активно применяется в:

  • логистике;
  • курьерских службах;
  • службах доставки;
  • торговых маршрутах.

Построение маршрутов общественного транспорта

HERE Routing API поддерживает общественный транспорт.

router.calculateRoute({
    transportMode: 'publicTransport',
    origin: start,
    destination: finish,
    return: 'polyline,actions,summary'
}, onSuccess, onError);

В результате могут присутствовать:

  • пешие участки;
  • автобусные маршруты;
  • метро;
  • трамваи;
  • пересадки между транспортом.

Каждая пересадка отображается как отдельная секция.

Пример:

route.sections.forEach(section => {
    console.log(section.transport.mode);
});

Возможный вывод:

pedestrian
bus
pedestrian
metro
pedestrian

Получение информации о пересадках

Для общественного транспорта каждая секция содержит подробные сведения.

console.log(section.departure.place.name);
console.log(section.arrival.place.name);

Также доступны:

console.log(section.departure.time);
console.log(section.arrival.time);

Дополнительная информация:

console.log(section.transport.name);
console.log(section.transport.category);
console.log(section.transport.headsign);

Например:

Автобус №24
Направление: Центральный вокзал

Работа с инструкциями движения

Маршрут может содержать пошаговые указания.

Для этого необходимо запросить действия:

return: 'actions,instructions,polyline'

Получение инструкций:

route.sections.forEach(section => {

    section.actions.forEach(action => {

        console.log(action.instruction);
    });

});

Пример результата:

Поверните направо
Через 500 метров поверните налево
Продолжайте движение прямо

Исключение определённых дорог

При маршрутизации можно задавать ограничения.

Избегать платных дорог:

router.calculateRoute({
    transportMode: 'car',
    origin: start,
    destination: finish,
    avoid: {
        features: ['tollRoad']
    }
}, onSuccess, onError);

Избегать паромов:

avoid: {
    features: ['ferry']
}

Избегать тоннелей:

avoid: {
    features: ['tunnel']
}

Такие параметры особенно полезны при длинных маршрутах с большим количеством остановок.


Учет дорожной ситуации

HERE использует данные о дорожном движении в реальном времени.

router.calculateRoute({
    transportMode: 'car',
    routingMode: 'fast',
    traffic: true,
    origin: start,
    destination: finish
}, onSuccess, onError);

В ответе можно анализировать влияние пробок:

const delay =
    section.summary.trafficDelay;

Если задержка значительна, маршрут может быть автоматически перестроен.


Перестроение маршрута при изменении точек

Часто возникает необходимость динамически добавлять остановки.

Исходный набор:

const via = [
    point1,
    point2
];

Добавление новой пересадки:

via.push(point3);

Повторный расчет:

router.calculateRoute({
    transportMode: 'car',
    origin,
    via,
    destination,
    return: 'polyline'
}, onSuccess, onError);

Такой подход широко применяется в системах доставки и диспетчеризации.


Построение кольцевых маршрутов

Иногда маршрут должен завершаться в исходной точке.

Пример:

router.calculateRoute({
    transportMode: 'car',
    origin: warehouse,
    via: [
        customer1,
        customer2,
        customer3
    ],
    destination: warehouse
}, onSuccess, onError);

Подобная схема характерна для:

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

Работа с большим количеством остановок

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

  1. Выполнять предварительную оптимизацию последовательности.
  2. Минимизировать количество повторных расчетов.
  3. Кэшировать уже построенные маршруты.
  4. Использовать асинхронную обработку ответов.
  5. Группировать близко расположенные точки.

Пример хранения результатов:

const routeCache = new Map();

routeCache.set(
    routeId,
    route
);

Повторный запрос:

if (routeCache.has(routeId)) {
    return routeCache.get(routeId);
}

Обработка ошибок маршрутизации

Типовая обработка ошибок:

function onError(error) {
    console.error(error);
}

Расширенный вариант:

function onError(error) {

    if (error.message) {
        console.error(
            'Ошибка маршрута:',
            error.message
        );
    }

}

Наиболее распространённые причины ошибок:

Причина Описание
Неверные координаты Ошибка геопозиции
Недоступный транспорт Маршрут не существует
Отсутствие соединения Ошибка сети
Некорректный API Key Ошибка авторизации
Недопустимые параметры Ошибка запроса

Практический пример полного маршрута с пересадками

router.calculateRoute({
    transportMode: 'car',
    origin: '50.4501,30.5234',
    via: [
        '49.8397,24.0297',
        '48.9226,24.7111',
        '46.4825,30.7233'
    ],
    destination: '48.4647,35.0462',
    routingMode: 'fast',
    traffic: true,
    return: 'polyline,summary,actions'
},
function(result) {

    const route = result.routes[0];

    route.sections.forEach(section => {

        console.log(
            section.summary.length,
            section.summary.duration
        );

        const lineString =
            H.geo.LineString.fromFlexiblePolyline(
                section.polyline
            );

        map.addObject(
            new H.map.Polyline(lineString)
        );
    });

},
function(error) {
    console.error(error);
});

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