mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(roadtrip): a drive bent through a via prints its whole length, and a refusal is said once
A via cuts a drive into one routed leg per waypoint pair. Folding them back onto the stop pair summed the metres and seconds but kept the first piece's printed texts and end point, and the phone reads the texts first: a drive into the next day of 3.9 mi in 14 min showed 1.9 mi in 6 min. Every choice under "Other ways" writes vias, so every choice ran into it. The pieces are now merged by one shared helper that writes the texts from the sums in the reader's unit, for a day's own run, for the drive between two days and in the server's plan; the drive between two days is asked for again when the unit changes, as a day's run already is. On the desk a refused way was said in the bar's status line and again in a toast laid over that line. The desk keeps the status line; the phone, whose bar has no room for the sentence, keeps the toast.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* ROADTRIP-ROUTERUN-001..003: a drive the router answered in pieces reads as one drive.
|
||||
*
|
||||
* A via cuts a stop-to-stop drive into one leg per waypoint pair. The numbers were summed
|
||||
* back together, but the printed texts stayed those of the first piece, and the phone reads
|
||||
* a segment's text before its numbers: a 3.9 mi drive into the next day said "1.9 mi in
|
||||
* 6 min" (#2461).
|
||||
*/
|
||||
import type { RoadtripStop, RouteSegment } from './planning-types';
|
||||
import { formatDurationShort } from './roadtripModel';
|
||||
import { foldRouteRun, mergeRouteSegments, roadtripLegKey } from './routeRun';
|
||||
import { formatDistance } from './units';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const piece = (from: [number, number], to: [number, number], distance: number, duration: number): RouteSegment => ({
|
||||
mid: [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2],
|
||||
from,
|
||||
to,
|
||||
distance,
|
||||
duration,
|
||||
distanceText: formatDistance(distance / 1000, 'imperial'),
|
||||
durationText: formatDurationShort(duration),
|
||||
drivingText: formatDurationShort(duration),
|
||||
walkingText: '',
|
||||
});
|
||||
|
||||
const A: [number, number] = [52.37, 4.89];
|
||||
const VIA: [number, number] = [52.36, 4.9];
|
||||
const B: [number, number] = [52.35, 4.87];
|
||||
|
||||
describe('mergeRouteSegments', () => {
|
||||
it('ROADTRIP-ROUTERUN-001: the pieces of one drive add up, and their texts are written from the sums', () => {
|
||||
const first = piece(A, VIA, 2996, 417);
|
||||
const second = piece(VIA, B, 3280, 423);
|
||||
|
||||
const merged = mergeRouteSegments([first, second], 'imperial');
|
||||
|
||||
expect(merged.distance).toBe(6276);
|
||||
expect(merged.duration).toBe(840);
|
||||
expect(merged.distanceText).toBe(formatDistance(6.276, 'imperial'));
|
||||
expect(merged.distanceText).not.toBe(first.distanceText);
|
||||
expect(merged.durationText).toBe('14 min');
|
||||
expect(merged.drivingText).toBe('14 min');
|
||||
// The drive ends where its last piece does, not at the via.
|
||||
expect(merged.from).toEqual(A);
|
||||
expect(merged.to).toEqual(B);
|
||||
});
|
||||
|
||||
it('ROADTRIP-ROUTERUN-002: a drive the router answered in one piece is handed on as it came', () => {
|
||||
const only = piece(A, B, 3000, 600);
|
||||
|
||||
expect(mergeRouteSegments([only], 'metric')).toBe(only);
|
||||
});
|
||||
});
|
||||
|
||||
describe('foldRouteRun', () => {
|
||||
it('ROADTRIP-ROUTERUN-003: a stop pair bent through a via gets the whole drive and its texts, in the reader unit', () => {
|
||||
const stops = [
|
||||
{ lat: A[0], lng: A[1] },
|
||||
{ lat: B[0], lng: B[1] },
|
||||
] as unknown as RoadtripStop[];
|
||||
const legs = [piece(A, VIA, 2996, 417), piece(VIA, B, 3280, 423)];
|
||||
|
||||
const folded = foldRouteRun(stops, [0, 2], { coordinates: [A, VIA, B], legs }, 'driving', 'metric');
|
||||
const seg = folded[roadtripLegKey(stops[0]!, stops[1]!)]!.seg;
|
||||
|
||||
expect(seg.distance).toBe(6276);
|
||||
expect(seg.duration).toBe(840);
|
||||
expect(seg.distanceText).toBe(formatDistance(6.276, 'metric'));
|
||||
expect(seg.durationText).toBe('14 min');
|
||||
expect(seg.mode).toBe('driving');
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,51 @@
|
||||
import { lineMetres, sliceAtMeters, projectOntoRoute } from './corridor';
|
||||
import type { RoadtripStop, RouteSegment, RouteVia, RoutedLeg } from './planning-types';
|
||||
import { legIndexForAlong } from './roadtripModel';
|
||||
import type { DistanceUnit, RoadtripStop, RouteSegment, RouteVia, RoutedLeg } from './planning-types';
|
||||
import { formatDurationShort, legIndexForAlong } from './roadtripModel';
|
||||
import { formatDistance } from './units';
|
||||
|
||||
export const roadtripStopKey = (stop: RoadtripStop): string =>
|
||||
`${stop.lat.toFixed(5)},${stop.lng.toFixed(5)},${stop.legMode ?? ''},${stop.incomingLegMode ?? ''}`;
|
||||
export const roadtripLegKey = (from: RoadtripStop, to: RoadtripStop): string =>
|
||||
`${roadtripStopKey(from)}>${roadtripStopKey(to)}`;
|
||||
|
||||
/** The walking pace a segment's walking time is printed at, in metres per second. */
|
||||
const WALKING_METRES_PER_SECOND = 5000 / 3600;
|
||||
|
||||
/**
|
||||
* The pieces a via cuts one stop-to-stop drive into, read as that one drive again.
|
||||
*
|
||||
* The router answers one leg per pair of waypoints, so a drive bent through a via comes
|
||||
* back in pieces. The numbers add up. The printed texts do not: carried over from the first
|
||||
* piece, they had the phone print "1.9 mi in 6 min" under a 3.9 mi drive, because it reads
|
||||
* a segment's text before its numbers. So the texts are written again from the sums, in the
|
||||
* reader's unit, and the drive ends where its last piece does rather than at the first via.
|
||||
*/
|
||||
export function mergeRouteSegments(parts: RouteSegment[], unit: DistanceUnit): RouteSegment {
|
||||
const first = parts[0]!;
|
||||
if (parts.length === 1) return first;
|
||||
const last = parts[parts.length - 1]!;
|
||||
const distance = parts.reduce((sum, leg) => sum + (leg.distance ?? 0), 0);
|
||||
const duration = parts.reduce((sum, leg) => sum + (leg.duration ?? 0), 0);
|
||||
const durationText = formatDurationShort(duration);
|
||||
return {
|
||||
...first,
|
||||
to: last.to,
|
||||
mid: [(first.from[0] + last.to[0]) / 2, (first.from[1] + last.to[1]) / 2],
|
||||
distance,
|
||||
duration,
|
||||
distanceText: formatDistance(distance / 1000, unit),
|
||||
durationText,
|
||||
drivingText: durationText,
|
||||
walkingText: formatDurationShort(distance / WALKING_METRES_PER_SECOND),
|
||||
};
|
||||
}
|
||||
|
||||
export function foldRouteRun(
|
||||
stops: RoadtripStop[],
|
||||
stopAt: number[],
|
||||
route: { coordinates: [number, number][]; legs: RouteSegment[]; vias?: RouteVia[] },
|
||||
mode: string,
|
||||
unit: DistanceUnit,
|
||||
): Record<string, RoutedLeg> {
|
||||
const spine = route.coordinates.map(([lat, lng]) => ({ lat, lng }));
|
||||
const cuts: { key: string; from: number; to: number; seg: RouteSegment }[] = [];
|
||||
@@ -19,14 +53,7 @@ export function foldRouteRun(
|
||||
for (let index = 0; index < stops.length - 1; index++) {
|
||||
const parts = route.legs.slice(stopAt[index], stopAt[index + 1]);
|
||||
if (!parts.length) continue;
|
||||
const merged =
|
||||
parts.length === 1
|
||||
? parts[0]!
|
||||
: {
|
||||
...parts[0]!,
|
||||
distance: parts.reduce((sum, leg) => sum + leg.distance, 0),
|
||||
duration: parts.reduce((sum, leg) => sum + leg.duration, 0),
|
||||
};
|
||||
const merged = mergeRouteSegments(parts, unit);
|
||||
const from = travelled;
|
||||
travelled += merged.distance;
|
||||
cuts.push({ key: roadtripLegKey(stops[index]!, stops[index + 1]!), from, to: travelled, seg: { ...merged, mode } });
|
||||
|
||||
Reference in New Issue
Block a user