From d0fa9c34065c6024c2c71280509f5a38a1a9f81d Mon Sep 17 00:00:00 2001 From: esgn <5435148+esgn@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:24:42 +0200 Subject: [PATCH 1/5] fix(itinerary): route with Valhalla, like the travel time isochrones The distance tool used bdtopo-osrm while travel_time_filter isochrones use bdtopo-valhalla, so the two could disagree by up to 12% on walking times. Reuse TRAVEL_TIME_RESOURCE for the itinerary. --- src/gpf/itinerary.ts | 6 ++++-- test/gpf/itinerary.test.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gpf/itinerary.ts b/src/gpf/itinerary.ts index c16ed601..9eba76b7 100644 --- a/src/gpf/itinerary.ts +++ b/src/gpf/itinerary.ts @@ -3,11 +3,13 @@ import logger from "../logger.js"; import type { JsonFetcher } from "../helpers/http.js"; import type { RateLimiter } from "../helpers/RateLimiter.js"; import { getNavigationRateLimiter } from "./navigationRateLimiter.js"; -import { TRAVEL_TIME_PROFILES } from "./navigation.js"; +import { TRAVEL_TIME_PROFILES, TRAVEL_TIME_RESOURCE } from "./navigation.js"; export const NAVIGATION_ITINERARY_SOURCE = "Géoplateforme (calcul d'itinéraire)"; export const NAVIGATION_ITINERARY_URL = "https://data.geopf.fr/navigation/itineraire"; -export const ITINERARY_RESOURCE = "bdtopo-osrm"; +// Same engine as the `travel_time_filter` isochrones, so that both report the +// same travel times. +export const ITINERARY_RESOURCE = TRAVEL_TIME_RESOURCE; export const ITINERARY_PROFILES = TRAVEL_TIME_PROFILES; export const ITINERARY_METRICS = ["time", "distance"] as const; diff --git a/test/gpf/itinerary.test.ts b/test/gpf/itinerary.test.ts index 46bb3682..a9b552db 100644 --- a/test/gpf/itinerary.test.ts +++ b/test/gpf/itinerary.test.ts @@ -36,7 +36,7 @@ describe("NavigationItineraryClient", () => { const parsedUrl = new URL(urls[0]); expect(parsedUrl.origin + parsedUrl.pathname).toEqual("https://data.geopf.fr/navigation/itineraire"); - expect(parsedUrl.searchParams.get("resource")).toEqual("bdtopo-osrm"); + expect(parsedUrl.searchParams.get("resource")).toEqual("bdtopo-valhalla"); expect(parsedUrl.searchParams.get("start")).toEqual("3.274356,49.839862"); expect(parsedUrl.searchParams.get("end")).toEqual("5.044572,47.326213"); expect(parsedUrl.searchParams.get("profile")).toEqual("car"); From 693983ec8125c160aabd038907cba4ac14094b5e Mon Sep 17 00:00:00 2001 From: esgn <5435148+esgn@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:28:37 +0200 Subject: [PATCH 2/5] fix(distance): restore the ellipsoidal precision lost in the rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rebase brought back "plus précise et coûteuse, précision à 1mm" instead of the 0.5 cm wording merged in #183, and the new ", " join produced "suivi :, `spherical`". Join the profile lines as before. --- docs/mcp-tools.md | 4 ++-- src/tools/DistanceTool.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md index 4ba2a787..94fcbd86 100644 --- a/docs/mcp-tools.md +++ b/docs/mcp-tools.md @@ -2237,7 +2237,7 @@ Renvoie aussi une estimation du temps de trajet dans le cas où un profil (march | `arrival` | object | oui | Le point d'arrivée | | `departure` | object | oui | Le point de départ | | `optimize` | string (enum) | non | La métrique à optimiser, lorsqu'il y a un choix : `time` chemin le plus rapide, `distance` chemin le plus court. Cette option est sans effet lorsque `profile=spherical` ou `ellipsoidal`. Valeurs : time, distance. Valeur par défaut : time. | -| `profile` | string (enum) | non | Le type de chemin suivi :, `spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%), `ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise et coûteuse, précision à 1mm), `car` en voiture, `pedestrian` à pied. Valeurs : spherical, ellipsoidal, car, pedestrian. Valeur par défaut : spherical. | +| `profile` | string (enum) | non | Le type de chemin suivi : `spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%), `ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise, précision à 0.5cm), `car` en voiture, `pedestrian` à pied. Valeurs : spherical, ellipsoidal, car, pedestrian. Valeur par défaut : spherical. |
Schéma d’entrée brut @@ -2301,7 +2301,7 @@ Renvoie aussi une estimation du temps de trajet dans le cas où un profil (march "pedestrian" ], "default": "spherical", - "description": "Le type de chemin suivi :, `spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%), `ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise et coûteuse, précision à 1mm), `car` en voiture, `pedestrian` à pied." + "description": "Le type de chemin suivi : `spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%), `ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise, précision à 0.5cm), `car` en voiture, `pedestrian` à pied." }, "optimize": { "type": "string", diff --git a/src/tools/DistanceTool.ts b/src/tools/DistanceTool.ts index 8b5ae88b..b477affc 100644 --- a/src/tools/DistanceTool.ts +++ b/src/tools/DistanceTool.ts @@ -27,11 +27,11 @@ const distanceInputSchema = z.object({ .enum(["spherical", "ellipsoidal", ...ITINERARY_PROFILES]) .default("spherical") .describe(["Le type de chemin suivi :", - "`spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%)", - "`ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise et coûteuse, précision à 1mm)", - "`car` en voiture", - "`pedestrian` à pied.", - ].join(", ")), + " `spherical` distance à vol d'oiseau (Terre ronde, précision à 0.5%),", + " `ellipsoidal` distance à vol d'oiseau (Terre ellipsoïde, plus précise, précision à 0.5cm),", + " `car` en voiture,", + " `pedestrian` à pied.", + ].join("")), optimize: z .enum(ITINERARY_METRICS) .default("time") From 84923b774a786d2d38cb9931775ef4afc52bf182 Mon Sep 17 00:00:00 2001 From: esgn <5435148+esgn@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:29:46 +0200 Subject: [PATCH 3/5] fix(distance): keep a tenth of a minute in the travel time Rounding to the whole minute turned a 40-second walk into 1 or even 0 minutes. --- src/tools/DistanceTool.ts | 2 +- test/tools/distance.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/DistanceTool.ts b/src/tools/DistanceTool.ts index b477affc..562f8794 100644 --- a/src/tools/DistanceTool.ts +++ b/src/tools/DistanceTool.ts @@ -107,7 +107,7 @@ class DistanceTool extends BaseTool { }); return { distance: itinerary.distance, - time: Math.round(itinerary.duration) + time: Math.round(itinerary.duration * 10) / 10 }; } default: { diff --git a/test/tools/distance.test.ts b/test/tools/distance.test.ts index 71428160..5c53c78f 100644 --- a/test/tools/distance.test.ts +++ b/test/tools/distance.test.ts @@ -72,7 +72,7 @@ describe("Test DistanceTool", () => { it.each([ ["car", 395174, 212, 212], - ["pedestrian", 12345, 67.8, 68], + ["pedestrian", 12345, 67.83, 67.8], ] as const)("should return itinerary distance and time for %s", async (profile, distance, duration, time) => { const tool = new DistanceTool(); const getItinerarySpy = vi.spyOn(navigationItineraryClient, "getItinerary").mockResolvedValue({ distance, duration }); From c89e33d53f7ef57f1ff36e9c92ed29074da45b1b Mon Sep 17 00:00:00 2001 From: esgn <5435148+esgn@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:30:40 +0200 Subject: [PATCH 4/5] docs(distance): say which profiles return a travel time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `profile` always has a value (`spherical` by default), so "lorsqu'un profil est renseigné" was always true. --- docs/mcp-tools.md | 2 +- src/tools/DistanceTool.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md index 94fcbd86..2163ef58 100644 --- a/docs/mcp-tools.md +++ b/docs/mcp-tools.md @@ -2226,7 +2226,7 @@ Distance et temps de trajet entre deux points ``` Renvoie la distance (en mètres) entre deux points à partir de leur longitude et latitude. -Renvoie aussi une estimation du temps de trajet dans le cas où un profil (marche, voiture) est renseigné. +Renvoie aussi une estimation du temps de trajet lorsque `profile` vaut `car` ou `pedestrian`. (source : Géoplateforme (calcul d'itinéraire)). ``` diff --git a/src/tools/DistanceTool.ts b/src/tools/DistanceTool.ts index 562f8794..87bcd180 100644 --- a/src/tools/DistanceTool.ts +++ b/src/tools/DistanceTool.ts @@ -55,7 +55,7 @@ type DistanceInput = z.infer; const DISTANCE_TOOL_DESCRIPTION = [ `Renvoie la distance (en mètres) entre deux points à partir de leur longitude et latitude.`, - `Renvoie aussi une estimation du temps de trajet dans le cas où un profil (marche, voiture) est renseigné.`, + `Renvoie aussi une estimation du temps de trajet lorsque \`profile\` vaut \`car\` ou \`pedestrian\`.`, `(source : ${NAVIGATION_ITINERARY_SOURCE}).`, ].join("\n"); From 41287e2f21be9311d6fade6315c6b0bb92c42453 Mon Sep 17 00:00:00 2001 From: esgn <5435148+esgn@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:31:45 +0200 Subject: [PATCH 5/5] fix(distance): round the itinerary distance to the centimeter The spherical and ellipsoidal profiles already round to the centimeter; the itinerary passed the service value through as is. --- src/tools/DistanceTool.ts | 2 +- test/tools/distance.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/DistanceTool.ts b/src/tools/DistanceTool.ts index 87bcd180..d21cc849 100644 --- a/src/tools/DistanceTool.ts +++ b/src/tools/DistanceTool.ts @@ -106,7 +106,7 @@ class DistanceTool extends BaseTool { optimize: input.optimize, }); return { - distance: itinerary.distance, + distance: Math.round(itinerary.distance * 100) / 100, time: Math.round(itinerary.duration * 10) / 10 }; } diff --git a/test/tools/distance.test.ts b/test/tools/distance.test.ts index 5c53c78f..7d575155 100644 --- a/test/tools/distance.test.ts +++ b/test/tools/distance.test.ts @@ -71,11 +71,11 @@ describe("Test DistanceTool", () => { }); it.each([ - ["car", 395174, 212, 212], - ["pedestrian", 12345, 67.83, 67.8], - ] as const)("should return itinerary distance and time for %s", async (profile, distance, duration, time) => { + ["car", 395174.126, 395174.13, 212, 212], + ["pedestrian", 12345, 12345, 67.83, 67.8], + ] as const)("should return itinerary distance and time for %s", async (profile, rawDistance, distance, duration, time) => { const tool = new DistanceTool(); - const getItinerarySpy = vi.spyOn(navigationItineraryClient, "getItinerary").mockResolvedValue({ distance, duration }); + const getItinerarySpy = vi.spyOn(navigationItineraryClient, "getItinerary").mockResolvedValue({ distance: rawDistance, duration }); const response = await tool.toolCall({ params: { name: "distance", arguments: { departure, arrival, profile } }, });