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)