Pourquoi migrer
L'ancienne API /services est dépréciée. Elle continue de répondre au même format qu'avant, mais elle n'évoluera plus : aucune nouvelle route, aucun nouveau paramètre. Seuls les correctifs de sécurité et de justesse y sont encore apportés.
Elle sera retirée le 1er avril 2027 : le 31 mars 2027 est le dernier jour où elle répond. Migrez dès maintenant pour ne pas avoir à le faire dans l'urgence.
L'API v2 renvoie les réponses des moteurs dans leur format d'origine, documenté par chacun d'eux, et ouvre des fonctions que l'ancienne API n'offre pas :
- tournées optimisées (ordre de passage optimal entre plusieurs points) et recalage de traces GPS ;
- itinéraires alternatifs, options de profil (éviter les péages, les autoroutes, les ferries…) et zones de chalandise (isochrone
reverse) ; - géocodage OSM structuré (rue, ville, code postal séparés), filtré par pays ou par emprise, et recherche par identifiant ;
- géocodage de fichiers CSV entiers en un seul appel ;
- et, à titre expérimental, les cellules H3.
Obtenir votre clé d'API
/services s'appelle sans clé. /v2 en exige une sur chaque appel, transmise dans l'en-tête X-API-Key (ou, à défaut, dans le paramètre api_key). Sans elle, la réponse est 401 unauthorized.
Demandez votre clé à Galigeo, puis suivez la prise en main pour l'envoyer et la protéger : une clé se garde côté serveur, jamais dans le code d'une page web.
Correspondance des routes
| Ancienne route | Route v2 | Ce qui change |
|---|---|---|
GET /services/ban/search | GET /v2/geocoding/ban/search | Le chemin et la clé : la réponse est la même |
GET /services/ban/reverse | GET /v2/geocoding/ban/reverse | Le chemin et la clé : la réponse est la même |
GET /services/geocode | GET /v2/geocoding/osm/search | Paramètres renommés, propriétés réorganisées |
GET /services/reverse | GET /v2/geocoding/osm/reverse | Paramètres renommés, propriétés réorganisées |
GET /services/directions/route | POST /v2/routing/route | Requête JSON, unités, géométrie encodée |
GET /services/directions/isochrone | POST /v2/routing/isochrone | Requête JSON, durées en minutes |
POST /services/directions/matrix | POST /v2/routing/sources_to_targets | Points {lat, lon}, une cellule par couple |
Les exemples ci-dessous envoient la même requête aux deux API ; les réponses sont réelles, abrégées quand elles sont longues (…).
Adresses BAN : changer le chemin, c'est tout
C'est la migration la plus simple. /services/ban/* et /v2/geocoding/ban/* interrogent le même moteur, avec les mêmes paramètres (q, limit, type, postcode, citycode, lat, lon, autocomplete…) et renvoient la même réponse. Seuls changent le préfixe du chemin et la clé d'API.
/servicescurl -G "https://geoservice.example.com/services/ban/search" \
--data-urlencode "q=87 avenue d'italie, 75013" \
-d limit=1
const url = new URL("https://geoservice.example.com/services/ban/search");
url.search = new URLSearchParams({ q: "87 avenue d'italie, 75013", limit: 1 });
const { features } = await (await fetch(url)).json();
const [lon, lat] = features[0].geometry.coordinates;
import httpx
r = httpx.get(
"https://geoservice.example.com/services/ban/search",
params={"q": "87 avenue d'italie, 75013", "limit": 1},
)
lon, lat = r.json()["features"][0]["geometry"]["coordinates"]
/v2curl -G "https://geoservice.example.com/v2/geocoding/ban/search" \
-H "X-API-Key: $GEOSERVICE_KEY" \
--data-urlencode "q=87 avenue d'italie, 75013" \
-d limit=1
const url = new URL("https://geoservice.example.com/v2/geocoding/ban/search");
url.search = new URLSearchParams({ q: "87 avenue d'italie, 75013", limit: 1 });
const res = await fetch(url, { headers: { "X-API-Key": GEOSERVICE_KEY } });
const { features } = await res.json();
const [lon, lat] = features[0].geometry.coordinates;
import httpx
r = httpx.get(
"https://geoservice.example.com/v2/geocoding/ban/search",
headers={"X-API-Key": GEOSERVICE_KEY},
params={"q": "87 avenue d'italie, 75013", "limit": 1},
)
lon, lat = r.json()["features"][0]["geometry"]["coordinates"]
Les deux réponses portent les mêmes champs, aux mêmes valeurs : elles sont décrites dans Géocodage BAN. La v2 y ajoute le géocodage de fichiers CSV, bien plus rapide que des milliers d'appels unitaires.
Géocodage OSM : geocode et reverse
L'ancienne API répondait au format de photon ; la v2 transmet celui de Nominatim. Le plus proche est format=geojson : une FeatureCollection, coordonnées [lon, lat], comme avant. Ajoutez addressdetails=1 pour recevoir l'adresse détaillée.
Paramètres
| Avant | Après | Remarque |
|---|---|---|
q | q | Inchangé. |
lang=fr | accept-language=fr | Langue des noms. |
limit | limit | Sans lui, 10 résultats au lieu de 15. Ignoré par reverse, qui en renvoie un seul. |
lat, lon (géocodage inverse) | lat, lon | Inchangés. |
| — | format=geojson, addressdetails=1 | À ajouter : sans format, reverse répond en XML. |
bbox, countrycode | viewbox + bounded=1, countrycodes | Disponibles à nouveau en v2 (voir paramètres communs). |
Propriétés d'un résultat
Avant (properties) | Après (properties, format=geojson) |
|---|---|
osm_id | osm_id |
osm_type : N, W, R | osm_type : node, way, relation |
osm_key, osm_value | category, type |
name | name (et display_name, l'adresse complète) |
housenumber, street, postcode | address.house_number, address.road, address.postcode |
city | address.city, sinon town, village ou municipality |
state, country | address.state, address.country |
countrycode : FR | address.country_code : fr, en minuscules |
extent : [minLon, maxLat, maxLon, minLat] | bbox de la Feature : [minLon, minLat, maxLon, maxLat], dans un autre ordre, et présent sur tous les résultats |
Exemple
/servicescurl -G "https://geoservice.example.com/services/geocode" \
--data-urlencode "q=Avenue d'Italie 75013 Paris France" \
-d lang=fr -d limit=1
const url = new URL("https://geoservice.example.com/services/geocode");
url.search = new URLSearchParams({
q: "Avenue d'Italie 75013 Paris France",
lang: "fr",
limit: 1,
});
const { features } = await (await fetch(url)).json();
const { name, city, countrycode } = features[0].properties;
import httpx
r = httpx.get(
"https://geoservice.example.com/services/geocode",
params={"q": "Avenue d'Italie 75013 Paris France", "lang": "fr", "limit": 1},
)
props = r.json()["features"][0]["properties"]
print(props["name"], props["city"], props["countrycode"])
{
"features": [{
"geometry": {"type": "Point", "coordinates": [2.3574814, 48.8254891]},
"type": "Feature",
"properties": {
"osm_id": 685719842,
"osm_type": "W",
"osm_key": "highway",
"osm_value": "primary",
"name": "Avenue d'Italie",
"street": "Avenue d'Italie",
"postcode": "75013",
"country": "France",
"city": "Paris",
"countrycode": "FR",
"extent": [2.3574144, 48.825715, 2.3575502, 48.8252637]
}
}],
"type": "FeatureCollection"
}
/v2curl -G "https://geoservice.example.com/v2/geocoding/osm/search" \
-H "X-API-Key: $GEOSERVICE_KEY" \
--data-urlencode "q=Avenue d'Italie 75013 Paris France" \
-d format=geojson -d addressdetails=1 \
-d accept-language=fr -d limit=1
const url = new URL("https://geoservice.example.com/v2/geocoding/osm/search");
url.search = new URLSearchParams({
q: "Avenue d'Italie 75013 Paris France",
format: "geojson",
addressdetails: 1,
"accept-language": "fr",
limit: 1,
});
const res = await fetch(url, { headers: { "X-API-Key": GEOSERVICE_KEY } });
const { features } = await res.json();
const { name, address } = features[0].properties;
// address.city, address.country_code ("fr", en minuscules)
import httpx
r = httpx.get(
"https://geoservice.example.com/v2/geocoding/osm/search",
headers={"X-API-Key": GEOSERVICE_KEY},
params={
"q": "Avenue d'Italie 75013 Paris France",
"format": "geojson",
"addressdetails": 1,
"accept-language": "fr",
"limit": 1,
},
)
props = r.json()["features"][0]["properties"]
print(props["name"], props["address"]["city"], props["address"]["country_code"])
{
"type": "FeatureCollection",
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
"features": [{
"type": "Feature",
"properties": {
"place_id": 256245,
"osm_type": "way",
"osm_id": 685719842,
"place_rank": 26,
"category": "highway",
"type": "primary",
"importance": 0.0533433333333333,
"addresstype": "road",
"name": "Avenue d'Italie",
"display_name": "Avenue d'Italie, Maison-Blanche, Paris 13e Arrondissement, Paris, 75013, France",
"address": {
"road": "Avenue d'Italie",
"quarter": "Maison-Blanche",
"suburb": "Paris 13e Arrondissement",
"city": "Paris",
"postcode": "75013",
"country": "France",
"country_code": "fr"
}
},
"bbox": [2.3574144, 48.8252637, 2.3575502, 48.825715],
"geometry": {"type": "Point", "coordinates": [2.3574814, 48.8254891]}
}]
}
Le géocodage inverse suit la même correspondance : /services/reverse?lat=…&lon=…&lang=fr devient /v2/geocoding/osm/reverse?lat=…&lon=…&format=geojson&accept-language=fr, qui renvoie une FeatureCollection d'un seul résultat, avec l'adresse détaillée par défaut. Un point sans résultat n'y renvoie pas une collection vide, mais {"error": "Unable to geocode"}, toujours en 200.
Itinéraire : directions/route
L'ancienne API répondait au format de GraphHopper ; la v2 transmet celui de Valhalla. La requête devient un objet JSON envoyé en POST.
Paramètres
| Avant (paramètres d'URL) | Après (corps JSON) | Remarque |
|---|---|---|
point=lat,lon, répété | locations: [{"lat", "lon"}, …] | Même ordre de passage. |
vehicule (ou vehicle) : car, foot, bike | costing : auto, pedestrian, bicycle | Obligatoire en v2 ; il n'y a plus de profil par défaut. |
locale=fr | directions_options.language: "fr-FR" | Sans elle, les instructions sont en anglais (en-US). |
| — | directions_options.units: "kilometers" | Le défaut ; miles possible. |
points_encoded | — | La géométrie est toujours une polyligne encodée, voir ci-dessous. |
Réponse
| Avant | Après | Conversion |
|---|---|---|
paths[0].distance | trip.summary.length | Mètres → kilomètres |
paths[0].time | trip.summary.time | Millisecondes → secondes |
paths[0].bbox | trip.summary.min_lon, min_lat, max_lon, max_lat | Quatre champs au lieu d'une liste |
paths[0].points | trip.legs[].shape | Une polyligne de précision 6 par étape, à décoder puis mettre bout à bout ; ou du GeoJSON avec "format": "osrm" et "shape_format": "geojson" |
paths[0].instructions[] | trip.legs[].maneuvers[] | Une liste par étape |
text | instruction | Même texte |
distance (m), time (ms) | length (km), time (s) | Mêmes conversions que le résumé |
interval: [début, fin] | begin_shape_index, end_shape_index | Index dans la géométrie de l'étape |
street_name | street_names[0] | Absent quand la voie n'a pas de nom |
sign (−7 à 7) | type (0 à 43) | Autre nomenclature : voir les types de manœuvre de Valhalla |
snapped_waypoints | Premier et dernier point de chaque shape | Les points recalés sur la route |
weight, hints, info | trip.summary.cost | hints et info n'ont pas d'équivalent |
Exemple
/servicescurl -G "https://geoservice.example.com/services/directions/route" \
-d point=48.855199,2.354035 \
-d point=48.853138,2.349185 \
-d vehicule=car -d locale=fr \
-d points_encoded=false
const url = new URL("https://geoservice.example.com/services/directions/route");
url.search = new URLSearchParams([
["point", "48.855199,2.354035"],
["point", "48.853138,2.349185"],
["vehicule", "car"],
["locale", "fr"],
["points_encoded", "false"],
]);
const { paths: [path] } = await (await fetch(url)).json();
console.log(`${path.distance} m, ${path.time / 1000} s`); // mètres, millisecondes
import httpx
r = httpx.get(
"https://geoservice.example.com/services/directions/route",
params=[
("point", "48.855199,2.354035"),
("point", "48.853138,2.349185"),
("vehicule", "car"),
("locale", "fr"),
("points_encoded", "false"),
],
)
path = r.json()["paths"][0]
print(f"{path['distance']} m, {path['time'] / 1000} s") # mètres, millisecondes
{
"hints": {},
"paths": [{
"instructions": [
{"distance": 15.0, "sign": 0, "interval": [0, 2], "text": "Conduisez vers le nord-ouest sur Rue de l'Hôtel de Ville.", "time": 5663, "street_name": "Rue de l'Hôtel de Ville"},
{"distance": 79.0, "sign": 2, "interval": [2, 8], "text": "Tournez à droite dans Rue de Brosse.", "time": 34211, "street_name": "Rue de Brosse"},
…
{"distance": 0.0, "sign": 4, "interval": [75, 75], "text": "Vous êtes arrivé à votre destination.", "time": 0, "street_name": ""}
],
"distance": 1185.0,
"time": 321450,
"bbox": [2.349207, 48.853633, 2.356614, 48.856957],
"points_encoded": false,
"points": {"type": "LineString", "coordinates": [[2.353976, 48.855143], [2.353882, 48.855185], …]},
"snapped_waypoints": {"type": "LineString", "coordinates": [[2.353976, 48.855143], [2.349207, 48.853633]]},
…
}],
"info": {"took": 96, "copyrights": ["Valhalla", "OpenStreetMap contributors"]}
}
/v2curl "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();
console.log(`${trip.summary.length} km, ${trip.summary.time} s`); // kilomètres, secondes
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"},
},
)
summary = r.json()["trip"]["summary"]
print(f"{summary['length']} km, {summary['time']} s") # kilomètres, secondes
{
"trip": {
"legs": [{
"maneuvers": [
{"type": 2, "instruction": "Conduisez vers le nord-ouest sur Rue de l'Hôtel de Ville.", "time": 5.663, "length": 0.015, "begin_shape_index": 0, "end_shape_index": 2, "street_names": ["Rue de l'Hôtel de Ville"], …},
{"type": 10, "instruction": "Tournez à droite dans Rue de Brosse.", "street_names": ["Rue de Brosse"], …},
…
{"type": 4, "instruction": "Vous êtes arrivé à votre destination.", "time": 0.0, "length": 0.0, "begin_shape_index": 75, "end_shape_index": 75, …}
],
"shape": "me{d|AortnCsAzDcA~CaB{AuNaM}BHkBBwIaEyAeEGcDmAyh@…"
}],
"summary": {
"time": 321.45, "length": 1.185, "cost": 731.776,
"min_lat": 48.853633, "min_lon": 2.349207, "max_lat": 48.856957, "max_lon": 2.356614,
…
},
"units": "kilometers",
"language": "fr-FR",
…
}
}
Le même trajet : 1 185 m en 321 450 ms d'un côté, 1,185 km en 321,45 s de l'autre. Les instructions sont désormais données par étape (legs) : avec trois points ou plus, mettez bout à bout les manœuvres et les géométries de chaque étape.
Isochrone : directions/isochrone
Paramètres
| Avant (paramètres d'URL) | Après (corps JSON) | Remarque |
|---|---|---|
point=lat,lon | locations: [{"lat", "lon"}] | Un seul point. |
vehicule (ou vehicle) | costing | Mêmes valeurs que pour l'itinéraire. |
time_limit, en secondes (600 par défaut) | contours: [{"time": …}], en minutes | time_limit=120 devient {"time": 2}. Pas de valeur par défaut. |
distance_limit, en mètres | contours: [{"distance": …}], en kilomètres | distance_limit=1500 devient {"distance": 1.5}. |
buckets=n | n contours | Écrivez chaque seuil : time_limit=600&buckets=3 devient [{"time": 3.33}, {"time": 6.67}, {"time": 10}]. |
| — | polygons: true | À ajouter pour recevoir des polygones, comme avant. |
Réponse
La liste polygons devient une FeatureCollection GeoJSON standard (features). Chaque contour porte son seuil dans properties.contour (en minutes ou en kilomètres) au lieu d'un rang bucket, et l'ordre s'inverse : le plus grand contour vient en premier, là où bucket: 0 était le plus petit. copyrights disparaît.
/servicescurl -G "https://geoservice.example.com/services/directions/isochrone" \
-d point=48.853401,2.348792 \
-d time_limit=120 -d vehicule=foot
const url = new URL("https://geoservice.example.com/services/directions/isochrone");
url.search = new URLSearchParams({
point: "48.853401,2.348792",
time_limit: 120, // secondes
vehicule: "foot",
});
const { polygons } = await (await fetch(url)).json();
const zone = polygons[0].geometry; // bucket 0 : le plus petit
import httpx
r = httpx.get(
"https://geoservice.example.com/services/directions/isochrone",
params={"point": "48.853401,2.348792", "time_limit": 120, "vehicule": "foot"},
)
zone = r.json()["polygons"][0]["geometry"] # bucket 0 : le plus petit
{
"polygons": [{
"geometry": {
"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]
]],
"type": "Polygon"
},
"type": "Feature",
"properties": {"bucket": 0}
}],
"copyrights": ["Valhalla", "OpenStreetMap contributors"]
}
/v2curl "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
}'
const res = await fetch("https://geoservice.example.com/v2/routing/isochrone", {
method: "POST",
headers: { "X-API-Key": GEOSERVICE_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
locations: [{ lat: 48.853401, lon: 2.348792 }],
costing: "pedestrian",
contours: [{ time: 2 }], // minutes
polygons: true,
}),
});
const { features } = await res.json();
const zone = features[0].geometry; // le plus grand contour en premier
import httpx
r = httpx.post(
"https://geoservice.example.com/v2/routing/isochrone",
headers={"X-API-Key": GEOSERVICE_KEY},
json={
"locations": [{"lat": 48.853401, "lon": 2.348792}],
"costing": "pedestrian",
"contours": [{"time": 2}], # minutes
"polygons": True,
},
)
zone = r.json()["features"][0]["geometry"] # le plus grand contour en premier
{
"features": [{
"properties": {
"contour": 2.0, "metric": "time",
"color": "#bf4040", "fill": "#bf4040", "fillColor": "#bf4040",
"opacity": 0.33, "fill-opacity": 0.33, "fillOpacity": 0.33
},
"geometry": {
"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]
]],
"type": "Polygon"
},
"type": "Feature"
}],
"type": "FeatureCollection"
}
Le polygone est le même ; seule l'enveloppe change. Comme avant, chaque contour n'a qu'un polygone : c'est l'effet de denoise, qui vaut 1 par défaut. Abaissez-le pour garder aussi les îlots.
Matrice : directions/matrix
Requête
| Avant (corps JSON) | Après (corps JSON) | Remarque |
|---|---|---|
from_points: [[lon, lat], …] | sources: [{"lat", "lon"}, …] | Attention à l'ordre : la longitude venait en premier, les champs sont désormais nommés. |
to_points | targets | Idem. |
points (N×N) | sources et targets | Donnez la même liste deux fois. |
vehicle : car, foot, bike | costing : auto, pedestrian, bicycle | Obligatoire en v2. |
out_arrays | — | Durée et distance sont toujours renvoyées. |
fail_fast | — | Une case sans itinéraire est toujours renvoyée, à null. |
Réponse
Les tableaux distances et times deviennent une seule grille, sources_to_targets[i][j], dont chaque cellule porte time (secondes, comme avant) et distance (kilomètres, au lieu de mètres entiers). weights n'a pas d'équivalent : utilisez time.
/servicescurl "https://geoservice.example.com/services/directions/matrix" \
-H "Content-Type: application/json" \
-d '{
"from_points": [[2.358052, 48.824101]],
"to_points": [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737]
],
"vehicle": "car",
"out_arrays": ["distances", "times"]
}'
const res = await fetch("https://geoservice.example.com/services/directions/matrix", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
from_points: [[2.358052, 48.824101]], // [lon, lat]
to_points: [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737],
],
vehicle: "car",
out_arrays: ["distances", "times"],
}),
});
const { distances, times } = await res.json(); // mètres, secondes
import httpx
r = httpx.post(
"https://geoservice.example.com/services/directions/matrix",
json={
"from_points": [[2.358052, 48.824101]], # [lon, lat]
"to_points": [
[2.364601, 48.833535],
[2.374128, 48.815678],
[2.342628, 48.845737],
],
"vehicle": "car",
"out_arrays": ["distances", "times"],
},
)
corps = r.json()
distances, times = corps["distances"], corps["times"] # mètres, secondes
{
"distances": [[1484, 2300, 3028]],
"times": [[216, 293, 485]],
"info": {"copyrights": ["Valhalla", "OpenStreetMap contributors"]}
}
/v2curl "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"
}'
const res = await fetch("https://geoservice.example.com/v2/routing/sources_to_targets", {
method: "POST",
headers: { "X-API-Key": GEOSERVICE_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
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",
}),
});
const { sources_to_targets } = await res.json();
// Les tableaux d'avant : distances en mètres, durées en secondes
// (une case sans itinéraire reste à null, comme avec fail_fast=false)
const distances = sources_to_targets.map((row) =>
row.map((c) => (c.distance === null ? null : Math.round(c.distance * 1000))),
);
const times = sources_to_targets.map((row) => row.map((c) => c.time));
import httpx
r = httpx.post(
"https://geoservice.example.com/v2/routing/sources_to_targets",
headers={"X-API-Key": GEOSERVICE_KEY},
json={
"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",
},
)
grille = r.json()["sources_to_targets"]
# Les tableaux d'avant : distances en mètres, durées en secondes
# (une case sans itinéraire reste à None, comme avec fail_fast=false)
distances = [
[None if c["distance"] is None else round(c["distance"] * 1000) for c in ligne]
for ligne in grille
]
times = [[c["time"] for c in ligne] for ligne in grille]
{
"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": […],
"units": "kilometers",
"algorithm": "costmatrix"
}
Mêmes valeurs : 1 484 m deviennent 1,484 km, les durées restent en secondes. Les coordonnées de sources et targets sont celles des points recalés sur la route.
Les erreurs changent de forme
Sur /services, toutes les erreurs arrivent au format du GeoService (application/problem+json), y compris celles du moteur, traduites : un trajet impossible y est un 422 route_impossible. Sur /v2, une erreur du moteur est transmise telle quelle, dans le format du moteur. Un code qui testait route_impossible doit désormais lire error_code.
/services{
"type": "tag:galigeo.com,2026:geoservice:route_impossible",
"title": "Itinéraire impossible",
"status": 422,
"detail": "Un des points est trop loin de toute voie praticable.",
"instance": "/services/directions/route",
"code": "route_impossible"
}
/v2{
"error_code": 171,
"error": "No suitable edges near location",
"status_code": 400,
"status": "Bad Request"
}
Le même itinéraire, hors des données couvertes, dans les deux API.
Sur /services | Sur /v2 |
|---|---|
422 route_impossible | 400 de Valhalla, error_code 170, 171 ou 442 (154 pour des points trop éloignés) |
400 invalid_parameter | 400 du moteur, dans son format (voir erreurs des moteurs) |
502 upstream_invalid_response | Erreur du moteur, transmise avec son propre code HTTP |
404, 405, 413, 503, 504 | Inchangés, au format du GeoService ; s'y ajoute 401 unauthorized sans clé valide |
Pour traiter les deux familles d'erreurs d'un même code, reprenez l'exemple de gestion des erreurs.
Calendrier de la dépréciation
- Dépréciation
/servicescontinue de répondre, au même format, mais n'évolue plus. Ses réponses portent les en-têtesDeprecation,LinketSunsetà partir de leur mise en service : voir les détecter dans votre code. - Annonce du retrait
La date de retrait est fixée au 1er avril 2027, à 0 h UTC (2 h à Paris). L'en-tête
Sunsetde chaque réponse la rappelle. - Retrait, le 1er avril 2027
Les routes
/servicescessent de répondre. Seule l'API/v2reste ouverte.
Une question sur votre migration ? Contactez Galigeo, avec l'ancienne requête et l'en-tête X-GGO-Upstream de sa réponse.