Comment appeler le calcul d'itinéraires
Chaque fonction de Valhalla est exposée sous /v2/routing/{fonction}. La requête est un objet JSON, envoyé de préférence en POST :
| Fonction | Usage |
|---|---|
route | Itinéraire entre deux points ou plus, avec instructions de navigation |
optimized_route | Tournée : ordre de passage optimal entre plusieurs points |
sources_to_targets | Matrice de temps et de distances (1×N, N×N, N×M) |
isochrone | Zones accessibles en un temps ou une distance donnés |
trace_route, trace_attributes | Recalage d'une trace GPS sur le réseau routier |
locate | Tronçons de route les plus proches d'un point |
status | Version du moteur et fonctions disponibles |
POST ou GET. En POST, le corps JSON est la requête. En GET, passez le même JSON, encodé, dans le paramètre json :
GET /v2/routing/route?json=%7B%22locations%22%3A…%7D
Préférez POST : la requête reste lisible et n'est pas limitée par la longueur maximale d'une URL.
Seules les fonctions de ce tableau sont ouvertes. Toute autre fonction de Valhalla renvoie 404 avec le code unknown_endpoint.
Profils de déplacement
Le paramètre costing choisit le mode de déplacement. Il est obligatoire dans toutes les requêtes. Les options de chaque profil se règlent dans costing_options.{profil}.
costing | Mode | Options utiles de costing_options |
|---|---|---|
auto | Voiture | use_highways, use_tolls, use_ferry (0 à 1 : de « éviter » à « privilégier ») |
pedestrian | Piéton | walking_speed (km/h), max_distance (mètres de marche) |
bicycle | Vélo | bicycle_type (Road, Hybrid, City, Cross, Mountain), cycling_speed, use_roads |
{
"costing": "auto",
"costing_options": {
"auto": {"use_tolls": 0, "use_highways": 0.1}
}
}
La liste complète des options est dans la documentation des profils de Valhalla.
Calculer un itinéraire
Renvoie le trajet qui passe par les points dans l'ordre donné, avec la distance, la durée, la géométrie et les instructions de navigation.
Paramètres principaux
| Paramètre | Description |
|---|---|
locationsrequis | Liste ordonnée d'au moins deux points {"lat", "lon"}. Un point intermédiaire peut être de type "through" (passage sans arrêt) au lieu de "break" (arrêt, par défaut). |
costingrequis | Profil : auto, pedestrian ou bicycle. |
costing_options | Réglages du profil (voir profils). |
directions_options.units | kilometers (par défaut) ou miles. |
directions_options.language | Langue des instructions, par exemple fr-FR, nl-NL, de-DE. |
directions_type | none pour ne recevoir que le résumé et la géométrie, sans instructions : réponse plus légère. Se place à la racine de la requête, pas dans directions_options. |
alternates | Nombre d'itinéraires alternatifs souhaités. Ils sont renvoyés dans une liste alternates, à côté de trip. |
Exemple
curl "https://geoservice.example.com/v2/routing/route" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [
{"lat": 48.855199, "lon": 2.354035},
{"lat": 48.853138, "lon": 2.349185}
],
"costing": "auto",
"directions_options": {"units": "kilometers", "language": "fr-FR"}
}'
const res = await fetch("https://geoservice.example.com/v2/routing/route", {
method: "POST",
headers: { "X-API-Key": GEOSERVICE_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
locations: [
{ lat: 48.855199, lon: 2.354035 },
{ lat: 48.853138, lon: 2.349185 },
],
costing: "auto",
directions_options: { units: "kilometers", language: "fr-FR" },
}),
});
const { trip } = await res.json();
import httpx
r = httpx.post(
"https://geoservice.example.com/v2/routing/route",
headers={"X-API-Key": GEOSERVICE_KEY},
json={
"locations": [
{"lat": 48.855199, "lon": 2.354035},
{"lat": 48.853138, "lon": 2.349185},
],
"costing": "auto",
"directions_options": {"units": "kilometers", "language": "fr-FR"},
},
)
trip = r.json()["trip"]
{
"trip": {
"locations": [
{"type": "break", "lat": 48.855199, "lon": 2.354035, "side_of_street": "right", "original_index": 0},
{"type": "break", "lat": 48.853138, "lon": 2.349185, "original_index": 1}
],
"legs": [{
"maneuvers": [
{
"type": 2,
"instruction": "Conduisez vers le nord-ouest sur Rue de l'Hôtel de Ville.",
"verbal_post_transition_instruction": "Continuez pendant 20 mètres.",
"street_names": ["Rue de l'Hôtel de Ville"],
"time": 5.651, "length": 0.015,
"begin_shape_index": 0, "end_shape_index": 2,
"travel_mode": "drive"
},
…
],
"summary": {"time": 321.438, "length": 1.185},
"shape": "me{d|AortnCsAzDcA~CaB{AuNaM}BHkBBwIaEyAe…"
}],
"summary": {
"time": 321.438, "length": 1.185,
"has_toll": false, "has_highway": false, "has_ferry": false,
"min_lat": 48.853633, "min_lon": 2.349207, "max_lat": 48.856957, "max_lon": 2.356614
},
"status_message": "Found route between points",
"status": 0,
"units": "kilometers",
"language": "fr-FR"
}
}
trip.summary- Durée totale (
time, en secondes), distance (length, dans l'unité demandée) et emprise du trajet. trip.legs[]- Une étape par couple de points d'arrêt consécutifs, chacune avec son résumé, sa géométrie
shapeet ses manœuvres. maneuvers[]- Instructions pas à pas.
begin_shape_indexetend_shape_indexrenvoient aux points de la géométrie décodée.
Décoder la géométrie
La géométrie shape est une polyligne encodée avec une précision de 6 décimales, et non de 5 comme la polyligne de Google. Avec une bibliothèque de décodage, pensez à préciser cette précision, sinon le tracé tombe très loin de la réalité.
// Décode une polyligne Valhalla en [[lon, lat], …] (ordre GeoJSON).
function decodeShape(str, precision = 6) {
const factor = 10 ** precision;
const coords = [];
let index = 0, lat = 0, lon = 0;
while (index < str.length) {
for (const axis of [0, 1]) {
let result = 0, shift = 0, byte;
do {
byte = str.charCodeAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
const delta = result & 1 ? ~(result >> 1) : result >> 1;
if (axis === 0) lat += delta; else lon += delta;
}
coords.push([lon / factor, lat / factor]);
}
return coords;
}
const line = { type: "LineString", coordinates: decodeShape(trip.legs[0].shape) };
# pip install polyline
import polyline
points = polyline.decode(trip["legs"][0]["shape"], 6) # [(lat, lon), …]
line = {"type": "LineString", "coordinates": [[lon, lat] for lat, lon in points]}
Recevoir directement du GeoJSON
Pour éviter le décodage, demandez une réponse au format OSRM avec une géométrie GeoJSON : "format": "osrm" et "shape_format": "geojson", ensemble. Au format natif de Valhalla, shape_format est ignoré sans erreur et shape reste une polyligne encodée.
curl "https://geoservice.example.com/v2/routing/route" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [
{"lat": 48.855199, "lon": 2.354035},
{"lat": 48.853138, "lon": 2.349185}
],
"costing": "auto",
"directions_options": {"language": "fr-FR"},
"format": "osrm",
"shape_format": "geojson"
}'
const res = await fetch("https://geoservice.example.com/v2/routing/route", {
method: "POST",
headers: { "X-API-Key": GEOSERVICE_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
locations: [
{ lat: 48.855199, lon: 2.354035 },
{ lat: 48.853138, lon: 2.349185 },
],
costing: "auto",
directions_options: { language: "fr-FR" },
format: "osrm",
shape_format: "geojson",
}),
});
const { routes } = await res.json();
const line = routes[0].geometry; // {type: "LineString", coordinates: [[lon, lat], …]}
import httpx
r = httpx.post(
"https://geoservice.example.com/v2/routing/route",
headers={"X-API-Key": GEOSERVICE_KEY},
json={
"locations": [
{"lat": 48.855199, "lon": 2.354035},
{"lat": 48.853138, "lon": 2.349185},
],
"costing": "auto",
"directions_options": {"language": "fr-FR"},
"format": "osrm",
"shape_format": "geojson",
},
)
line = r.json()["routes"][0]["geometry"] # {"type": "LineString", "coordinates": [[lon, lat], …]}
{
"routes": [{
"distance": 1185.71,
"duration": 321.45,
"weight_name": "auto",
"weight": 731.777,
"geometry": {
"type": "LineString",
"coordinates": [[2.353976, 48.855143], [2.353882, 48.855185], [2.353802, 48.855219], …, [2.349207, 48.853633]]
},
"legs": [{
"distance": 1185.71,
"duration": 321.45,
"summary": "Rue de Rivoli, Pont d'Arcole",
"steps": [
{
"name": "Rue de l'Hôtel de Ville",
"distance": 15.71, "duration": 5.663,
"mode": "driving",
"maneuver": {
"type": "depart",
"instruction": "Conduisez vers le nord-ouest sur Rue de l'Hôtel de Ville.",
"location": [2.353976, 48.855143],
"bearing_before": 0, "bearing_after": 304
},
"geometry": {
"type": "LineString",
"coordinates": [[2.353976, 48.855143], [2.353882, 48.855185], [2.353802, 48.855219]]
},
…
},
…
]
}]
}],
"waypoints": [
{"name": "Rue de l'Hôtel de Ville", "location": [2.353976, 48.855143], "distance": 7.621},
{"name": "Rue d'Arcole", "location": [2.349207, 48.853633], "distance": 55.139}
],
"code": "Ok"
}
La réponse suit alors la structure d'OSRM, et non celle de Valhalla :
routes[].geometry- Tracé complet, directement utilisable comme géométrie d'une
FeatureGeoJSON (coordonnées en[lon, lat]). routes[].legs[].steps[]- Instructions pas à pas, chacune avec sa propre géométrie GeoJSON. Avec
"directions_type": "none", la liste est vide. distance,duration- Toujours en mètres et en secondes, quelle que soit la valeur de
directions_options.units.
Ce couple de paramètres fonctionne aussi avec optimized_route et trace_route (le tracé recalé est alors dans matchings[].geometry). Il est sans effet sur trace_attributes, dont la géométrie reste une polyligne encodée à décoder comme ci-dessus.
Optimiser une tournée
Cherche l'ordre de passage le plus court entre les points (problème du voyageur de commerce). Le premier et le dernier point restent fixes : pour une boucle, répétez le point de départ en fin de liste. Les paramètres sont ceux de route.
curl "https://geoservice.example.com/v2/routing/optimized_route" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [
{"lat": 48.853401, "lon": 2.348792},
{"lat": 48.833535, "lon": 2.364601},
{"lat": 48.845737, "lon": 2.342628},
{"lat": 48.824381, "lon": 2.330955},
{"lat": 48.853401, "lon": 2.348792}
],
"costing": "auto"
}'
{
"trip": {
"locations": [
{"type": "break", "lat": 48.853401, "lon": 2.348792, "original_index": 0},
{"type": "break", "lat": 48.845737, "lon": 2.342628, "original_index": 2},
{"type": "break", "lat": 48.824381, "lon": 2.330955, "original_index": 3},
{"type": "break", "lat": 48.833535, "lon": 2.364601, "original_index": 1},
{"type": "break", "lat": 48.853401, "lon": 2.348792, "original_index": 4}
],
"legs": [ … 4 étapes … ],
"summary": {"time": 2399.136, "length": 14.047}
}
}
trip.locations est renvoyé dans l'ordre optimisé ; original_index donne la position de chaque point dans votre requête. Ici, l'ordre optimal est 0 → 2 → 3 → 1 → 4 : 14 km en 40 minutes.
Calculer une matrice de temps et de distances
Calcule le temps et la distance de chaque origine (sources) vers chaque destination (targets), sans les géométries. Pour une matrice 1×N, donnez une seule source ; pour une matrice N×N, la même liste en sources et en destinations.
| Paramètre | Description |
|---|---|
sourcesrequis | Points d'origine {"lat", "lon"}. |
targetsrequis | Points de destination {"lat", "lon"}. |
costingrequis | Profil : auto, pedestrian ou bicycle. |
units | kilometers (par défaut) ou miles. |
curl "https://geoservice.example.com/v2/routing/sources_to_targets" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"sources": [{"lat": 48.824101, "lon": 2.358052}],
"targets": [
{"lat": 48.833535, "lon": 2.364601},
{"lat": 48.815678, "lon": 2.374128},
{"lat": 48.845737, "lon": 2.342628}
],
"costing": "auto"
}'
{
"sources_to_targets": [[
{"from_index": 0, "to_index": 0, "time": 216, "distance": 1.484},
{"from_index": 0, "to_index": 1, "time": 293, "distance": 2.3},
{"from_index": 0, "to_index": 2, "time": 485, "distance": 3.028}
]],
"sources": [{"lat": 48.824082, "lon": 2.357917}],
"targets": [
{"lat": 48.833633, "lon": 2.364525},
{"lat": 48.815759, "lon": 2.373594},
{"lat": 48.845708, "lon": 2.342741}
],
"units": "kilometers",
"algorithm": "costmatrix"
}
sources_to_targets[i][j] donne le trajet de la source i vers la destination j : time en secondes, distance dans l'unité demandée. Les coordonnées renvoyées sont celles des points recalés sur la route la plus proche. Chaque cellule porte aussi les caps et coordonnées de départ et d'arrivée, omis ici.
Taille des matrices. Le nombre de couples origine × destination et la distance couverte sont limités. Au-delà, le moteur renvoie une erreur 400 (voir limites). Pour de très grandes matrices, découpez-les en blocs de sources.
Calculer une isochrone
Renvoie la zone atteignable depuis un point en un temps donné (isochrone) ou sur une distance donnée (isodistance), au format GeoJSON.
| Paramètre | Description |
|---|---|
locationsrequis | Un point de départ {"lat", "lon"}. |
costingrequis | Profil : auto, pedestrian ou bicycle. |
contoursrequis | Liste de seuils : {"time": 10} en minutes, ou {"distance": 5} en kilomètres. Une couleur facultative par contour : "color": "ff8a3d". |
polygons | true pour des polygones (à privilégier pour l'affichage et l'analyse), false pour des lignes. |
denoise | De 0 à 1 : supprime les petites îles du résultat (1 : ne garde que le plus grand polygone). |
generalize | Tolérance de simplification du contour, en mètres. |
reverse | true pour la zone depuis laquelle on atteint le point (zone de chalandise). |
curl "https://geoservice.example.com/v2/routing/isochrone" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"locations": [{"lat": 48.853401, "lon": 2.348792}],
"costing": "pedestrian",
"contours": [{"time": 2}],
"polygons": true
}'
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"contour": 2.0, "metric": "time",
"color": "#bf4040", "fill": "#bf4040", "fillColor": "#bf4040",
"opacity": 0.33, "fill-opacity": 0.33, "fillOpacity": 0.33
},
"geometry": {
"type": "Polygon",
"coordinates": [[
[2.349792, 48.855144], [2.347792, 48.854954], [2.346638, 48.853554],
[2.348792, 48.851993], [2.350097, 48.852401], [2.351211, 48.853401],
[2.349792, 48.855144]
]]
}
}]
}
Une Feature par contour, prête à être affichée par Leaflet, MapLibre ou OpenLayers. properties.contour rappelle le seuil et metric sa nature (time ou distance). Avec plusieurs contours, le plus grand vient en premier.
Recaler une trace GPS
Deux fonctions pour associer une suite de positions GPS au réseau routier (map matching) :
trace_routerenvoie l'itinéraire réellement suivi, au même format queroute.trace_attributesrenvoie les tronçons parcourus et leurs attributs : noms de rue, vitesses, types de voie…
curl "https://geoservice.example.com/v2/routing/trace_route" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"shape": [
{"lat": 48.855143, "lon": 2.353976},
{"lat": 48.855219, "lon": 2.353802},
{"lat": 48.856957, "lon": 2.356614}
],
"costing": "auto",
"shape_match": "map_snap"
}'
La trace se donne dans shape (points {"lat", "lon"}, avec un horodatage time facultatif) ou dans encoded_polyline. shape_match vaut map_snap pour une trace GPS bruitée, edge_walk pour une géométrie qui suit déjà exactement les routes.
Trouver les routes proches d'un point
Renvoie les tronçons du réseau sur lesquels un point serait recalé pour un profil donné. Utile pour comprendre pourquoi un point de départ est mal placé.
curl "https://geoservice.example.com/v2/routing/locate" \
-H "X-API-Key: $GEOSERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{"locations": [{"lat": 48.853401, "lon": 2.348792}], "costing": "auto"}'
[{
"input_lat": 48.853401, "input_lon": 2.348792,
"edges": [
{"way_id": 45518911, "correlated_lat": 48.853633, "correlated_lon": 2.349207, "side_of_street": "neither", "percent_along": 0.0},
{"way_id": 203476035, "correlated_lat": 48.853021, "correlated_lon": 2.348907, "side_of_street": "neither", "percent_along": 0.0},
…
],
"nodes": [{"lon": 2.348907, "lat": 48.853021}, {"lon": 2.349207, "lat": 48.853633}]
}]
way_id est l'identifiant de la voie OpenStreetMap.
Statut du moteur
{
"version": "3.8.3",
"tileset_last_modified": 1789734449,
"available_actions": ["tile", "status", "centroid", "expansion", "transit_available", "trace_attributes",
"trace_route", "isochrone", "optimized_route", "sources_to_targets", "height", "route", "locate"]
}
tileset_last_modified est la date (horodatage Unix) des données routières en service. available_actions liste les fonctions du moteur ; seules celles documentées ici sont ouvertes par le GeoService.
Limites et erreurs du moteur
Le moteur limite la taille des requêtes : nombre de couples dans une matrice, distance d'un itinéraire selon le profil, nombre et durée des contours d'isochrone. Une requête qui dépasse une limite reçoit une erreur 400 de Valhalla, transmise telle quelle :
{"error_code": 120, "error": "Insufficient number of locations provided", "status_code": 400, "status": "Bad Request"}
Toutes les erreurs de requête ont le même code HTTP 400 : distinguez-les par error_code.
error_code | Signification | Que faire |
|---|---|---|
| 120 | Pas assez de points | Fournir au moins deux points à route. |
| 125 | Profil inconnu (No costing method found) | Utiliser auto, pedestrian ou bicycle. |
| 150 à 158 | Limite de service dépassée (matrice, distance, contours…) | Réduire la requête ou la découper. |
| 171 | Aucune route à proximité d'un point | Vérifier les coordonnées (inversion lat/lon ?) et la couverture. |
| 442 | Aucun itinéraire trouvé entre les points | Points non reliés pour ce profil (île, zone piétonne…). |
Trajets à pied de plus de 100 km. Le profil pedestrian limite par défaut la marche à 100 km. Au-delà, aucun itinéraire n'est trouvé : augmentez costing_options.pedestrian.max_distance (en mètres) dans votre requête, par exemple 300000.
La liste complète des codes figure dans la référence des erreurs de Valhalla. Les erreurs propres au GeoService sont décrites dans Erreurs.