From 369697650f8405709599fbeaa7d0f89581de88ab Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 14:38:29 +0200 Subject: [PATCH] refactor(map): build OSRM leg connectors in one place calculateSegments and the cached legs call turned each OSRM leg into the same connector record line by line. That record now comes from osrmLegSegment; the legs call keeps adding its durationText on top, so both answers stay exactly as they were. --- .../components/Map/RouteCalculator.test.ts | 26 +++++++++ client/src/components/Map/RouteCalculator.ts | 57 +++++++++---------- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/client/src/components/Map/RouteCalculator.test.ts b/client/src/components/Map/RouteCalculator.test.ts index 40752bc36..5392c8f3f 100644 --- a/client/src/components/Map/RouteCalculator.test.ts +++ b/client/src/components/Map/RouteCalculator.test.ts @@ -17,6 +17,7 @@ import { routeEngineFor, sameRoad, furthestFrom, + osrmLegSegment, } from './RouteCalculator' // Every route now goes to the FOSSGIS per-profile hosts. The car-only project-osrm.org @@ -1246,3 +1247,28 @@ describe('a trip that avoids something', () => { expect(routes[1]).toMatchObject({ avoids: 'motorway', engine: 'valhalla', hasFerry: true }) }) }) + +describe('osrmLegSegment', () => { + const waypoints = [{ lat: 48, lng: 2 }, { lat: 50, lng: 4 }, { lat: 52, lng: 6 }] + + it('spans waypoint i to i + 1 with the label at the midpoint', () => { + const seg = osrmLegSegment({ distance: 1500, duration: 600 }, 1, waypoints) + expect(seg.from).toEqual([50, 4]) + expect(seg.to).toEqual([52, 6]) + expect(seg.mid).toEqual([51, 5]) + expect(seg.distance).toBe(1500) + expect(seg.duration).toBe(600) + }) + + it('formats driving as measured and walking at 5 km/h', () => { + const seg = osrmLegSegment({ distance: 5000, duration: 600 }, 0, waypoints) + expect(seg.drivingText).toBe('10 min') + expect(seg.walkingText).toBe('1 h 0 min') + expect(seg.durationText).toBeUndefined() + }) + + it('writes short metric distances in metres', () => { + useSettingsStore.setState({ settings: { ...useSettingsStore.getState().settings, distance_unit: 'metric' } }) + expect(osrmLegSegment({ distance: 420.4, duration: 60 }, 0, waypoints).distanceText).toBe('420 m') + }) +}) diff --git a/client/src/components/Map/RouteCalculator.ts b/client/src/components/Map/RouteCalculator.ts index e8eb59bbe..85787a7b4 100644 --- a/client/src/components/Map/RouteCalculator.ts +++ b/client/src/components/Map/RouteCalculator.ts @@ -470,20 +470,29 @@ export async function calculateSegments( if (data.code !== 'Ok' || !data.routes?.[0]) throw new Error('No route found') const legs = data.routes[0].legs - return legs.map((leg: { distance: number; duration: number }, i: number): RouteSegment => { - const from: [number, number] = [waypoints[i].lat, waypoints[i].lng] - const to: [number, number] = [waypoints[i + 1].lat, waypoints[i + 1].lng] - const mid: [number, number] = [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2] - const walkingDuration = leg.distance / (5000 / 3600) - return { - mid, from, to, - distance: leg.distance, - duration: leg.duration, - walkingText: formatDuration(walkingDuration), - drivingText: formatDuration(leg.duration), - distanceText: formatRouteDistance(leg.distance), - } - }) + return legs.map((leg: OsrmLeg, i: number): RouteSegment => osrmLegSegment(leg, i, waypoints)) +} + +interface OsrmLeg { distance: number; duration: number } + +/** + * The connector data for OSRM leg i, which runs from waypoint i to waypoint i + 1: + * both ends, the midpoint the label sits on, and the walking time at 5 km/h next to + * the driving time OSRM measured. + */ +export function osrmLegSegment(leg: OsrmLeg, i: number, waypoints: Waypoint[]): RouteSegment { + const from: [number, number] = [waypoints[i].lat, waypoints[i].lng] + const to: [number, number] = [waypoints[i + 1].lat, waypoints[i + 1].lng] + const mid: [number, number] = [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2] + const walkingDuration = leg.distance / (5000 / 3600) + return { + mid, from, to, + distance: leg.distance, + duration: leg.duration, + walkingText: formatDuration(walkingDuration), + drivingText: formatDuration(leg.duration), + distanceText: formatRouteDistance(leg.distance), + } } /** @@ -617,22 +626,12 @@ async function osrmWithLegs( const coordinates: [number, number][] = route.geometry.coordinates.map( ([lng, lat]: [number, number]) => [lat, lng] ) + // The day plan's connectors also show the leg's own duration line. const legs: RouteSegment[] = (route.legs || []).map( - (leg: { distance: number; duration: number }, i: number): RouteSegment => { - const from: [number, number] = [waypoints[i].lat, waypoints[i].lng] - const to: [number, number] = [waypoints[i + 1].lat, waypoints[i + 1].lng] - const mid: [number, number] = [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2] - const walkingDuration = leg.distance / (5000 / 3600) - return { - mid, from, to, - distance: leg.distance, - duration: leg.duration, - walkingText: formatDuration(walkingDuration), - drivingText: formatDuration(leg.duration), - distanceText: formatRouteDistance(leg.distance), - durationText: formatDuration(leg.duration), - } - } + (leg: OsrmLeg, i: number): RouteSegment => ({ + ...osrmLegSegment(leg, i, waypoints), + durationText: formatDuration(leg.duration), + }) ) const snapped = readSnapped(data, waypoints)