mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
refactor(map): the road trip via helpers move to utils, so the route hook imports no component
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { buildAssignment, buildPlace } from '../../../tests/helpers/factories';
|
||||
import type { AssignmentsMap, RouteSegment } from '../../types';
|
||||
import { dayViaLookup } from '../../utils/dayRouteVias';
|
||||
import { routeLeg } from './dayRouteLeg';
|
||||
|
||||
vi.mock('./RouteCalculator', async (importActual) => {
|
||||
const actual = await importActual<typeof import('./RouteCalculator')>();
|
||||
return { ...actual, calculateRouteWithLegs: vi.fn() };
|
||||
});
|
||||
|
||||
const { calculateRouteWithLegs } = await import('./RouteCalculator');
|
||||
|
||||
const leg = (distance: number): RouteSegment => ({
|
||||
mid: [0, 0],
|
||||
from: [distance, 0],
|
||||
to: [distance + 1, 0],
|
||||
distance,
|
||||
duration: distance / 10,
|
||||
distanceText: '',
|
||||
durationText: '',
|
||||
walkingText: '',
|
||||
drivingText: '',
|
||||
});
|
||||
|
||||
const via = (dayId: number, afterOrderIndex: number, lat: number, sequence = 0): RoadtripVia =>
|
||||
({
|
||||
id: lat,
|
||||
day_id: dayId,
|
||||
after_order_index: afterOrderIndex,
|
||||
sequence,
|
||||
lat,
|
||||
lng: 0,
|
||||
created_at: '',
|
||||
}) as RoadtripVia;
|
||||
|
||||
const stop = (id: number, order: number, extra: Record<string, unknown> = {}) =>
|
||||
buildAssignment({ id, day_id: 1, order_index: order, place: buildPlace({ lat: 48 + order, lng: 2 }), ...extra });
|
||||
|
||||
// Stored order: hotel row, Paris, a place without coordinates, Lille, Brussels.
|
||||
const STORED: AssignmentsMap = {
|
||||
'1': [
|
||||
stop(14, 3),
|
||||
stop(11, 1),
|
||||
stop(10, 0, { accommodation_id: 5 }),
|
||||
buildAssignment({
|
||||
id: 12,
|
||||
day_id: 1,
|
||||
order_index: 2,
|
||||
place: buildPlace({ lat: null as unknown as number, lng: null as unknown as number }),
|
||||
}),
|
||||
stop(13, 2.5),
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(calculateRouteWithLegs).mockReset();
|
||||
});
|
||||
|
||||
describe('dayRouteLeg (#2580)', () => {
|
||||
it('FE-MAP-DRV-009: one leg is routed through its vias and read back as one leg in its mode', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValue({
|
||||
coordinates: [],
|
||||
distance: 0,
|
||||
duration: 0,
|
||||
legs: [leg(1000), leg(2000)],
|
||||
});
|
||||
const lookup = dayViaLookup(STORED, { 1: [via(1, 1, 49.3)] });
|
||||
const options = { profile: 'driving', tripId: 7, dayId: 1 };
|
||||
|
||||
const seg = await routeLeg(
|
||||
{ lat: 49, lng: 2, assignmentId: 11 },
|
||||
{ lat: 50.5, lng: 2, assignmentId: 13 },
|
||||
lookup,
|
||||
options,
|
||||
'metric'
|
||||
);
|
||||
|
||||
expect(vi.mocked(calculateRouteWithLegs).mock.calls[0][0]).toEqual([
|
||||
{ lat: 49, lng: 2 },
|
||||
{ lat: 49.3, lng: 0 },
|
||||
{ lat: 50.5, lng: 2 },
|
||||
]);
|
||||
expect(seg?.distance).toBe(3000);
|
||||
expect(seg?.mode).toBe('driving');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-010: a leg the router cannot answer is undefined', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockRejectedValueOnce(new Error('down'));
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValueOnce({ coordinates: [], distance: 0, duration: 0, legs: [] });
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { DistanceUnit, RouteSegment } from '../../types';
|
||||
import { foldViaLegs, viasOnDayLeg, type DayViaLookup } from '../../utils/dayRouteVias';
|
||||
import { calculateRouteWithLegs } from './RouteCalculator';
|
||||
|
||||
/** One end of a leg: a planned stop carries its assignment, which the vias are filed against. */
|
||||
export type LegEnd = { lat: number; lng: number; assignmentId?: number };
|
||||
|
||||
/**
|
||||
* The drive between two points of a day as one leg, bent through the road trip's vias
|
||||
* between them and tagged with the mode it was routed in. Undefined when the router has
|
||||
* no road for it.
|
||||
*/
|
||||
export async function routeLeg(
|
||||
from: LegEnd,
|
||||
to: LegEnd,
|
||||
lookup: DayViaLookup | null,
|
||||
options: Parameters<typeof calculateRouteWithLegs>[1] & { profile: string; dayId: number },
|
||||
unit: DistanceUnit | undefined
|
||||
): Promise<RouteSegment | undefined> {
|
||||
const vias = viasOnDayLeg(lookup, options.dayId, options.profile, from, to);
|
||||
try {
|
||||
const waypoints = [from, ...vias, to].map((p) => ({ lat: p.lat, lng: p.lng }));
|
||||
const r = await calculateRouteWithLegs(waypoints, options);
|
||||
const leg = foldViaLegs(r.legs, [0, vias.length + 1], unit)[0];
|
||||
return leg ? { ...leg, mode: options.profile } : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { buildAssignment, buildPlace } from '../../../tests/helpers/factories';
|
||||
import type { AssignmentsMap, RouteSegment } from '../../types';
|
||||
import {
|
||||
dayViaLookup,
|
||||
foldViaLegs,
|
||||
roadtripStopList,
|
||||
routeLeg,
|
||||
threadVias,
|
||||
viaKeyOf,
|
||||
viasOnDayLeg,
|
||||
} from './dayRouteVias';
|
||||
|
||||
vi.mock('./RouteCalculator', async (importActual) => {
|
||||
const actual = await importActual<typeof import('./RouteCalculator')>();
|
||||
return { ...actual, calculateRouteWithLegs: vi.fn() };
|
||||
});
|
||||
|
||||
const { calculateRouteWithLegs } = await import('./RouteCalculator');
|
||||
|
||||
const leg = (distance: number): RouteSegment => ({
|
||||
mid: [0, 0],
|
||||
from: [distance, 0],
|
||||
to: [distance + 1, 0],
|
||||
distance,
|
||||
duration: distance / 10,
|
||||
distanceText: '',
|
||||
durationText: '',
|
||||
walkingText: '',
|
||||
drivingText: '',
|
||||
});
|
||||
|
||||
const via = (dayId: number, afterOrderIndex: number, lat: number, sequence = 0): RoadtripVia =>
|
||||
({
|
||||
id: lat,
|
||||
day_id: dayId,
|
||||
after_order_index: afterOrderIndex,
|
||||
sequence,
|
||||
lat,
|
||||
lng: 0,
|
||||
created_at: '',
|
||||
}) as RoadtripVia;
|
||||
|
||||
const stop = (id: number, order: number, extra: Record<string, unknown> = {}) =>
|
||||
buildAssignment({ id, day_id: 1, order_index: order, place: buildPlace({ lat: 48 + order, lng: 2 }), ...extra });
|
||||
|
||||
// Stored order: hotel row, Paris, a place without coordinates, Lille, Brussels.
|
||||
const STORED: AssignmentsMap = {
|
||||
'1': [
|
||||
stop(14, 3),
|
||||
stop(11, 1),
|
||||
stop(10, 0, { accommodation_id: 5 }),
|
||||
buildAssignment({
|
||||
id: 12,
|
||||
day_id: 1,
|
||||
order_index: 2,
|
||||
place: buildPlace({ lat: null as unknown as number, lng: null as unknown as number }),
|
||||
}),
|
||||
stop(13, 2.5),
|
||||
],
|
||||
};
|
||||
|
||||
/** The lookup of a trip that has vias, which is never null. */
|
||||
function lookupFor(viasByDay: Record<number, RoadtripVia[]>) {
|
||||
const lookup = dayViaLookup(STORED, viasByDay);
|
||||
if (!lookup) throw new Error('expected a lookup');
|
||||
return lookup;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(calculateRouteWithLegs).mockReset();
|
||||
});
|
||||
|
||||
describe('dayRouteVias (#2580)', () => {
|
||||
it('FE-MAP-DRV-001: the stop list is the stored order with coordinates, hidden rows included', () => {
|
||||
expect(roadtripStopList(STORED['1']).map((a) => a.id)).toEqual([10, 11, 13, 14]);
|
||||
expect(roadtripStopList(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-002: a leg gets the vias filed behind its first stop, in sequence', () => {
|
||||
const lookup = lookupFor({ 1: [via(1, 1, 49.6, 1), via(1, 1, 49.3, 0), via(1, 2, 50.5)] });
|
||||
expect(lookup(1, 11, 13).map((v) => v.lat)).toEqual([49.3, 49.6]);
|
||||
expect(lookup(1, 13, 14).map((v) => v.lat)).toEqual([50.5]);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-003: two stops that do not follow each other in the stored list get none', () => {
|
||||
const lookup = lookupFor({ 1: [via(1, 1, 49.3)] });
|
||||
// Paris to Brussels skips Lille: the via belongs to the leg to Lille.
|
||||
expect(lookup(1, 11, 14)).toEqual([]);
|
||||
expect(lookup(1, 13, 11)).toEqual([]);
|
||||
expect(lookup(1, 999, 13)).toEqual([]);
|
||||
expect(lookup(2, 11, 13)).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-004: a trip without vias routes as before', () => {
|
||||
expect(dayViaLookup(STORED, undefined)).toBeNull();
|
||||
expect(dayViaLookup(STORED, {})).toBeNull();
|
||||
expect(dayViaLookup(STORED, { 1: [] })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-005: a ride, a hotel bookend or a missing lookup bends nothing', () => {
|
||||
const lookup = dayViaLookup(STORED, { 1: [via(1, 1, 49.3)] });
|
||||
expect(viasOnDayLeg(lookup, 1, 'driving', { assignmentId: 11 }, { assignmentId: 13 })).toHaveLength(1);
|
||||
expect(viasOnDayLeg(lookup, 1, 'walking', { assignmentId: 11 }, { assignmentId: 13 })).toHaveLength(1);
|
||||
expect(viasOnDayLeg(lookup, 1, 'train', { assignmentId: 11 }, { assignmentId: 13 })).toEqual([]);
|
||||
expect(viasOnDayLeg(lookup, 1, 'driving', { assignmentId: 11 }, {})).toEqual([]);
|
||||
expect(viasOnDayLeg(null, 1, 'driving', { assignmentId: 11 }, { assignmentId: 13 })).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-006: threading remembers where each stop went, folding gives one leg per stop pair', () => {
|
||||
const points = [
|
||||
{ lat: 1, lng: 0, k: 'a' },
|
||||
{ lat: 2, lng: 0, k: 'b' },
|
||||
{ lat: 3, lng: 0, k: 'c' },
|
||||
];
|
||||
const { waypoints, stopAt } = threadVias(points, (from) =>
|
||||
from.k === 'a'
|
||||
? [
|
||||
{ lat: 1.5, lng: 0 },
|
||||
{ lat: 1.7, lng: 0 },
|
||||
]
|
||||
: []
|
||||
);
|
||||
expect(waypoints.map((w) => w.lat)).toEqual([1, 1.5, 1.7, 2, 3]);
|
||||
expect(stopAt).toEqual([0, 3, 4]);
|
||||
|
||||
const folded = foldViaLegs([leg(1000), leg(2000), leg(3000), leg(4000)], stopAt, 'imperial');
|
||||
expect(folded.map((l) => l.distance)).toEqual([6000, 4000]);
|
||||
expect(folded[0].from).toEqual([1000, 0]);
|
||||
expect(folded[0].to).toEqual([3001, 0]);
|
||||
expect(folded[0].distanceText).toMatch(/mi/);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-007: legs are left alone when nothing was threaded or the answer does not line up', () => {
|
||||
const legs = [leg(1000), leg(2000)];
|
||||
expect(foldViaLegs(legs, [0, 1, 2], 'metric')).toBe(legs);
|
||||
expect(foldViaLegs(legs, [0, 2, 3], 'metric')).toBe(legs);
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-008: the via key changes with the road and not with the order of the list', () => {
|
||||
const a = { 1: [via(1, 0, 49.3)], 2: [via(2, 0, 50.1)] };
|
||||
expect(viaKeyOf({ 2: a[2], 1: a[1] })).toBe(viaKeyOf(a));
|
||||
expect(viaKeyOf({ ...a, 1: [via(1, 0, 49.4)] })).not.toBe(viaKeyOf(a));
|
||||
expect(viaKeyOf(undefined)).toBe('');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-009: one leg is routed through its vias and read back as one leg in its mode', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValue({
|
||||
coordinates: [],
|
||||
distance: 0,
|
||||
duration: 0,
|
||||
legs: [leg(1000), leg(2000)],
|
||||
});
|
||||
const lookup = dayViaLookup(STORED, { 1: [via(1, 1, 49.3)] });
|
||||
const options = { profile: 'driving', tripId: 7, dayId: 1 };
|
||||
|
||||
const seg = await routeLeg(
|
||||
{ lat: 49, lng: 2, assignmentId: 11 },
|
||||
{ lat: 50.5, lng: 2, assignmentId: 13 },
|
||||
lookup,
|
||||
options,
|
||||
'metric'
|
||||
);
|
||||
|
||||
expect(vi.mocked(calculateRouteWithLegs).mock.calls[0][0]).toEqual([
|
||||
{ lat: 49, lng: 2 },
|
||||
{ lat: 49.3, lng: 0 },
|
||||
{ lat: 50.5, lng: 2 },
|
||||
]);
|
||||
expect(seg?.distance).toBe(3000);
|
||||
expect(seg?.mode).toBe('driving');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-010: a leg the router cannot answer is undefined', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockRejectedValueOnce(new Error('down'));
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValueOnce({ coordinates: [], distance: 0, duration: 0, legs: [] });
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,144 +0,0 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { isCarrierMode, mergeRouteSegments, viasOnLeg } from '@trek/shared/roadtrip';
|
||||
import type { Assignment, AssignmentsMap, DistanceUnit, RouteSegment } from '../../types';
|
||||
import { calculateRouteWithLegs } from './RouteCalculator';
|
||||
|
||||
/**
|
||||
* The stops of a day as the road trip counts them, in the order it drives them.
|
||||
*
|
||||
* This is the index space a via's `after_order_index` lives in: the STORED list, sorted
|
||||
* by `order_index` and filtered to the rows with coordinates. The stop a lodging booking
|
||||
* put on the day and the service stops are counted too, even where the day list hides
|
||||
* them, because the rail and the server count them.
|
||||
*/
|
||||
export function roadtripStopList(dayAssignments: readonly Assignment[] | undefined): Assignment[] {
|
||||
return (dayAssignments ?? [])
|
||||
.slice()
|
||||
.sort((a, b) => (a.order_index ?? 0) - (b.order_index ?? 0))
|
||||
.filter((a) => typeof a.place?.lat === 'number' && typeof a.place?.lng === 'number');
|
||||
}
|
||||
|
||||
/** The trip's vias, keyed by the day they are filed against. */
|
||||
export type ViasByDay = Record<number, readonly RoadtripVia[]>;
|
||||
|
||||
/** The vias shaping the drive between two assignments of a day, in driving order. */
|
||||
export type DayViaLookup = (dayId: number, fromAssignmentId: number, toAssignmentId: number) => readonly RoadtripVia[];
|
||||
|
||||
const NONE: readonly RoadtripVia[] = [];
|
||||
|
||||
/**
|
||||
* The road a traveller picked in the road trip, for the views that draw a day without it.
|
||||
*
|
||||
* A chosen alternative or a hand-bent drive is stored as vias filed against the stop
|
||||
* they leave, so the day view asks for the same points between the same two stops. Only
|
||||
* between two stops that follow each other in the stored list: with a stop the day list
|
||||
* hides in between, the vias belong to a leg the day view does not draw, and bending the
|
||||
* shortcut through them would be a road nobody chose.
|
||||
*
|
||||
* Null when the trip has no vias, so a trip without a road trip routes exactly as before.
|
||||
*/
|
||||
export function dayViaLookup(stored: AssignmentsMap, viasByDay: ViasByDay | undefined): DayViaLookup | null {
|
||||
if (!viasByDay || !Object.values(viasByDay).some((list) => list.length)) return null;
|
||||
const indexByDay = new Map<number, Map<number, number>>();
|
||||
return (dayId, fromAssignmentId, toAssignmentId) => {
|
||||
const vias = viasByDay[dayId];
|
||||
if (!vias?.length) return NONE;
|
||||
let index = indexByDay.get(dayId);
|
||||
if (!index) {
|
||||
index = new Map(roadtripStopList(stored[String(dayId)]).map((a, i) => [a.id, i]));
|
||||
indexByDay.set(dayId, index);
|
||||
}
|
||||
const from = index.get(fromAssignmentId);
|
||||
if (from === undefined || index.get(toAssignmentId) !== from + 1) return NONE;
|
||||
return viasOnLeg({ ownerDayId: dayId, ownerIndex: from }, {}, vias);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The vias of one leg of a drawn day: none unless both ends are planned stops, and none
|
||||
* on a ride, which is no road.
|
||||
*/
|
||||
export function viasOnDayLeg(
|
||||
lookup: DayViaLookup | null,
|
||||
dayId: number,
|
||||
mode: string,
|
||||
from: { assignmentId?: number },
|
||||
to: { assignmentId?: number }
|
||||
): readonly RoadtripVia[] {
|
||||
if (!lookup || isCarrierMode(mode) || from.assignmentId == null || to.assignmentId == null) return NONE;
|
||||
return lookup(dayId, from.assignmentId, to.assignmentId);
|
||||
}
|
||||
|
||||
/** A stable key for the via list, so a routing effect re-runs when the road changes and only then. */
|
||||
export function viaKeyOf(viasByDay: ViasByDay | undefined): string {
|
||||
return Object.entries(viasByDay ?? {})
|
||||
.map(
|
||||
([dayId, vias]) =>
|
||||
`${dayId}:${vias.map((v) => `${v.after_order_index}@${v.lat.toFixed(5)},${v.lng.toFixed(5)}`).join('|')}`
|
||||
)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.join(';');
|
||||
}
|
||||
|
||||
/**
|
||||
* The waypoints to ask the router for: the stops with the vias of each leg threaded in
|
||||
* between. `stopAt[i]` is the waypoint the i-th stop became, which is how the answer's
|
||||
* legs (one per waypoint pair) are folded back to one per stop pair.
|
||||
*/
|
||||
export function threadVias<P extends { lat: number; lng: number }>(
|
||||
points: readonly P[],
|
||||
viasBetween: (from: P, to: P) => readonly { lat: number; lng: number }[]
|
||||
): { waypoints: { lat: number; lng: number }[]; stopAt: number[] } {
|
||||
const waypoints: { lat: number; lng: number }[] = [];
|
||||
const stopAt: number[] = [];
|
||||
points.forEach((point, i) => {
|
||||
stopAt.push(waypoints.length);
|
||||
waypoints.push({ lat: point.lat, lng: point.lng });
|
||||
if (i < points.length - 1)
|
||||
for (const via of viasBetween(point, points[i + 1])) waypoints.push({ lat: via.lat, lng: via.lng });
|
||||
});
|
||||
return { waypoints, stopAt };
|
||||
}
|
||||
|
||||
/**
|
||||
* The router's legs read as one leg per stop pair again. Untouched when nothing was
|
||||
* threaded, or when the answer does not have a leg per waypoint pair (a plugin route may
|
||||
* not), since the pieces could not be told apart then.
|
||||
*/
|
||||
export function foldViaLegs(legs: RouteSegment[], stopAt: number[], unit: DistanceUnit | undefined): RouteSegment[] {
|
||||
const waypointCount = stopAt.length ? stopAt[stopAt.length - 1] + 1 : 0;
|
||||
if (waypointCount === stopAt.length || legs.length !== waypointCount - 1) return legs;
|
||||
const folded: RouteSegment[] = [];
|
||||
for (let i = 0; i < stopAt.length - 1; i++) {
|
||||
const parts = legs.slice(stopAt[i], stopAt[i + 1]);
|
||||
if (parts.length)
|
||||
folded.push(mergeRouteSegments(parts, unit === 'imperial' ? 'imperial' : 'metric') as RouteSegment);
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
/** One end of a leg: a planned stop carries its assignment, which the vias are filed against. */
|
||||
export type LegEnd = { lat: number; lng: number; assignmentId?: number };
|
||||
|
||||
/**
|
||||
* The drive between two points of a day as one leg, bent through the road trip's vias
|
||||
* between them and tagged with the mode it was routed in. Undefined when the router has
|
||||
* no road for it.
|
||||
*/
|
||||
export async function routeLeg(
|
||||
from: LegEnd,
|
||||
to: LegEnd,
|
||||
lookup: DayViaLookup | null,
|
||||
options: Parameters<typeof calculateRouteWithLegs>[1] & { profile: string; dayId: number },
|
||||
unit: DistanceUnit | undefined
|
||||
): Promise<RouteSegment | undefined> {
|
||||
const vias = viasOnDayLeg(lookup, options.dayId, options.profile, from, to);
|
||||
try {
|
||||
const waypoints = [from, ...vias, to].map((p) => ({ lat: p.lat, lng: p.lng }));
|
||||
const r = await calculateRouteWithLegs(waypoints, options);
|
||||
const leg = foldViaLegs(r.legs, [0, vias.length + 1], unit)[0];
|
||||
return leg ? { ...leg, mode: options.profile } : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { calculateRouteWithLegs, RoutingRefusedError, type RouteProfileKey } from './RouteCalculator'
|
||||
import { buildDayRouteRuns, type DayRouteInputs, type DayRoutePoint } from './dayRoutePlan'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viasOnDayLeg, type DayViaLookup } from './dayRouteVias'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viasOnDayLeg, type DayViaLookup } from '../../utils/dayRouteVias'
|
||||
import { projectDayItinerary } from './dayTourProjection'
|
||||
import { resolveLegMode } from '../Planner/legMode'
|
||||
import { dayColor } from '../Roadtrip/dayColors'
|
||||
|
||||
Reference in New Issue
Block a user