minotor
Version:
A lightweight client-side transit routing library.
112 lines (102 loc) • 3.88 kB
text/typescript
import { SourceStopId, StopId } from '../stops/stops.js';
import { StopsIndex } from '../stops/stopsIndex.js';
import { Query } from './query.js';
import { Leg, Route } from './route.js';
import { ReachingTime, TripLeg } from './router.js';
export class Result {
private readonly query: Query;
public readonly earliestArrivals: Map<StopId, ReachingTime>;
public readonly earliestArrivalsPerRound: Map<StopId, TripLeg>[];
private readonly stopsIndex: StopsIndex;
constructor(
query: Query,
earliestArrivals: Map<StopId, ReachingTime>,
earliestArrivalsPerRound: Map<StopId, TripLeg>[],
stopsIndex: StopsIndex,
) {
this.query = query;
this.earliestArrivals = earliestArrivals;
this.earliestArrivalsPerRound = earliestArrivalsPerRound;
this.stopsIndex = stopsIndex;
}
/**
* Reconstructs the best route to a stop.
* (to any stop reachable in less time / transfers than the destination(s) of the query)
*
* @param to The destination stop. Defaults to the destination of the original query.
* @returns a route to the destination stop if it exists.
*/
bestRoute(to?: SourceStopId | SourceStopId[]): Route | undefined {
const destinationList = Array.isArray(to) ? to : to ? [to] : this.query.to;
const destinations = destinationList.flatMap((destination) =>
this.stopsIndex.equivalentStops(destination),
);
let fastestDestination: StopId | undefined = undefined;
let fastestTime: ReachingTime | undefined = undefined;
for (const destination of destinations) {
const arrivalTime = this.earliestArrivals.get(destination.id);
if (arrivalTime !== undefined) {
if (
fastestTime === undefined ||
arrivalTime.time.toMinutes() < fastestTime.time.toMinutes()
) {
fastestDestination = destination.id;
fastestTime = arrivalTime;
}
}
}
if (!fastestDestination || !fastestTime) {
return undefined;
}
const route: Leg[] = [];
let currentStop = fastestDestination;
let round = fastestTime.legNumber;
while (fastestTime.origin !== currentStop) {
const tripLeg = this.earliestArrivalsPerRound[round]?.get(currentStop);
if (!tripLeg?.leg) {
throw new Error(
`No leg found for a trip leg: start stop=${
tripLeg?.leg?.from.id ?? 'unknown'
}, end stop=${currentStop}, round=${round}, origin=${fastestTime.origin}`,
);
}
route.unshift(tripLeg.leg);
currentStop = tripLeg.leg.from.id;
if ('route' in tripLeg.leg) {
round -= 1;
}
}
return new Route(route);
}
/**
* Returns the arrival time at any stop reachable in less time / transfers than the destination(s) of the query)
*
* @param stop The target stop for which to return the arrival time.
* @param maxTransfers The optional maximum number of transfers allowed.
* @returns The arrival time if the target stop is reachable, otherwise undefined.
*/
arrivalAt(
stop: SourceStopId,
maxTransfers?: number,
): ReachingTime | undefined {
const equivalentStops = this.stopsIndex.equivalentStops(stop);
let earliestArrival: ReachingTime | undefined = undefined;
const relevantArrivals =
maxTransfers !== undefined
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.earliestArrivalsPerRound[maxTransfers - 1]!
: this.earliestArrivals;
for (const equivalentStop of equivalentStops) {
const arrivalTime = relevantArrivals.get(equivalentStop.id);
if (arrivalTime !== undefined) {
if (
earliestArrival === undefined ||
arrivalTime.time.toMinutes() < earliestArrival.time.toMinutes()
) {
earliestArrival = arrivalTime;
}
}
}
return earliestArrival;
}
}