minotor 11.1.1 → 11.1.3
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/CHANGELOG.md +3 -3
- package/dist/cli.mjs +171 -66
- package/dist/cli.mjs.map +1 -1
- package/dist/parser.cjs.js +65 -15
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +65 -15
- 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.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/router.d.ts +23 -10
- package/dist/routing/state.d.ts +18 -0
- package/dist/timetable/route.d.ts +27 -0
- package/package.json +1 -1
- package/src/routing/router.ts +110 -56
- package/src/routing/state.ts +26 -0
- package/src/timetable/route.ts +47 -0
- package/src/timetable/timetable.ts +26 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
## [11.1.
|
|
1
|
+
## [11.1.3](https://github.com/aubryio/minotor/compare/v11.1.2...v11.1.3) (2026-04-18)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
4
|
+
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* re-expose arrivals iterator ([681ee3a](https://github.com/aubryio/minotor/commit/681ee3a7c131c097734333cd60f5b0eed35cf3bd))
|
package/dist/cli.mjs
CHANGED
|
@@ -17263,6 +17263,45 @@ let Route$1 = class Route {
|
|
|
17263
17263
|
}
|
|
17264
17264
|
return arrival;
|
|
17265
17265
|
}
|
|
17266
|
+
/**
|
|
17267
|
+
* Computes the per-trip base offset used by the hot-path scanning methods.
|
|
17268
|
+
*
|
|
17269
|
+
* Cache the result once when boarding a new trip and pass it to
|
|
17270
|
+
* {@link arrivalAtOffset} and {@link dropOffTypeAtOffset} throughout the
|
|
17271
|
+
* scanning loop to avoid recomputing `tripIndex × nbStops` on every stop.
|
|
17272
|
+
*
|
|
17273
|
+
* @param tripIndex - The index of the trip.
|
|
17274
|
+
* @returns `tripIndex × nbStops`.
|
|
17275
|
+
*/
|
|
17276
|
+
tripStopOffset(tripIndex) {
|
|
17277
|
+
return tripIndex * this.nbStops;
|
|
17278
|
+
}
|
|
17279
|
+
/**
|
|
17280
|
+
* Hot-path variant of {@link arrivalAt} that accepts a precomputed base offset.
|
|
17281
|
+
*
|
|
17282
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17283
|
+
* @param offset - Precomputed value from {@link tripStopOffset}.
|
|
17284
|
+
* @returns The arrival time at the specified stop.
|
|
17285
|
+
*/
|
|
17286
|
+
arrivalAtOffset(stopIndex, offset) {
|
|
17287
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17288
|
+
return this.stopTimes[(offset + stopIndex) * 2];
|
|
17289
|
+
}
|
|
17290
|
+
/**
|
|
17291
|
+
* Hot-path variant of {@link dropOffTypeAt} that accepts a precomputed base offset.
|
|
17292
|
+
*
|
|
17293
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17294
|
+
* @param offset - Precomputed value from {@link tripStopOffset}.
|
|
17295
|
+
* @returns The drop-off type at the specified stop.
|
|
17296
|
+
*/
|
|
17297
|
+
dropOffTypeAtOffset(stopIndex, offset) {
|
|
17298
|
+
const globalIndex = offset + stopIndex;
|
|
17299
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17300
|
+
const byte = this.pickupDropOffTypes[globalIndex >> 1];
|
|
17301
|
+
// Bit layout per byte (two pairs): [pickup_1(2)][dropOff_1(2)][pickup_0(2)][dropOff_0(2)]
|
|
17302
|
+
// First pair → drop-off in lower 2 bits; second pair → drop-off in bits 4-5.
|
|
17303
|
+
return (globalIndex & 1 ? (byte >> 4) & 0x03 : byte & 0x03);
|
|
17304
|
+
}
|
|
17266
17305
|
/**
|
|
17267
17306
|
* Retrieves the departure time at a specific stop for a given trip.
|
|
17268
17307
|
*
|
|
@@ -17854,21 +17893,26 @@ class Timetable {
|
|
|
17854
17893
|
*/
|
|
17855
17894
|
findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
|
|
17856
17895
|
const reachableRoutes = new Map();
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
for (let
|
|
17865
|
-
const route =
|
|
17866
|
-
|
|
17867
|
-
|
|
17896
|
+
// Skip the per-route mode check entirely when all modes are allowed,
|
|
17897
|
+
// which is the common case.
|
|
17898
|
+
const filterByMode = transportModes !== ALL_TRANSPORT_MODES;
|
|
17899
|
+
for (const originStop of fromStops) {
|
|
17900
|
+
const stopData = this.stopsAdjacency[originStop];
|
|
17901
|
+
if (!stopData)
|
|
17902
|
+
continue;
|
|
17903
|
+
for (let i = 0; i < stopData.routes.length; i++) {
|
|
17904
|
+
const route = this.routesAdjacency[stopData.routes[i]];
|
|
17905
|
+
if (!route)
|
|
17906
|
+
continue;
|
|
17907
|
+
if (filterByMode) {
|
|
17908
|
+
const serviceRoute = this.serviceRoutes[route.serviceRoute()];
|
|
17909
|
+
if (!serviceRoute || !transportModes.has(serviceRoute.type))
|
|
17910
|
+
continue;
|
|
17911
|
+
}
|
|
17912
|
+
const originStopIndex = route.stopRouteIndices(originStop)[0];
|
|
17868
17913
|
const existingHopOnStopIndex = reachableRoutes.get(route);
|
|
17869
17914
|
if (existingHopOnStopIndex !== undefined) {
|
|
17870
17915
|
if (originStopIndex < existingHopOnStopIndex) {
|
|
17871
|
-
// if the current stop is before the existing hop on stop, replace it
|
|
17872
17916
|
reachableRoutes.set(route, originStopIndex);
|
|
17873
17917
|
}
|
|
17874
17918
|
}
|
|
@@ -17893,9 +17937,15 @@ class Timetable {
|
|
|
17893
17937
|
if (!guaranteedTransfers) {
|
|
17894
17938
|
return false;
|
|
17895
17939
|
}
|
|
17896
|
-
|
|
17897
|
-
transfer
|
|
17898
|
-
transfer.
|
|
17940
|
+
for (let i = 0; i < guaranteedTransfers.length; i++) {
|
|
17941
|
+
const transfer = guaranteedTransfers[i];
|
|
17942
|
+
if (transfer.stopIndex === toTripStop.stopIndex &&
|
|
17943
|
+
transfer.routeId === toTripStop.routeId &&
|
|
17944
|
+
transfer.tripIndex === toTripStop.tripIndex) {
|
|
17945
|
+
return true;
|
|
17946
|
+
}
|
|
17947
|
+
}
|
|
17948
|
+
return false;
|
|
17899
17949
|
}
|
|
17900
17950
|
/**
|
|
17901
17951
|
* Retrieves all guaranteed trip transfer options available at the specified stop for a given trip.
|
|
@@ -21949,6 +21999,31 @@ class RoutingState {
|
|
|
21949
21999
|
this.earliestArrivalTimes[stop] = time;
|
|
21950
22000
|
this.earliestArrivalLegs[stop] = leg;
|
|
21951
22001
|
}
|
|
22002
|
+
/**
|
|
22003
|
+
* Iterates over every stop that has been reached, yielding its stop ID,
|
|
22004
|
+
* earliest arrival time, and the number of legs taken to reach it.
|
|
22005
|
+
*
|
|
22006
|
+
* Unreached stops (those still at UNREACHED_TIME) are skipped entirely.
|
|
22007
|
+
*
|
|
22008
|
+
* @example
|
|
22009
|
+
* ```ts
|
|
22010
|
+
* for (const { stop, arrival, legNumber } of routingState.arrivals()) {
|
|
22011
|
+
* console.log(`Stop ${stop}: arrived at ${arrival} after ${legNumber} leg(s)`);
|
|
22012
|
+
* }
|
|
22013
|
+
* ```
|
|
22014
|
+
*/
|
|
22015
|
+
*arrivals() {
|
|
22016
|
+
for (let stop = 0; stop < this.earliestArrivalTimes.length; stop++) {
|
|
22017
|
+
const time = this.earliestArrivalTimes[stop];
|
|
22018
|
+
if (time < UNREACHED_TIME) {
|
|
22019
|
+
yield {
|
|
22020
|
+
stop,
|
|
22021
|
+
arrival: time,
|
|
22022
|
+
legNumber: this.earliestArrivalLegs[stop],
|
|
22023
|
+
};
|
|
22024
|
+
}
|
|
22025
|
+
}
|
|
22026
|
+
}
|
|
21952
22027
|
/**
|
|
21953
22028
|
* Returns the earliest arrival at a stop as an {@link Arrival} object,
|
|
21954
22029
|
* or undefined if the stop has not been reached.
|
|
@@ -22041,11 +22116,12 @@ class Router {
|
|
|
22041
22116
|
}
|
|
22042
22117
|
// process in-seat trip continuations
|
|
22043
22118
|
let continuations = this.findTripContinuations(markedStops, edgesAtCurrentRound);
|
|
22119
|
+
const stopsFromContinuations = new Set();
|
|
22044
22120
|
while (continuations.length > 0) {
|
|
22045
|
-
|
|
22121
|
+
stopsFromContinuations.clear();
|
|
22046
22122
|
for (const continuation of continuations) {
|
|
22047
22123
|
const route = this.timetable.getRoute(continuation.routeId);
|
|
22048
|
-
const routeScanResults = this.
|
|
22124
|
+
const routeScanResults = this.scanRouteContinuation(route, continuation.stopIndex, round, routingState, continuation);
|
|
22049
22125
|
for (const newStop of routeScanResults) {
|
|
22050
22126
|
stopsFromContinuations.add(newStop);
|
|
22051
22127
|
}
|
|
@@ -22112,84 +22188,113 @@ class Router {
|
|
|
22112
22188
|
return new RoutingState(origins, destinations, departureTime, this.timetable.nbStops());
|
|
22113
22189
|
}
|
|
22114
22190
|
/**
|
|
22115
|
-
* Scans a route
|
|
22191
|
+
* Scans a route for an in-seat trip continuation.
|
|
22116
22192
|
*
|
|
22117
|
-
*
|
|
22118
|
-
*
|
|
22119
|
-
* maintaining the current best trip and updating arrival times when improvements
|
|
22120
|
-
* are found. The method also handles boarding new trips when earlier departures
|
|
22121
|
-
* are available if no given trip is provided as a parameter.
|
|
22193
|
+
* The boarded trip and entry stop are fixed, so there is no need to probe for
|
|
22194
|
+
* earlier boardings.
|
|
22122
22195
|
*
|
|
22123
|
-
* @param route The route to scan
|
|
22124
|
-
* @param hopOnStopIndex The stop index where
|
|
22125
|
-
* @param round The current
|
|
22126
|
-
* @param routingState
|
|
22196
|
+
* @param route The route to scan
|
|
22197
|
+
* @param hopOnStopIndex The stop index where the continuation begins
|
|
22198
|
+
* @param round The current RAPTOR round
|
|
22199
|
+
* @param routingState Current routing state
|
|
22200
|
+
* @param tripContinuation The in-seat continuation descriptor
|
|
22127
22201
|
*/
|
|
22128
|
-
|
|
22202
|
+
scanRouteContinuation(route, hopOnStopIndex, round, routingState, tripContinuation) {
|
|
22129
22203
|
const newlyMarkedStops = new Set();
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
|
|
22204
|
+
const edgesAtCurrentRound = routingState.graph[round];
|
|
22205
|
+
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState);
|
|
22206
|
+
const nbStops = route.getNbStops();
|
|
22207
|
+
const routeId = route.id;
|
|
22208
|
+
const tripIndex = tripContinuation.tripIndex;
|
|
22209
|
+
const tripStopOffset = route.tripStopOffset(tripIndex);
|
|
22210
|
+
const previousEdge = tripContinuation.previousEdge;
|
|
22211
|
+
for (let currentStopIndex = hopOnStopIndex; currentStopIndex < nbStops; currentStopIndex++) {
|
|
22212
|
+
const currentStop = route.stops[currentStopIndex];
|
|
22213
|
+
const arrivalTime = route.arrivalAtOffset(currentStopIndex, tripStopOffset);
|
|
22214
|
+
const dropOffType = route.dropOffTypeAtOffset(currentStopIndex, tripStopOffset);
|
|
22215
|
+
const earliestArrivalAtCurrentStop = routingState.arrivalTime(currentStop);
|
|
22216
|
+
if (dropOffType !== NOT_AVAILABLE &&
|
|
22217
|
+
arrivalTime < earliestArrivalAtCurrentStop &&
|
|
22218
|
+
arrivalTime < earliestArrivalAtAnyDestination) {
|
|
22219
|
+
edgesAtCurrentRound[currentStop] = {
|
|
22220
|
+
routeId,
|
|
22221
|
+
stopIndex: hopOnStopIndex,
|
|
22222
|
+
tripIndex,
|
|
22223
|
+
arrival: arrivalTime,
|
|
22224
|
+
hopOffStopIndex: currentStopIndex,
|
|
22225
|
+
continuationOf: previousEdge,
|
|
22226
|
+
};
|
|
22227
|
+
routingState.updateArrival(currentStop, arrivalTime, round);
|
|
22228
|
+
newlyMarkedStops.add(currentStop);
|
|
22135
22229
|
}
|
|
22136
|
-
|
|
22230
|
+
}
|
|
22231
|
+
return newlyMarkedStops;
|
|
22232
|
+
}
|
|
22233
|
+
/**
|
|
22234
|
+
* Scans a route using the standard RAPTOR boarding logic.
|
|
22235
|
+
*
|
|
22236
|
+
* Iterates through all stops from the hop-on point, maintaining the current
|
|
22237
|
+
* best trip and improving arrival times when possible. At each marked stop it
|
|
22238
|
+
* also checks whether an earlier (or first) trip can be boarded, upgrading the
|
|
22239
|
+
* active trip when one is found.
|
|
22240
|
+
*
|
|
22241
|
+
* @param route The route to scan
|
|
22242
|
+
* @param hopOnStopIndex The stop index where passengers can first board
|
|
22243
|
+
* @param round The current RAPTOR round
|
|
22244
|
+
* @param routingState Current routing state
|
|
22245
|
+
* @param options Query options (minTransferTime, etc.)
|
|
22246
|
+
*/
|
|
22247
|
+
scanRoute(route, hopOnStopIndex, round, routingState, options) {
|
|
22248
|
+
const newlyMarkedStops = new Set();
|
|
22137
22249
|
const edgesAtCurrentRound = routingState.graph[round];
|
|
22138
22250
|
const edgesAtPreviousRound = routingState.graph[round - 1];
|
|
22139
|
-
// Compute target pruning criteria only once per route
|
|
22140
22251
|
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState);
|
|
22141
|
-
|
|
22252
|
+
const nbStops = route.getNbStops();
|
|
22253
|
+
const routeId = route.id;
|
|
22254
|
+
let activeTripIndex;
|
|
22255
|
+
let activeTripBoardStopIndex = hopOnStopIndex;
|
|
22256
|
+
// tripStopOffset = activeTripIndex * nbStops, precomputed when the trip changes.
|
|
22257
|
+
// Only valid while activeTripIndex !== undefined.
|
|
22258
|
+
let activeTripStopOffset = 0;
|
|
22259
|
+
for (let currentStopIndex = hopOnStopIndex; currentStopIndex < nbStops; currentStopIndex++) {
|
|
22142
22260
|
const currentStop = route.stops[currentStopIndex];
|
|
22143
|
-
// If
|
|
22144
|
-
|
|
22145
|
-
|
|
22146
|
-
const
|
|
22147
|
-
const dropOffType = route.dropOffTypeAt(currentStopIndex, activeTrip.tripIndex);
|
|
22261
|
+
// If on a trip, check whether alighting here improves the global best.
|
|
22262
|
+
if (activeTripIndex !== undefined) {
|
|
22263
|
+
const arrivalTime = route.arrivalAtOffset(currentStopIndex, activeTripStopOffset);
|
|
22264
|
+
const dropOffType = route.dropOffTypeAtOffset(currentStopIndex, activeTripStopOffset);
|
|
22148
22265
|
const earliestArrivalAtCurrentStop = routingState.arrivalTime(currentStop);
|
|
22149
22266
|
if (dropOffType !== NOT_AVAILABLE &&
|
|
22150
22267
|
arrivalTime < earliestArrivalAtCurrentStop &&
|
|
22151
22268
|
arrivalTime < earliestArrivalAtAnyDestination) {
|
|
22152
|
-
|
|
22153
|
-
routeId
|
|
22154
|
-
stopIndex:
|
|
22155
|
-
tripIndex:
|
|
22269
|
+
edgesAtCurrentRound[currentStop] = {
|
|
22270
|
+
routeId,
|
|
22271
|
+
stopIndex: activeTripBoardStopIndex,
|
|
22272
|
+
tripIndex: activeTripIndex,
|
|
22156
22273
|
arrival: arrivalTime,
|
|
22157
22274
|
hopOffStopIndex: currentStopIndex,
|
|
22158
22275
|
};
|
|
22159
|
-
if (tripContinuation) {
|
|
22160
|
-
// In case of continuous trip, we set a pointer to the previous edge
|
|
22161
|
-
edge.continuationOf = tripContinuation.previousEdge;
|
|
22162
|
-
}
|
|
22163
|
-
edgesAtCurrentRound[currentStop] = edge;
|
|
22164
22276
|
routingState.updateArrival(currentStop, arrivalTime, round);
|
|
22165
22277
|
newlyMarkedStops.add(currentStop);
|
|
22166
22278
|
}
|
|
22167
22279
|
}
|
|
22168
|
-
|
|
22169
|
-
// If it's a trip continuation, no need to check for earlier trips
|
|
22170
|
-
continue;
|
|
22171
|
-
}
|
|
22172
|
-
// check if we can board an earlier trip at the current stop
|
|
22173
|
-
// if there was no current trip, find the first one reachable
|
|
22280
|
+
// Check whether we can board an earlier (or first) trip at this stop.
|
|
22174
22281
|
const previousEdge = edgesAtPreviousRound[currentStop];
|
|
22175
22282
|
const earliestArrivalOnPreviousRound = previousEdge === null || previousEdge === void 0 ? void 0 : previousEdge.arrival;
|
|
22176
22283
|
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
22177
|
-
(
|
|
22284
|
+
(activeTripIndex === undefined ||
|
|
22178
22285
|
earliestArrivalOnPreviousRound <=
|
|
22179
|
-
route.departureFrom(currentStopIndex,
|
|
22180
|
-
const earliestTrip = route.findEarliestTrip(currentStopIndex, earliestArrivalOnPreviousRound,
|
|
22286
|
+
route.departureFrom(currentStopIndex, activeTripIndex))) {
|
|
22287
|
+
const earliestTrip = route.findEarliestTrip(currentStopIndex, earliestArrivalOnPreviousRound, activeTripIndex);
|
|
22181
22288
|
if (earliestTrip === undefined) {
|
|
22182
22289
|
continue;
|
|
22183
22290
|
}
|
|
22184
|
-
const firstBoardableTrip = this.findFirstBoardableTrip(currentStopIndex, route, earliestTrip, earliestArrivalOnPreviousRound,
|
|
22185
|
-
// provide the previous
|
|
22291
|
+
const firstBoardableTrip = this.findFirstBoardableTrip(currentStopIndex, route, earliestTrip, earliestArrivalOnPreviousRound, activeTripIndex,
|
|
22292
|
+
// provide the previous edge only if it was a vehicle leg
|
|
22186
22293
|
previousEdge && 'routeId' in previousEdge ? previousEdge : undefined, options.minTransferTime);
|
|
22187
22294
|
if (firstBoardableTrip !== undefined) {
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
stopIndex: currentStopIndex,
|
|
22192
|
-
};
|
|
22295
|
+
activeTripIndex = firstBoardableTrip;
|
|
22296
|
+
activeTripBoardStopIndex = currentStopIndex;
|
|
22297
|
+
activeTripStopOffset = route.tripStopOffset(firstBoardableTrip);
|
|
22193
22298
|
}
|
|
22194
22299
|
}
|
|
22195
22300
|
}
|