Маршрутизація, матриці відстаней та оптимізація доставки по реальній дорожній мережі України.
Маршрутизація на основі реальної дорожньої мережі України
Routing API надає набір ендпоінтів для розв'язання задач маршрутизації на основі реальної дорожньої мережі України. В основі — даних OpenStreetMap для VRP-оптимізації.
https://geodataua.runasp.net/api/routing
Усі запити вимагають API-ключа
API-ключ передається у заголовку кожного запиту:
X-API-Key: <YOUR_API_KEY>
API-ключ видається при реєстрації клієнта в особистому кабінеті.
HTTP методи, формат координат та коди відповідей
| Метод | Опис |
|---|---|
| POST | Усі ендпоінти використовують POST + JSON body |
{
"latitude": 50.45,
"longitude": 30.52
}
| Поле | Тип | Опис |
|---|---|---|
latitude | double | Широта, –90 … +90 |
longitude | double | Довгота, –180 … +180 |
| Код | Опис |
|---|---|
| 200 | Успішний запит |
| 400 | Невалідні параметри / перевищено ліміт |
| 401 | Відсутній або невалідний API-ключ |
| 422 | Маршрут не може бути прокладений |
| 500 | Внутрішня помилка сервера |
Обчислення найкоротшого маршруту по дорожній мережі
/api/routing/route
Обчислює найкоротший маршрут між двома точками по дорожній мережі. Повертає відстань, тривалість та масив координат для відмалювання треку.
| Поле | Тип | Обов'язковий | Опис |
|---|---|---|---|
from | object (Point) | Так | Точка відправлення |
to | object (Point) | Так | Точка призначення |
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 }
}'
{
"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+ точок, скорочено для стислості).
| Поле | Тип | Опис |
|---|---|---|
distance | double | Відстань у метрах |
duration | double | Тривалість у секундах |
geometry | array<Point> | Координати треку маршруту |
{
"message": "Маршрут не може бути прокладений між вказаними точками",
"error": "NoRoute"
}
Інтерактивна карта з прикладом маршруту між точками запиту та геометрією відповіді (15.5 км, ~26 хв).
Оптимальний маршрут обходу набору точок
/api/routing/tsp
Знаходить оптимальний маршрут обходу набору точок з мінімальною загальною відстанню.
| Поле | Тип | Опис |
|---|---|---|
waypoints | array<Point> | Точки обходу (2–50) |
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 }
]
}'
{
"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 хв).
| Поле | Тип | Опис |
|---|---|---|
originalIndex | int | Індекс у вхідному масиві waypoints |
location | object (Point) | Географічна точка (latitude, longitude) |
{
"message": "Перевищено ліміт точок",
"error": "Максимум 50 точок. Передано: 75"
}
Інтерактивна карта з прикладом оптимального маршруту обходу 3 точок у Києві: Центр → Оболонь → Святошин → Центр (~36.6 км, ~61 хв).
Відстань та тривалість між усіма парами точок
/api/routing/matrix
Обчислює матрицю відстаней (метри) і тривалостей (секунди) між усіма парами переданих точок.
| Поле | Тип | Опис |
|---|---|---|
locations | array<Point> | Точки (2–100) |
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 }
]
}'
{
"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.
{
"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 | — |
Прив'язка GNSS-координат до реальної дорожньої мережі
/api/routing/snap
Прив'язує координати GNSS (GPS) до реальної дорожньої мережі. Усуває похибки позиціонування.
| Поле | Тип | Обов'язковий | Опис |
|---|---|---|---|
points | array<Point> | Так | GNSS-координати |
interpolate | bool | Ні | Інтерполяція між точками (за замовч. false) |
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 — повертає суцільну лінію по дорозі між точками.
{
"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 }
]
}
{
"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 }
]
}
{
"message": "Не вдалось прив'язати трек до дорожньої мережі",
"error": "NoMatch"
}
Карта показує вхідні GNSS-точки (запит) та результат прив'язки до дороги з інтерполяцією (114.4 м, ~10 с).
Vehicle Routing Problem з обмеженнями ваги, об'єму та часових вікон
/api/routing/vrp
Розв'язує задачу CVRPTW — оптимальний розподіл замовлень між транспортними засобами з урахуванням:
| Поле | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Унікальний ідентифікатор |
start | object (Point) | Так | Точка старту |
end | object (Point) | Ні | Точка фінішу |
capacityWeight | double | Так | Максимальна вага (кг) |
capacityVolume | double | Так | Максимальний об'єм (м³) |
maxOrders | int | Так | Максимальна кількість замовлень |
end = null — авто не повертається на базу (відкритий маршрут).
| Поле | Тип | Обов'язковий | Опис |
|---|---|---|---|
id | string | Так | Унікальний ідентифікатор |
location | object (Point) | Так | Точка доставки |
demandWeight | double | Так | Вага замовлення (кг) |
demandVolume | double | Так | Об'єм замовлення (м³) |
timeWindowStart | int? | Ні | Початок часового вікна (секунди від старту) |
timeWindowEnd | int? | Ні | Кінець часового вікна (секунди від старту) |
serviceTime | int | Так | Час розвантаження (секунди) |
| Поле | Тип | За замовчуванням | Опис |
|---|---|---|---|
timeLimit | int | 30 | Ліміт часу солвера (1–120 сек) |
strategy | string | minimize_total_distance | Стратегія оптимізації |
| Значення | Опис |
|---|---|
minimize_total_distance | Мінімізація загальної відстані |
minimize_total_duration | Мінімізація загального часу |
minimize_vehicles | Мінімізація кількості задіяних авто |
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"
}
}'
{
"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": []
}
Побудовані маршрути відображені на карті різними кольорами. Кожна зупинка позначена маркером з інформацією.
| Поле | Тип | Опис |
|---|---|---|
status | string | optimal / feasible / no_solution |
solveTime | double | Час роботи солвера (секунди) |
totalDistance | double | Сума відстаней усіх маршрутів (м) |
totalDuration | double | Сума тривалостей усіх маршрутів (с) |
vehiclesUsed | int | Кількість задіяних авто |
ordersAssigned | int | Кількість розподілених замовлень |
unassignedOrders | array<string> | ID нерозподілених замовлень |
routes | array<Route> | Побудовані маршрути |
| Поле | Тип | Опис |
|---|---|---|
vehicleId | string | ID транспортного засобу |
distance | double | Відстань маршруту (метри) |
duration | double | Тривалість маршруту (секунди) |
usedWeight | double | Використана вага (кг) |
usedVolume | double | Використаний об'єм (м³) |
orderCount | int | Кількість замовлень на маршруті |
stops | array<Stop> | Зупинки на маршруті |
| Поле | Тип | Опис |
|---|---|---|
type | string | start / delivery / end |
orderId | string | ID замовлення (для type = delivery) |
location | object (Point) | Координати зупинки |
arrivalTime | datetime | Час прибуття |
departureTime | datetime | Час від'їзду (для delivery) |
| Значення | Опис |
|---|---|
optimal | Знайдено оптимальне рішення |
feasible | Допустиме рішення (таймаут солвера вичерпано) |
no_solution | Рішення не знайдено |
{
"status": "feasible",
"unassignedOrders": ["order-102"],
"vehiclesUsed": 1,
"ordersAssigned": 2,
"routes": [...]
}
{
"message": "Невалідні параметри запиту",
"error": "Перевищено ліміт: максимум 20 транспортних засобів та 200 замовлень"
}
{
"message": "Неможливо побудувати маршрути",
"error": "NoSolution: жодне замовлення не може бути доставлене з врахуванням обмежень"
}
Короткий огляд усіх доступних ендпоінтів Routing API
| № | Метод | Ендпоінт | Опис | Ліміт точок |
|---|---|---|---|---|
| 1 | POST | /api/routing/route | Маршрут A→B | 2 |
| 2 | POST | /api/routing/tsp | Задача комівояжера (TSP) | 50 |
| 3 | POST | /api/routing/matrix | Матриця відстаней і часу | 100 |
| 4 | POST | /api/routing/snap | Snap to Road | ∞ |
| 5 | POST | /api/routing/vrp | VRP (CVRPTW) | 20 авто / 200 замовл. |
Швидкий старт для вашого додатку
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();
}