minotor 11.1.2 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cspell.json +7 -1
- package/CHANGELOG.md +3 -3
- package/README.md +111 -86
- package/dist/cli/perf.d.ts +57 -18
- package/dist/cli.mjs +1371 -342
- package/dist/cli.mjs.map +1 -1
- package/dist/parser.cjs.js +57 -4
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +57 -4
- package/dist/parser.esm.js.map +1 -1
- package/dist/router.cjs.js +1 -1
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +5 -5
- package/dist/router.esm.js +1 -1
- package/dist/router.esm.js.map +1 -1
- package/dist/router.umd.js +1 -1
- package/dist/router.umd.js.map +1 -1
- package/dist/routing/__tests__/access.test.d.ts +1 -0
- package/dist/routing/__tests__/plainRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeResult.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeState.test.d.ts +1 -0
- package/dist/routing/__tests__/raptor.test.d.ts +1 -0
- package/dist/routing/__tests__/state.test.d.ts +1 -0
- package/dist/routing/access.d.ts +55 -0
- package/dist/routing/plainRouter.d.ts +21 -0
- package/dist/routing/plotter.d.ts +9 -0
- package/dist/routing/query.d.ts +132 -13
- package/dist/routing/rangeResult.d.ts +155 -0
- package/dist/routing/rangeRouter.d.ts +24 -0
- package/dist/routing/rangeState.d.ts +83 -0
- package/dist/routing/raptor.d.ts +96 -0
- package/dist/routing/result.d.ts +27 -7
- package/dist/routing/route.d.ts +5 -21
- package/dist/routing/router.d.ts +20 -91
- package/dist/routing/state.d.ts +92 -17
- package/dist/timetable/route.d.ts +8 -0
- package/dist/timetable/timetable.d.ts +17 -1
- package/package.json +1 -1
- package/src/__e2e__/benchmark.json +18 -0
- package/src/__e2e__/router.test.ts +461 -127
- package/src/cli/minotor.ts +39 -3
- package/src/cli/perf.ts +324 -60
- package/src/cli/repl.ts +96 -41
- package/src/router.ts +11 -3
- package/src/routing/__tests__/access.test.ts +294 -0
- package/src/routing/__tests__/plainRouter.test.ts +1633 -0
- package/src/routing/__tests__/plotter.test.ts +8 -8
- package/src/routing/__tests__/rangeResult.test.ts +273 -0
- package/src/routing/__tests__/rangeRouter.test.ts +472 -0
- package/src/routing/__tests__/rangeState.test.ts +246 -0
- package/src/routing/__tests__/raptor.test.ts +366 -0
- package/src/routing/__tests__/result.test.ts +27 -27
- package/src/routing/__tests__/route.test.ts +28 -0
- package/src/routing/__tests__/router.test.ts +75 -1587
- package/src/routing/__tests__/state.test.ts +78 -0
- package/src/routing/access.ts +144 -0
- package/src/routing/plainRouter.ts +60 -0
- package/src/routing/plotter.ts +53 -6
- package/src/routing/query.ts +116 -13
- package/src/routing/rangeResult.ts +292 -0
- package/src/routing/rangeRouter.ts +167 -0
- package/src/routing/rangeState.ts +150 -0
- package/src/routing/raptor.ts +416 -0
- package/src/routing/result.ts +68 -26
- package/src/routing/route.ts +15 -53
- package/src/routing/router.ts +40 -480
- package/src/routing/state.ts +191 -32
- package/src/timetable/__tests__/timetable.test.ts +373 -0
- package/src/timetable/route.ts +16 -4
- package/src/timetable/timetable.ts +54 -1
package/src/routing/router.ts
CHANGED
|
@@ -1,30 +1,17 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
-
import { StopId } from '../stops/stops.js';
|
|
3
1
|
import { StopsIndex } from '../stops/stopsIndex.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '
|
|
10
|
-
import {
|
|
11
|
-
Duration,
|
|
12
|
-
DURATION_ZERO,
|
|
13
|
-
Time,
|
|
14
|
-
TIME_ORIGIN,
|
|
15
|
-
} from '../timetable/time.js';
|
|
16
|
-
import { Timetable, TripStop } from '../timetable/timetable.js';
|
|
17
|
-
import { Query, QueryOptions } from './query.js';
|
|
2
|
+
import { Timetable } from '../timetable/timetable.js';
|
|
3
|
+
import { AccessFinder } from './access.js';
|
|
4
|
+
import { PlainRouter } from './plainRouter.js';
|
|
5
|
+
import { Query, RangeQuery } from './query.js';
|
|
6
|
+
import { RangeResult } from './rangeResult.js';
|
|
7
|
+
import { RangeRouter } from './rangeRouter.js';
|
|
8
|
+
import { Raptor } from './raptor.js';
|
|
18
9
|
import { Result } from './result.js';
|
|
19
|
-
import {
|
|
20
|
-
RoutingEdge,
|
|
21
|
-
RoutingState,
|
|
22
|
-
TransferEdge,
|
|
23
|
-
UNREACHED_TIME,
|
|
24
|
-
VehicleEdge,
|
|
25
|
-
} from './state.js';
|
|
26
10
|
|
|
11
|
+
export type { ArrivalWithDuration, ParetoRun } from './rangeResult.js';
|
|
12
|
+
export { RangeResult } from './rangeResult.js';
|
|
27
13
|
export type {
|
|
14
|
+
AccessEdge,
|
|
28
15
|
Arrival,
|
|
29
16
|
OriginNode,
|
|
30
17
|
RoutingEdge,
|
|
@@ -33,477 +20,50 @@ export type {
|
|
|
33
20
|
} from './state.js';
|
|
34
21
|
export { RoutingState, UNREACHED_TIME } from './state.js';
|
|
35
22
|
|
|
36
|
-
type TripContinuation = TripStop & {
|
|
37
|
-
previousEdge: VehicleEdge;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type Round = number;
|
|
41
|
-
|
|
42
23
|
/**
|
|
43
|
-
* A public transportation router implementing the RAPTOR
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
24
|
+
* A public transportation router implementing the RAPTOR and Range RAPTOR
|
|
25
|
+
* algorithms.
|
|
26
|
+
*
|
|
27
|
+
* Thin facade over {@link PlainRouter} and {@link RangeRouter}: constructs the
|
|
28
|
+
* shared {@link Raptor} engine and {@link AccessFinder} once and delegates each
|
|
29
|
+
* query to the appropriate router.
|
|
30
|
+
*
|
|
31
|
+
* @see https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
|
|
47
32
|
*/
|
|
48
33
|
export class Router {
|
|
49
|
-
private readonly
|
|
50
|
-
private readonly
|
|
34
|
+
private readonly plainRouter: PlainRouter;
|
|
35
|
+
private readonly rangeRouter: RangeRouter;
|
|
51
36
|
|
|
52
37
|
constructor(timetable: Timetable, stopsIndex: StopsIndex) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* @param query The query containing the main parameters for the routing.
|
|
61
|
-
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
62
|
-
*/
|
|
63
|
-
route(query: Query): Result {
|
|
64
|
-
const routingState = this.initRoutingState(query);
|
|
65
|
-
const markedStops = new Set<StopId>(routingState.origins);
|
|
66
|
-
// Initial transfer consideration for origins
|
|
67
|
-
const newlyMarkedStops = this.considerTransfers(
|
|
68
|
-
query,
|
|
69
|
-
0,
|
|
70
|
-
markedStops,
|
|
71
|
-
routingState,
|
|
38
|
+
const raptor = new Raptor(timetable);
|
|
39
|
+
const accessFinder = new AccessFinder(timetable, stopsIndex);
|
|
40
|
+
this.plainRouter = new PlainRouter(
|
|
41
|
+
timetable,
|
|
42
|
+
stopsIndex,
|
|
43
|
+
accessFinder,
|
|
44
|
+
raptor,
|
|
72
45
|
);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
RoutingEdge | undefined
|
|
79
|
-
>(routingState.nbStops);
|
|
80
|
-
routingState.graph.push(edgesAtCurrentRound);
|
|
81
|
-
const reachableRoutes = this.timetable.findReachableRoutes(
|
|
82
|
-
markedStops,
|
|
83
|
-
query.options.transportModes,
|
|
84
|
-
);
|
|
85
|
-
markedStops.clear();
|
|
86
|
-
// for each route that can be reached with at least round - 1 trips
|
|
87
|
-
for (const [route, hopOnStopIndex] of reachableRoutes) {
|
|
88
|
-
const newlyMarkedStops = this.scanRoute(
|
|
89
|
-
route,
|
|
90
|
-
hopOnStopIndex,
|
|
91
|
-
round,
|
|
92
|
-
routingState,
|
|
93
|
-
query.options,
|
|
94
|
-
);
|
|
95
|
-
for (const newStop of newlyMarkedStops) {
|
|
96
|
-
markedStops.add(newStop);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// process in-seat trip continuations
|
|
100
|
-
let continuations = this.findTripContinuations(
|
|
101
|
-
markedStops,
|
|
102
|
-
edgesAtCurrentRound,
|
|
103
|
-
);
|
|
104
|
-
const stopsFromContinuations = new Set<StopId>();
|
|
105
|
-
while (continuations.length > 0) {
|
|
106
|
-
stopsFromContinuations.clear();
|
|
107
|
-
for (const continuation of continuations) {
|
|
108
|
-
const route = this.timetable.getRoute(continuation.routeId)!;
|
|
109
|
-
const routeScanResults = this.scanRouteContinuation(
|
|
110
|
-
route,
|
|
111
|
-
continuation.stopIndex,
|
|
112
|
-
round,
|
|
113
|
-
routingState,
|
|
114
|
-
continuation,
|
|
115
|
-
);
|
|
116
|
-
for (const newStop of routeScanResults) {
|
|
117
|
-
stopsFromContinuations.add(newStop);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
for (const newStop of stopsFromContinuations) {
|
|
121
|
-
markedStops.add(newStop);
|
|
122
|
-
}
|
|
123
|
-
continuations = this.findTripContinuations(
|
|
124
|
-
stopsFromContinuations,
|
|
125
|
-
edgesAtCurrentRound,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
const newlyMarkedStops = this.considerTransfers(
|
|
129
|
-
query,
|
|
130
|
-
round,
|
|
131
|
-
markedStops,
|
|
132
|
-
routingState,
|
|
133
|
-
);
|
|
134
|
-
for (const newStop of newlyMarkedStops) {
|
|
135
|
-
markedStops.add(newStop);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (markedStops.size === 0) break;
|
|
139
|
-
}
|
|
140
|
-
return new Result(query, routingState, this.stopsIndex, this.timetable);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Finds trip continuations for the given marked stops and edges at the current round.
|
|
145
|
-
* @param markedStops The set of marked stops.
|
|
146
|
-
* @param edgesAtCurrentRound The array of edges at the current round, indexed by stop ID.
|
|
147
|
-
* @returns An array of trip continuations.
|
|
148
|
-
*/
|
|
149
|
-
private findTripContinuations(
|
|
150
|
-
markedStops: Set<StopId>,
|
|
151
|
-
edgesAtCurrentRound: (RoutingEdge | undefined)[],
|
|
152
|
-
): TripContinuation[] {
|
|
153
|
-
const continuations: TripContinuation[] = [];
|
|
154
|
-
for (const stopId of markedStops) {
|
|
155
|
-
const arrival = edgesAtCurrentRound[stopId];
|
|
156
|
-
if (!arrival || !('routeId' in arrival)) continue;
|
|
157
|
-
|
|
158
|
-
const continuousTrips = this.timetable.getContinuousTrips(
|
|
159
|
-
arrival.hopOffStopIndex,
|
|
160
|
-
arrival.routeId,
|
|
161
|
-
arrival.tripIndex,
|
|
162
|
-
);
|
|
163
|
-
for (let i = 0; i < continuousTrips.length; i++) {
|
|
164
|
-
const trip = continuousTrips[i]!;
|
|
165
|
-
continuations.push({
|
|
166
|
-
routeId: trip.routeId,
|
|
167
|
-
stopIndex: trip.stopIndex,
|
|
168
|
-
tripIndex: trip.tripIndex,
|
|
169
|
-
previousEdge: arrival,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return continuations;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Initializes the routing state for the RAPTOR algorithm.
|
|
178
|
-
*
|
|
179
|
-
* This method sets up the initial data structures needed for route planning,
|
|
180
|
-
* including origin and destination stops (considering equivalent stops),
|
|
181
|
-
* earliest arrival times, and marked stops for processing.
|
|
182
|
-
*
|
|
183
|
-
* @param query The routing query containing origin, destination, and departure time
|
|
184
|
-
* @returns The initialized routing state with all necessary data structures
|
|
185
|
-
*/
|
|
186
|
-
private initRoutingState(query: Query): RoutingState {
|
|
187
|
-
const { from, to, departureTime } = query;
|
|
188
|
-
// Consider children or siblings of the "from" stop as potential origins
|
|
189
|
-
const origins = this.stopsIndex
|
|
190
|
-
.equivalentStops(from)
|
|
191
|
-
.map((origin) => origin.id);
|
|
192
|
-
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
193
|
-
const destinations = Array.from(to)
|
|
194
|
-
.flatMap((destination) => this.stopsIndex.equivalentStops(destination))
|
|
195
|
-
.map((destination) => destination.id);
|
|
196
|
-
return new RoutingState(
|
|
197
|
-
origins,
|
|
198
|
-
destinations,
|
|
199
|
-
departureTime,
|
|
200
|
-
this.timetable.nbStops(),
|
|
46
|
+
this.rangeRouter = new RangeRouter(
|
|
47
|
+
timetable,
|
|
48
|
+
stopsIndex,
|
|
49
|
+
accessFinder,
|
|
50
|
+
raptor,
|
|
201
51
|
);
|
|
202
52
|
}
|
|
203
53
|
|
|
204
54
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* The boarded trip and entry stop are fixed, so there is no need to probe for
|
|
208
|
-
* earlier boardings.
|
|
209
|
-
*
|
|
210
|
-
* @param route The route to scan
|
|
211
|
-
* @param hopOnStopIndex The stop index where the continuation begins
|
|
212
|
-
* @param round The current RAPTOR round
|
|
213
|
-
* @param routingState Current routing state
|
|
214
|
-
* @param tripContinuation The in-seat continuation descriptor
|
|
215
|
-
*/
|
|
216
|
-
private scanRouteContinuation(
|
|
217
|
-
route: Route,
|
|
218
|
-
hopOnStopIndex: StopRouteIndex,
|
|
219
|
-
round: Round,
|
|
220
|
-
routingState: RoutingState,
|
|
221
|
-
tripContinuation: TripContinuation,
|
|
222
|
-
): Set<StopId> {
|
|
223
|
-
const newlyMarkedStops = new Set<StopId>();
|
|
224
|
-
const edgesAtCurrentRound = routingState.graph[round]!;
|
|
225
|
-
const earliestArrivalAtAnyDestination =
|
|
226
|
-
this.earliestArrivalAtAnyStop(routingState);
|
|
227
|
-
|
|
228
|
-
const nbStops = route.getNbStops();
|
|
229
|
-
const routeId = route.id;
|
|
230
|
-
const tripIndex = tripContinuation.tripIndex;
|
|
231
|
-
const tripStopOffset = route.tripStopOffset(tripIndex);
|
|
232
|
-
const previousEdge = tripContinuation.previousEdge;
|
|
233
|
-
|
|
234
|
-
for (
|
|
235
|
-
let currentStopIndex = hopOnStopIndex;
|
|
236
|
-
currentStopIndex < nbStops;
|
|
237
|
-
currentStopIndex++
|
|
238
|
-
) {
|
|
239
|
-
const currentStop: StopId = route.stops[currentStopIndex]!;
|
|
240
|
-
const arrivalTime = route.arrivalAtOffset(
|
|
241
|
-
currentStopIndex,
|
|
242
|
-
tripStopOffset,
|
|
243
|
-
);
|
|
244
|
-
const dropOffType = route.dropOffTypeAtOffset(
|
|
245
|
-
currentStopIndex,
|
|
246
|
-
tripStopOffset,
|
|
247
|
-
);
|
|
248
|
-
const earliestArrivalAtCurrentStop =
|
|
249
|
-
routingState.arrivalTime(currentStop);
|
|
250
|
-
if (
|
|
251
|
-
dropOffType !== NOT_AVAILABLE &&
|
|
252
|
-
arrivalTime < earliestArrivalAtCurrentStop &&
|
|
253
|
-
arrivalTime < earliestArrivalAtAnyDestination
|
|
254
|
-
) {
|
|
255
|
-
edgesAtCurrentRound[currentStop] = {
|
|
256
|
-
routeId,
|
|
257
|
-
stopIndex: hopOnStopIndex,
|
|
258
|
-
tripIndex,
|
|
259
|
-
arrival: arrivalTime,
|
|
260
|
-
hopOffStopIndex: currentStopIndex,
|
|
261
|
-
continuationOf: previousEdge,
|
|
262
|
-
};
|
|
263
|
-
routingState.updateArrival(currentStop, arrivalTime, round);
|
|
264
|
-
newlyMarkedStops.add(currentStop);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return newlyMarkedStops;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Scans a route using the standard RAPTOR boarding logic.
|
|
272
|
-
*
|
|
273
|
-
* Iterates through all stops from the hop-on point, maintaining the current
|
|
274
|
-
* best trip and improving arrival times when possible. At each marked stop it
|
|
275
|
-
* also checks whether an earlier (or first) trip can be boarded, upgrading the
|
|
276
|
-
* active trip when one is found.
|
|
277
|
-
*
|
|
278
|
-
* @param route The route to scan
|
|
279
|
-
* @param hopOnStopIndex The stop index where passengers can first board
|
|
280
|
-
* @param round The current RAPTOR round
|
|
281
|
-
* @param routingState Current routing state
|
|
282
|
-
* @param options Query options (minTransferTime, etc.)
|
|
283
|
-
*/
|
|
284
|
-
private scanRoute(
|
|
285
|
-
route: Route,
|
|
286
|
-
hopOnStopIndex: StopRouteIndex,
|
|
287
|
-
round: Round,
|
|
288
|
-
routingState: RoutingState,
|
|
289
|
-
options: QueryOptions,
|
|
290
|
-
): Set<StopId> {
|
|
291
|
-
const newlyMarkedStops = new Set<StopId>();
|
|
292
|
-
const edgesAtCurrentRound = routingState.graph[round]!;
|
|
293
|
-
const edgesAtPreviousRound = routingState.graph[round - 1]!;
|
|
294
|
-
const earliestArrivalAtAnyDestination =
|
|
295
|
-
this.earliestArrivalAtAnyStop(routingState);
|
|
296
|
-
|
|
297
|
-
const nbStops = route.getNbStops();
|
|
298
|
-
const routeId = route.id;
|
|
299
|
-
let activeTripIndex: TripRouteIndex | undefined;
|
|
300
|
-
let activeTripBoardStopIndex = hopOnStopIndex;
|
|
301
|
-
// tripStopOffset = activeTripIndex * nbStops, precomputed when the trip changes.
|
|
302
|
-
// Only valid while activeTripIndex !== undefined.
|
|
303
|
-
let activeTripStopOffset = 0;
|
|
304
|
-
|
|
305
|
-
for (
|
|
306
|
-
let currentStopIndex = hopOnStopIndex;
|
|
307
|
-
currentStopIndex < nbStops;
|
|
308
|
-
currentStopIndex++
|
|
309
|
-
) {
|
|
310
|
-
const currentStop: StopId = route.stops[currentStopIndex]!;
|
|
311
|
-
|
|
312
|
-
// If on a trip, check whether alighting here improves the global best.
|
|
313
|
-
if (activeTripIndex !== undefined) {
|
|
314
|
-
const arrivalTime = route.arrivalAtOffset(
|
|
315
|
-
currentStopIndex,
|
|
316
|
-
activeTripStopOffset,
|
|
317
|
-
);
|
|
318
|
-
const dropOffType = route.dropOffTypeAtOffset(
|
|
319
|
-
currentStopIndex,
|
|
320
|
-
activeTripStopOffset,
|
|
321
|
-
);
|
|
322
|
-
const earliestArrivalAtCurrentStop =
|
|
323
|
-
routingState.arrivalTime(currentStop);
|
|
324
|
-
if (
|
|
325
|
-
dropOffType !== NOT_AVAILABLE &&
|
|
326
|
-
arrivalTime < earliestArrivalAtCurrentStop &&
|
|
327
|
-
arrivalTime < earliestArrivalAtAnyDestination
|
|
328
|
-
) {
|
|
329
|
-
edgesAtCurrentRound[currentStop] = {
|
|
330
|
-
routeId,
|
|
331
|
-
stopIndex: activeTripBoardStopIndex,
|
|
332
|
-
tripIndex: activeTripIndex,
|
|
333
|
-
arrival: arrivalTime,
|
|
334
|
-
hopOffStopIndex: currentStopIndex,
|
|
335
|
-
};
|
|
336
|
-
routingState.updateArrival(currentStop, arrivalTime, round);
|
|
337
|
-
newlyMarkedStops.add(currentStop);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Check whether we can board an earlier (or first) trip at this stop.
|
|
342
|
-
const previousEdge = edgesAtPreviousRound[currentStop];
|
|
343
|
-
const earliestArrivalOnPreviousRound = previousEdge?.arrival;
|
|
344
|
-
if (
|
|
345
|
-
earliestArrivalOnPreviousRound !== undefined &&
|
|
346
|
-
(activeTripIndex === undefined ||
|
|
347
|
-
earliestArrivalOnPreviousRound <=
|
|
348
|
-
route.departureFrom(currentStopIndex, activeTripIndex))
|
|
349
|
-
) {
|
|
350
|
-
const earliestTrip = route.findEarliestTrip(
|
|
351
|
-
currentStopIndex,
|
|
352
|
-
earliestArrivalOnPreviousRound,
|
|
353
|
-
activeTripIndex,
|
|
354
|
-
);
|
|
355
|
-
if (earliestTrip === undefined) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const firstBoardableTrip = this.findFirstBoardableTrip(
|
|
360
|
-
currentStopIndex,
|
|
361
|
-
route,
|
|
362
|
-
earliestTrip,
|
|
363
|
-
earliestArrivalOnPreviousRound,
|
|
364
|
-
activeTripIndex,
|
|
365
|
-
// provide the previous edge only if it was a vehicle leg
|
|
366
|
-
previousEdge && 'routeId' in previousEdge ? previousEdge : undefined,
|
|
367
|
-
options.minTransferTime,
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
if (firstBoardableTrip !== undefined) {
|
|
371
|
-
activeTripIndex = firstBoardableTrip;
|
|
372
|
-
activeTripBoardStopIndex = currentStopIndex;
|
|
373
|
-
activeTripStopOffset = route.tripStopOffset(firstBoardableTrip);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return newlyMarkedStops;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Finds the first boardable trip on a route at a given stop that meets transfer requirements.
|
|
382
|
-
*
|
|
383
|
-
* This method searches through trips on a route starting from the earliest trip index reachable
|
|
384
|
-
* from the previous edge to find the first trip that can be effectively boarded,
|
|
385
|
-
* considering pickup availability, transfer guarantees, and minimum transfer times.
|
|
386
|
-
*
|
|
387
|
-
* @param stopIndex The index in the route of the stop where boarding is attempted
|
|
388
|
-
* @param route The route to search for boardable trips
|
|
389
|
-
* @param earliestTrip The earliest trip index to start searching from
|
|
390
|
-
* @param after The earliest time after which boarding can occur
|
|
391
|
-
* @param beforeTrip Optional upper bound trip index to limit search
|
|
392
|
-
* @param previousTrip The previous trip taken (for transfer guarantee checks)
|
|
393
|
-
* @param transferTime Minimum time required for transfers between trips
|
|
394
|
-
* @returns The trip index of the first boardable trip, or undefined if none found
|
|
55
|
+
* Standard RAPTOR: finds the earliest-arrival journey from `query.from` to
|
|
56
|
+
* `query.to` for the given departure time.
|
|
395
57
|
*/
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
route: Route,
|
|
399
|
-
earliestTrip: TripRouteIndex,
|
|
400
|
-
after: Time = TIME_ORIGIN,
|
|
401
|
-
beforeTrip?: TripRouteIndex,
|
|
402
|
-
previousTrip?: VehicleEdge,
|
|
403
|
-
transferTime: Duration = DURATION_ZERO,
|
|
404
|
-
): TripRouteIndex | undefined {
|
|
405
|
-
const nbTrips = route.getNbTrips();
|
|
406
|
-
|
|
407
|
-
for (let t = earliestTrip; t < (beforeTrip ?? nbTrips); t++) {
|
|
408
|
-
const pickup = route.pickUpTypeFrom(stopIndex, t);
|
|
409
|
-
if (pickup === NOT_AVAILABLE) {
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
if (previousTrip === undefined) {
|
|
413
|
-
return t;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const isGuaranteed = this.timetable.isTripTransferGuaranteed(
|
|
417
|
-
{
|
|
418
|
-
stopIndex: previousTrip.hopOffStopIndex,
|
|
419
|
-
routeId: previousTrip.routeId,
|
|
420
|
-
tripIndex: previousTrip.tripIndex,
|
|
421
|
-
},
|
|
422
|
-
{ stopIndex, routeId: route.id, tripIndex: t },
|
|
423
|
-
);
|
|
424
|
-
if (isGuaranteed) {
|
|
425
|
-
return t;
|
|
426
|
-
}
|
|
427
|
-
const departure = route.departureFrom(stopIndex, t);
|
|
428
|
-
const requiredTime = after + transferTime;
|
|
429
|
-
if (departure >= requiredTime) {
|
|
430
|
-
return t;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return undefined;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Processes all currently marked stops to find available transfers
|
|
438
|
-
* and determines if using these transfers would result in earlier arrival times
|
|
439
|
-
* at destination stops. It handles different transfer types including in-seat
|
|
440
|
-
* transfers and walking transfers with appropriate minimum transfer times.
|
|
441
|
-
*
|
|
442
|
-
* @param query The routing query containing transfer options and constraints
|
|
443
|
-
* @param round The current round number in the RAPTOR algorithm
|
|
444
|
-
* @param routingState The current routing state containing arrival times and marked stops
|
|
445
|
-
*/
|
|
446
|
-
private considerTransfers(
|
|
447
|
-
query: Query,
|
|
448
|
-
round: number,
|
|
449
|
-
markedStops: Set<StopId>,
|
|
450
|
-
routingState: RoutingState,
|
|
451
|
-
): Set<StopId> {
|
|
452
|
-
const { options } = query;
|
|
453
|
-
const arrivalsAtCurrentRound = routingState.graph[round]!;
|
|
454
|
-
const newlyMarkedStops: Set<StopId> = new Set();
|
|
455
|
-
for (const stop of markedStops) {
|
|
456
|
-
const currentArrival = arrivalsAtCurrentRound[stop];
|
|
457
|
-
// Skip transfers if the last leg was also a transfer
|
|
458
|
-
if (!currentArrival || 'type' in currentArrival) continue;
|
|
459
|
-
const transfers = this.timetable.getTransfers(stop);
|
|
460
|
-
for (let j = 0; j < transfers.length; j++) {
|
|
461
|
-
const transfer = transfers[j]!;
|
|
462
|
-
let transferTime: Duration;
|
|
463
|
-
if (transfer.minTransferTime) {
|
|
464
|
-
transferTime = transfer.minTransferTime;
|
|
465
|
-
} else if (transfer.type === 'IN_SEAT') {
|
|
466
|
-
// TODO not needed anymore now that trip continuations are handled separately
|
|
467
|
-
transferTime = DURATION_ZERO;
|
|
468
|
-
} else {
|
|
469
|
-
transferTime = options.minTransferTime;
|
|
470
|
-
}
|
|
471
|
-
const arrivalAfterTransfer = currentArrival.arrival + transferTime;
|
|
472
|
-
const originalArrival = routingState.arrivalTime(transfer.destination);
|
|
473
|
-
if (arrivalAfterTransfer < originalArrival) {
|
|
474
|
-
arrivalsAtCurrentRound[transfer.destination] = {
|
|
475
|
-
arrival: arrivalAfterTransfer,
|
|
476
|
-
from: stop,
|
|
477
|
-
to: transfer.destination,
|
|
478
|
-
minTransferTime: transfer.minTransferTime,
|
|
479
|
-
type: transfer.type,
|
|
480
|
-
} as TransferEdge;
|
|
481
|
-
routingState.updateArrival(
|
|
482
|
-
transfer.destination,
|
|
483
|
-
arrivalAfterTransfer,
|
|
484
|
-
round,
|
|
485
|
-
);
|
|
486
|
-
newlyMarkedStops.add(transfer.destination);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return newlyMarkedStops;
|
|
58
|
+
route(query: Query): Result {
|
|
59
|
+
return this.plainRouter.route(query);
|
|
491
60
|
}
|
|
492
61
|
|
|
493
62
|
/**
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* @param routingState The routing state containing arrival times and destinations.
|
|
497
|
-
* @returns The earliest arrival time among the provided destinations.
|
|
63
|
+
* Range RAPTOR: finds all Pareto-optimal journeys within the departure-time
|
|
64
|
+
* window `[query.departureTime, query.lastDepartureTime]`.
|
|
498
65
|
*/
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
for (let i = 0; i < routingState.destinations.length; i++) {
|
|
502
|
-
const arrival = routingState.arrivalTime(routingState.destinations[i]!);
|
|
503
|
-
if (arrival < earliestArrivalAtAnyDestination) {
|
|
504
|
-
earliestArrivalAtAnyDestination = arrival;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return earliestArrivalAtAnyDestination;
|
|
66
|
+
rangeRoute(query: RangeQuery): RangeResult {
|
|
67
|
+
return this.rangeRouter.rangeRoute(query);
|
|
508
68
|
}
|
|
509
69
|
}
|