🗺️ Документація Routing API

Маршрутизація, матриці відстаней та оптимізація доставки по реальній дорожній мережі України.

Зміст

📋Огляд 🔑Автентифікація ⚙️Принципи 🧭Route 🔄TSP 📊Matrix 📍Snap 🚚VRP 📋Ендпоінти 💻Приклад

Огляд Routing API

Маршрутизація на основі реальної дорожньої мережі України

Routing API надає набір ендпоінтів для розв'язання задач маршрутизації на основі реальної дорожньої мережі України. В основі — даних OpenStreetMap для VRP-оптимізації.

Базова URL: https://geodataua.runasp.net/api/routing
🧭 Route → 🔄 TSP → 📊 Matrix → 📍 Snap → 🚚 VRP

🔑 Автентифікація

Усі запити вимагають API-ключа

API-ключ передається у заголовку кожного запиту:

X-API-Key: <YOUR_API_KEY>

API-ключ видається при реєстрації клієнта в особистому кабінеті.

⚙️ Загальні принципи

HTTP методи, формат координат та коди відповідей

HTTP методи

МетодОпис
POSTУсі ендпоінти використовують POST + JSON body

Географічна точка (Point)

{
  "latitude": 50.45,
  "longitude": 30.52
}
ПолеТипОпис
latitudedoubleШирота, –90 … +90
longitudedoubleДовгота, –180 … +180

Коди відповідей

КодОпис
200Успішний запит
400Невалідні параметри / перевищено ліміт
401Відсутній або невалідний API-ключ
422Маршрут не може бути прокладений
500Внутрішня помилка сервера

🧭 Маршрут між двома точками (Route)

Обчислення найкоротшого маршруту по дорожній мережі

POST /api/routing/route

Обчислює найкоротший маршрут між двома точками по дорожній мережі. Повертає відстань, тривалість та масив координат для відмалювання треку.

Тіло запиту

ПолеТипОбов'язковийОпис
fromobject (Point)ТакТочка відправлення
toobject (Point)ТакТочка призначення

Приклад запиту (curl)

curl -X POST 'https://geodataua.runasp.net/api/routing/route' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -d '{
    "from": { "latitude": 50.4413, "longitude": 30.5220 },
    "to": { "latitude": 50.4967, "longitude": 30.3588 }
  }'

Приклад відповіді 200

{
  "distance": 15496.5,
  "duration": 1568.1,
  "geometry": [
    { "latitude": 50.441334, "longitude": 30.522354 },
    { "latitude": 50.441828, "longitude": 30.522236 },
    { "latitude": 50.441959, "longitude": 30.522334 },
    ...
    { "latitude": 50.496772, "longitude": 30.359262 },
    { "latitude": 50.49673, "longitude": 30.359269 }
  ]
}
💡 Масив geometry містить повний набір координат для відмалювання треку (380+ точок, скорочено для стислості).
ПолеТипОпис
distancedoubleВідстань у метрах
durationdoubleТривалість у секундах
geometryarray<Point>Координати треку маршруту

Відповідь 422

{
  "message": "Маршрут не може бути прокладений між вказаними точками",
  "error": "NoRoute"
}

🗺️ Візуалізація маршруту на карті

Інтерактивна карта з прикладом маршруту між точками запиту та геометрією відповіді (15.5 км, ~26 хв).

Точка відправлення (from) Точка призначення (to) Маршрут (geometry)

🔄 Задача комівояжера (TSP)

Оптимальний маршрут обходу набору точок

POST /api/routing/tsp

Знаходить оптимальний маршрут обходу набору точок з мінімальною загальною відстанню.

⚠️ Обмеження: максимум 50 точок.

Тіло запиту

ПолеТипОпис
waypointsarray<Point>Точки обходу (2–50)

Приклад запиту (curl)

curl -X POST 'https://geodataua.runasp.net/api/routing/tsp' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -d '{
    "waypoints": [
      { "latitude": 50.4413, "longitude": 30.5220 },
      { "latitude": 50.4967, "longitude": 30.3588 },
      { "latitude": 50.5227, "longitude": 30.4515 }
    ]
  }'

Приклад відповіді 200

{
  "totalDistance": 36593.3,
  "totalDuration": 3664.5,
  "orderedWaypoints": [
    { "originalIndex": 0, "location": { "latitude": 50.441334, "longitude": 30.522354 } },
    { "originalIndex": 1, "location": { "latitude": 50.522383, "longitude": 30.45137 } },
    { "originalIndex": 2, "location": { "latitude": 50.49673, "longitude": 30.359269 } }
  ],
  "geometry": [
    { "latitude": 50.441334, "longitude": 30.522354 },
    { "latitude": 50.442604, "longitude": 30.522141 },
    { "latitude": 50.448118, "longitude": 30.522344 },
    ...
    { "latitude": 50.522383, "longitude": 30.45137 },
    ...
    { "latitude": 50.49673, "longitude": 30.359269 },
    ...
    { "latitude": 50.444797, "longitude": 30.521044 },
    { "latitude": 50.441334, "longitude": 30.522354 }
  ]
}
💡 Масив geometry містить повний набір координат маршруту обходу (51 точка, скорочено для стислості). Маршрут: Центр → Оболонь → Святошин → Центр (~36.6 км, ~61 хв).

Структура OrderedPoint

ПолеТипОпис
originalIndexintІндекс у вхідному масиві waypoints
locationobject (Point)Географічна точка (latitude, longitude)

Відповідь 400

{
  "message": "Перевищено ліміт точок",
  "error": "Максимум 50 точок. Передано: 75"
}

🗺️ Візуалізація маршруту TSP

Інтерактивна карта з прикладом оптимального маршруту обходу 3 точок у Києві: Центр → Оболонь → Святошин → Центр (~36.6 км, ~61 хв).

Старт / Фініш (Центр) Waypoints (Оболонь, Святошин) Оптимальний маршрут

📊 Матриця відстаней і часу (Matrix)

Відстань та тривалість між усіма парами точок

POST /api/routing/matrix

Обчислює матрицю відстаней (метри) і тривалостей (секунди) між усіма парами переданих точок.

⚠️ Обмеження: максимум 100 точок (матриця 100×100).

Тіло запиту

ПолеТипОпис
locationsarray<Point>Точки (2–100)

Приклад запиту (curl)

curl -X POST 'https://geodataua.runasp.net/api/routing/matrix' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -d '{
    "locations": [
      { "latitude": 50.5227, "longitude": 30.4515 },
      { "latitude": 50.4413, "longitude": 30.5220 },
      { "latitude": 50.4967, "longitude": 30.3588 }
    ]
  }'

Приклад відповіді 200

{
  "distances": [
    [0, 12120.7, 9576.7],
    [12362.5, 0, 15496.5],
    [9218, 14654.1, 0]
  ],
  "durations": [
    [0, 1213.9, 981.8],
    [1200.2, 0, 1568.1],
    [913.3, 1482.5, 0]
  ]
}
💡 distances[i][j] — відстань від точки i до точки j. Головна діагональ завжди дорівнює 0.

Відповідь 400

{
  "message": "Перевищено ліміт точок",
  "error": "Максимум 100 точок. Передано: 130"
}

🗺️ Інтерактивна візуалізація матриці

Клікніть на комірку матриці, щоб побачити маршрут між відповідними точками на карті. Приклад побудовано для 3 точок у Києві.

Від \ До A Оболонь B Центр C Святошин
A — 12 120.7 9 576.7
B 12 362.5 — 15 496.5
C 9 218.0 14 654.1 —
🗺️ Показано всі маршрути між точками 👆 Клікніть на комірку для деталей окремого маршруту
A — Оболонь (50.5227, 30.4515) B — Центр (50.4413, 30.5220) C — Святошин (50.4967, 30.3588) Обраний маршрут

📍 Прив'язка треку до доріг (Snap to Road)

Прив'язка GNSS-координат до реальної дорожньої мережі

POST /api/routing/snap

Прив'язує координати GNSS (GPS) до реальної дорожньої мережі. Усуває похибки позиціонування.

Тіло запиту

ПолеТипОбов'язковийОпис
pointsarray<Point>ТакGNSS-координати
interpolateboolНіІнтерполяція між точками (за замовч. false)

Приклад запиту (curl)

curl -X POST 'https://geodataua.runasp.net/api/routing/snap' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -d '{
    "points": [
      { "latitude": 50.451834, "longitude": 30.526153 },
      { "latitude": 50.451588, "longitude": 30.525841 },
      { "latitude": 50.451004, "longitude": 30.525191 }
    ],
    "interpolate": true
  }'
💡 interpolate: false — повертає тільки прив'язані точки.
interpolate: true — повертає суцільну лінію по дорозі між точками.

Відповідь 200 (interpolate: false)

{
  "interpolated": false,
  "distance": 1245.8,
  "duration": 142.0,
  "matchedPoints": [
    { "latitude": 50.4504, "longitude": 30.5238 },
    { "latitude": 50.4511, "longitude": 30.5248 },
    { "latitude": 50.4519, "longitude": 30.5263 }
  ]
}

Відповідь 200 (interpolate: true)

{
  "interpolated": true,
  "distance": 114.4,
  "duration": 10.4,
  "matchedPoints": [
    { "latitude": 50.451747, "longitude": 30.526309 },
    { "latitude": 50.451742, "longitude": 30.526302 },
    { "latitude": 50.451594, "longitude": 30.526101 },
    { "latitude": 50.451509, "longitude": 30.525985 },
    { "latitude": 50.451307, "longitude": 30.525711 },
    { "latitude": 50.451053, "longitude": 30.525366 },
    { "latitude": 50.45097, "longitude": 30.525253 }
  ]
}

Відповідь 422

{
  "message": "Не вдалось прив'язати трек до дорожньої мережі",
  "error": "NoMatch"
}

🗺️ Візуалізація Snap to Road

Карта показує вхідні GNSS-точки (запит) та результат прив'язки до дороги з інтерполяцією (114.4 м, ~10 с).

Вхідні GNSS-точки (points) Прив'язані точки (matchedPoints) Вхідний трек Прив'язаний маршрут

🚚 Оптимізація маршрутів доставки (VRP)

Vehicle Routing Problem з обмеженнями ваги, об'єму та часових вікон

POST /api/routing/vrp

Розв'язує задачу CVRPTW — оптимальний розподіл замовлень між транспортними засобами з урахуванням:

⚠️ Ліміти: до 20 транспортних засобів та 200 замовлень. Таймаут солвера: 1–120 секунд.

Структура Vehicle (транспортний засіб)

ПолеТипОбов'язковийОпис
idstringТакУнікальний ідентифікатор
startobject (Point)ТакТочка старту
endobject (Point)НіТочка фінішу
capacityWeightdoubleТакМаксимальна вага (кг)
capacityVolumedoubleТакМаксимальний об'єм (м³)
maxOrdersintТакМаксимальна кількість замовлень
💡 Якщо end = null — авто не повертається на базу (відкритий маршрут).

Структура Order (замовлення)

ПолеТипОбов'язковийОпис
idstringТакУнікальний ідентифікатор
locationobject (Point)ТакТочка доставки
demandWeightdoubleТакВага замовлення (кг)
demandVolumedoubleТакОб'єм замовлення (м³)
timeWindowStartint?НіПочаток часового вікна (секунди від старту)
timeWindowEndint?НіКінець часового вікна (секунди від старту)
serviceTimeintТакЧас розвантаження (секунди)

Опції солвера (Options)

ПолеТипЗа замовчуваннямОпис
timeLimitint30Ліміт часу солвера (1–120 сек)
strategystringminimize_total_distanceСтратегія оптимізації

Стратегії оптимізації

ЗначенняОпис
minimize_total_distanceМінімізація загальної відстані
minimize_total_durationМінімізація загального часу
minimize_vehiclesМінімізація кількості задіяних авто

Повний приклад запиту (curl)

curl -X POST 'https://geodataua.runasp.net/api/routing/vrp' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -d '{
    "vehicles": [
      {
        "id": "car-1",
        "start": { "latitude": 50.4295, "longitude": 30.4821 },
        "end": { "latitude": 50.4295, "longitude": 30.4821 },
        "capacityWeight": 5000,
        "capacityVolume": 20,
        "maxOrders": 8
      },
      {
        "id": "car-2",
        "start": { "latitude": 50.4295, "longitude": 30.4821 },
        "end": { "latitude": 50.4295, "longitude": 30.4821 },
        "capacityWeight": 3000,
        "capacityVolume": 12,
        "maxOrders": 10
      }
    ],
    "orders": [
      { "id": "order-1",  "location": { "latitude": 50.4413, "longitude": 30.5220 }, "demandWeight": 120, "demandVolume": 0.5, "timeWindowStart": 0, "timeWindowEnd": 10800, "serviceTime": 300 },
      { "id": "order-2",  "location": { "latitude": 50.4119, "longitude": 30.4131 }, "demandWeight": 200, "demandVolume": 1.2, "timeWindowStart": 0, "timeWindowEnd": 14400, "serviceTime": 300 },
      { "id": "order-3",  "location": { "latitude": 50.4589, "longitude": 30.3989 }, "demandWeight": 150, "demandVolume": 0.8, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 },
      { "id": "order-4",  "location": { "latitude": 50.4356, "longitude": 30.3809 }, "demandWeight": 100, "demandVolume": 0.4, "timeWindowStart": 0, "timeWindowEnd": 14400, "serviceTime": 300 },
      { "id": "order-5",  "location": { "latitude": 50.3844, "longitude": 30.4583 }, "demandWeight": 180, "demandVolume": 1.0, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 },
      { "id": "order-6",  "location": { "latitude": 50.4248, "longitude": 30.3830 }, "demandWeight": 90,  "demandVolume": 0.3, "timeWindowStart": 0, "timeWindowEnd": 10800, "serviceTime": 300 },
      { "id": "order-7",  "location": { "latitude": 50.4707, "longitude": 30.4617 }, "demandWeight": 130, "demandVolume": 0.6, "timeWindowStart": 0, "timeWindowEnd": 14400, "serviceTime": 300 },
      { "id": "order-8",  "location": { "latitude": 50.4388, "longitude": 30.5233 }, "demandWeight": 110, "demandVolume": 0.5, "timeWindowStart": 0, "timeWindowEnd": 10800, "serviceTime": 300 },
      { "id": "order-9",  "location": { "latitude": 50.5227, "longitude": 30.4515 }, "demandWeight": 160, "demandVolume": 0.9, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 },
      { "id": "order-10", "location": { "latitude": 50.4562, "longitude": 30.5940 }, "demandWeight": 140, "demandVolume": 0.7, "timeWindowStart": 0, "timeWindowEnd": 14400, "serviceTime": 300 },
      { "id": "order-11", "location": { "latitude": 50.4143, "longitude": 30.6519 }, "demandWeight": 170, "demandVolume": 1.1, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 },
      { "id": "order-12", "location": { "latitude": 50.4294, "longitude": 30.6197 }, "demandWeight": 80,  "demandVolume": 0.3, "timeWindowStart": 0, "timeWindowEnd": 10800, "serviceTime": 300 },
      { "id": "order-13", "location": { "latitude": 50.4049, "longitude": 30.6115 }, "demandWeight": 190, "demandVolume": 1.3, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 },
      { "id": "order-14", "location": { "latitude": 50.4196, "longitude": 30.4525 }, "demandWeight": 105, "demandVolume": 0.4, "timeWindowStart": 0, "timeWindowEnd": 14400, "serviceTime": 300 },
      { "id": "order-15", "location": { "latitude": 50.4967, "longitude": 30.3588 }, "demandWeight": 135, "demandVolume": 0.6, "timeWindowStart": 0, "timeWindowEnd": 18000, "serviceTime": 300 }
    ],
    "options": {
      "timeLimit": 30,
      "strategy": "minimize_total_distance"
    }
  }'

Приклад відповіді 200

{
  "status": "success",
  "routes": [
    {
      "vehicleId": "car-1",
      "totalDistance": 46352.1,
      "totalDuration": 7132.4,
      "stops": [
        { "orderId": "",         "type": "start",    "location": { "latitude": 50.4295, "longitude": 30.4821 }, "arrivalTime": 0,    "departureTime": 0 },
        { "orderId": "order-8",  "type": "delivery", "location": { "latitude": 50.4388, "longitude": 30.5233 }, "arrivalTime": 576,  "departureTime": 876 },
        { "orderId": "order-1",  "type": "delivery", "location": { "latitude": 50.4413, "longitude": 30.5220 }, "arrivalTime": 1099, "departureTime": 1399 },
        { "orderId": "order-10", "type": "delivery", "location": { "latitude": 50.4562, "longitude": 30.5940 }, "arrivalTime": 2327, "departureTime": 2627 },
        { "orderId": "order-12", "type": "delivery", "location": { "latitude": 50.4294, "longitude": 30.6197 }, "arrivalTime": 3159, "departureTime": 3459 },
        { "orderId": "order-11", "type": "delivery", "location": { "latitude": 50.4143, "longitude": 30.6519 }, "arrivalTime": 3961, "departureTime": 4261 },
        { "orderId": "order-13", "type": "delivery", "location": { "latitude": 50.4049, "longitude": 30.6115 }, "arrivalTime": 4764, "departureTime": 5064 },
        { "orderId": "order-14", "type": "delivery", "location": { "latitude": 50.4196, "longitude": 30.4525 }, "arrivalTime": 6413, "departureTime": 6713 },
        { "orderId": "",         "type": "end",      "location": { "latitude": 50.4295, "longitude": 30.4821 }, "arrivalTime": 7128, "departureTime": 7128 }
      ]
    },
    {
      "vehicleId": "car-2",
      "totalDistance": 57505.9,
      "totalDuration": 8279.7,
      "stops": [
        { "orderId": "",         "type": "start",    "location": { "latitude": 50.4295, "longitude": 30.4821 }, "arrivalTime": 0,    "departureTime": 0 },
        { "orderId": "order-5",  "type": "delivery", "location": { "latitude": 50.3844, "longitude": 30.4583 }, "arrivalTime": 763,  "departureTime": 1063 },
        { "orderId": "order-2",  "type": "delivery", "location": { "latitude": 50.4119, "longitude": 30.4131 }, "arrivalTime": 1697, "departureTime": 1997 },
        { "orderId": "order-6",  "type": "delivery", "location": { "latitude": 50.4248, "longitude": 30.3830 }, "arrivalTime": 2501, "departureTime": 2801 },
        { "orderId": "order-4",  "type": "delivery", "location": { "latitude": 50.4356, "longitude": 30.3809 }, "arrivalTime": 3073, "departureTime": 3373 },
        { "orderId": "order-3",  "type": "delivery", "location": { "latitude": 50.4589, "longitude": 30.3989 }, "arrivalTime": 3906, "departureTime": 4206 },
        { "orderId": "order-15", "type": "delivery", "location": { "latitude": 50.4967, "longitude": 30.3588 }, "arrivalTime": 4983, "departureTime": 5283 },
        { "orderId": "order-9",  "type": "delivery", "location": { "latitude": 50.5227, "longitude": 30.4515 }, "arrivalTime": 6196, "departureTime": 6496 },
        { "orderId": "order-7",  "type": "delivery", "location": { "latitude": 50.4707, "longitude": 30.4617 }, "arrivalTime": 7307, "departureTime": 7607 },
        { "orderId": "",         "type": "end",      "location": { "latitude": 50.4295, "longitude": 30.4821 }, "arrivalTime": 8276, "departureTime": 8276 }
      ]
    }
  ],
  "unassignedOrders": []
}

🗺️ Візуалізація маршрутів

Побудовані маршрути відображені на карті різними кольорами. Кожна зупинка позначена маркером з інформацією.

car-1 (7 замовлень) car-2 (8 замовлень) Старт Фініш Доставка

Структури відповіді

Верхній рівень

ПолеТипОпис
statusstringoptimal / feasible / no_solution
solveTimedoubleЧас роботи солвера (секунди)
totalDistancedoubleСума відстаней усіх маршрутів (м)
totalDurationdoubleСума тривалостей усіх маршрутів (с)
vehiclesUsedintКількість задіяних авто
ordersAssignedintКількість розподілених замовлень
unassignedOrdersarray<string>ID нерозподілених замовлень
routesarray<Route>Побудовані маршрути

Структура Route

ПолеТипОпис
vehicleIdstringID транспортного засобу
distancedoubleВідстань маршруту (метри)
durationdoubleТривалість маршруту (секунди)
usedWeightdoubleВикористана вага (кг)
usedVolumedoubleВикористаний об'єм (м³)
orderCountintКількість замовлень на маршруті
stopsarray<Stop>Зупинки на маршруті

Структура Stop

ПолеТипОпис
typestringstart / delivery / end
orderIdstringID замовлення (для type = delivery)
locationobject (Point)Координати зупинки
arrivalTimedatetimeЧас прибуття
departureTimedatetimeЧас від'їзду (для delivery)

Значення status

ЗначенняОпис
optimalЗнайдено оптимальне рішення
feasibleДопустиме рішення (таймаут солвера вичерпано)
no_solutionРішення не знайдено

Помилки

Відповідь 200 з нерозподіленими замовленнями

{
  "status": "feasible",
  "unassignedOrders": ["order-102"],
  "vehiclesUsed": 1,
  "ordersAssigned": 2,
  "routes": [...]
}

Відповідь 400

{
  "message": "Невалідні параметри запиту",
  "error": "Перевищено ліміт: максимум 20 транспортних засобів та 200 замовлень"
}

Відповідь 422

{
  "message": "Неможливо побудувати маршрути",
  "error": "NoSolution: жодне замовлення не може бути доставлене з врахуванням обмежень"
}

📋 Зведена таблиця ендпоінтів

Короткий огляд усіх доступних ендпоінтів Routing API

№МетодЕндпоінтОписЛіміт точок
1POST/api/routing/routeМаршрут A→B2
2POST/api/routing/tspЗадача комівояжера (TSP)50
3POST/api/routing/matrixМатриця відстаней і часу100
4POST/api/routing/snapSnap to Road∞
5POST/api/routing/vrpVRP (CVRPTW)20 авто / 200 замовл.

💻 Приклад інтеграції (JavaScript)

Швидкий старт для вашого додатку

const API_BASE = 'https://geodataua.runasp.net/api/routing';
const API_KEY = 'your-api-key';

const headers = {
  'X-API-Key': API_KEY,
  'Content-Type': 'application/json'
};

// Маршрут між двома точками
async function getRoute(from, to) {
  const res = await fetch(`${API_BASE}/route`, {
    method: 'POST', headers,
    body: JSON.stringify({ from, to })
  });
  return res.json();
}

// Матриця відстаней
async function getMatrix(locations) {
  const res = await fetch(`${API_BASE}/matrix`, {
    method: 'POST', headers,
    body: JSON.stringify({ locations })
  });
  return res.json();
}

// VRP-оптимізація
async function solveVrp(vehicles, orders, options) {
  const res = await fetch(`${API_BASE}/vrp`, {
    method: 'POST', headers,
    body: JSON.stringify({ vehicles, orders, options })
  });
  return res.json();
}