minotor 8.0.0 → 9.0.1
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/.github/workflows/minotor.yml +1 -1
- package/CHANGELOG.md +3 -8
- package/README.md +1 -1
- package/dist/cli.mjs +352 -256
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +21 -6
- package/dist/gtfs/trips.d.ts +2 -2
- package/dist/parser.cjs.js +296 -188
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +296 -188
- 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 +4 -4
- package/dist/timetable/io.d.ts +3 -3
- package/dist/timetable/proto/timetable.d.ts +6 -4
- package/dist/timetable/route.d.ts +13 -21
- package/dist/timetable/timetable.d.ts +13 -11
- package/dist/timetable/tripBoardingId.d.ts +34 -0
- package/package.json +1 -1
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/repl.ts +53 -67
- package/src/gtfs/__tests__/parser.test.ts +19 -4
- package/src/gtfs/__tests__/transfers.test.ts +598 -318
- package/src/gtfs/__tests__/trips.test.ts +3 -44
- package/src/gtfs/parser.ts +26 -8
- package/src/gtfs/transfers.ts +151 -20
- package/src/gtfs/trips.ts +1 -39
- package/src/routing/__tests__/result.test.ts +10 -10
- package/src/routing/__tests__/router.test.ts +11 -9
- package/src/routing/result.ts +2 -2
- package/src/routing/router.ts +34 -22
- package/src/timetable/__tests__/io.test.ts +8 -7
- package/src/timetable/__tests__/route.test.ts +66 -80
- package/src/timetable/__tests__/timetable.test.ts +32 -29
- package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
- package/src/timetable/io.ts +21 -20
- package/src/timetable/proto/timetable.proto +6 -4
- package/src/timetable/proto/timetable.ts +84 -48
- package/src/timetable/route.ts +39 -56
- package/src/timetable/timetable.ts +37 -26
- package/src/timetable/tripBoardingId.ts +94 -0
- package/dist/timetable/tripId.d.ts +0 -15
- package/src/timetable/__tests__/tripId.test.ts +0 -27
- package/src/timetable/tripId.ts +0 -29
- /package/dist/timetable/__tests__/{tripId.test.d.ts → tripBoardingId.test.d.ts} +0 -0
package/src/routing/router.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
import { StopId } from '../stops/stops.js';
|
|
3
3
|
import { StopsIndex } from '../stops/stopsIndex.js';
|
|
4
4
|
import { Duration } from '../timetable/duration.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Route,
|
|
7
|
+
RouteId,
|
|
8
|
+
StopRouteIndex,
|
|
9
|
+
TripRouteIndex,
|
|
10
|
+
} from '../timetable/route.js';
|
|
6
11
|
import { Time } from '../timetable/time.js';
|
|
7
12
|
import {
|
|
8
13
|
Timetable,
|
|
@@ -18,8 +23,8 @@ export type OriginNode = { arrival: Time };
|
|
|
18
23
|
|
|
19
24
|
export type VehicleEdge = {
|
|
20
25
|
arrival: Time;
|
|
21
|
-
from:
|
|
22
|
-
to:
|
|
26
|
+
from: StopRouteIndex;
|
|
27
|
+
to: StopRouteIndex;
|
|
23
28
|
routeId: RouteId;
|
|
24
29
|
tripIndex: TripRouteIndex;
|
|
25
30
|
continuationOf?: VehicleEdge;
|
|
@@ -96,10 +101,10 @@ export class Router {
|
|
|
96
101
|
);
|
|
97
102
|
markedStops.clear();
|
|
98
103
|
// for each route that can be reached with at least round - 1 trips
|
|
99
|
-
for (const [route,
|
|
104
|
+
for (const [route, hopOnStopIndex] of reachableRoutes) {
|
|
100
105
|
const newlyMarkedStops = this.scanRoute(
|
|
101
106
|
route,
|
|
102
|
-
|
|
107
|
+
hopOnStopIndex,
|
|
103
108
|
round,
|
|
104
109
|
routingState,
|
|
105
110
|
);
|
|
@@ -118,7 +123,7 @@ export class Router {
|
|
|
118
123
|
const route = this.timetable.getRoute(continuation.routeId)!;
|
|
119
124
|
const routeScanResults = this.scanRoute(
|
|
120
125
|
route,
|
|
121
|
-
continuation.
|
|
126
|
+
continuation.hopOnStopIndex,
|
|
122
127
|
round,
|
|
123
128
|
routingState,
|
|
124
129
|
continuation,
|
|
@@ -144,6 +149,7 @@ export class Router {
|
|
|
144
149
|
for (const newStop of newlyMarkedStops) {
|
|
145
150
|
markedStops.add(newStop);
|
|
146
151
|
}
|
|
152
|
+
|
|
147
153
|
if (markedStops.size === 0) break;
|
|
148
154
|
}
|
|
149
155
|
return new Result(query, routingState, this.stopsIndex, this.timetable);
|
|
@@ -165,7 +171,7 @@ export class Router {
|
|
|
165
171
|
if (!arrival || !('routeId' in arrival)) continue;
|
|
166
172
|
|
|
167
173
|
const continuousTrips = this.timetable.getContinuousTrips(
|
|
168
|
-
|
|
174
|
+
arrival.to,
|
|
169
175
|
arrival.routeId,
|
|
170
176
|
arrival.tripIndex,
|
|
171
177
|
);
|
|
@@ -173,7 +179,7 @@ export class Router {
|
|
|
173
179
|
const trip = continuousTrips[i]!;
|
|
174
180
|
continuations.push({
|
|
175
181
|
routeId: trip.routeId,
|
|
176
|
-
|
|
182
|
+
hopOnStopIndex: trip.hopOnStopIndex,
|
|
177
183
|
tripIndex: trip.tripIndex,
|
|
178
184
|
previousEdge: arrival,
|
|
179
185
|
});
|
|
@@ -232,13 +238,13 @@ export class Router {
|
|
|
232
238
|
* are available if no given trip is provided as a parameter.
|
|
233
239
|
*
|
|
234
240
|
* @param route The route to scan for possible trips
|
|
235
|
-
* @param
|
|
241
|
+
* @param hopOnStopIndex The stop index where passengers can board the route
|
|
236
242
|
* @param round The current round number in the RAPTOR algorithm
|
|
237
243
|
* @param routingState The current routing state containing arrival times and marked stops
|
|
238
244
|
*/
|
|
239
245
|
private scanRoute(
|
|
240
246
|
route: Route,
|
|
241
|
-
|
|
247
|
+
hopOnStopIndex: StopRouteIndex,
|
|
242
248
|
round: Round,
|
|
243
249
|
routingState: RoutingState,
|
|
244
250
|
tripContinuation?: TripContinuation,
|
|
@@ -247,26 +253,32 @@ export class Router {
|
|
|
247
253
|
let activeTrip: TripBoarding | undefined = tripContinuation
|
|
248
254
|
? {
|
|
249
255
|
routeId: route.id,
|
|
250
|
-
|
|
256
|
+
hopOnStopIndex,
|
|
251
257
|
tripIndex: tripContinuation.tripIndex,
|
|
252
258
|
}
|
|
253
259
|
: undefined;
|
|
254
260
|
const edgesAtCurrentRound = routingState.graph[round]!;
|
|
255
261
|
const edgesAtPreviousRound = routingState.graph[round - 1]!;
|
|
256
|
-
const startIndex = route.stopRouteIndex(hopOnStop);
|
|
257
262
|
// Compute target pruning criteria only once per route
|
|
258
263
|
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(
|
|
259
264
|
routingState.earliestArrivals,
|
|
260
265
|
routingState.destinations,
|
|
261
266
|
);
|
|
262
|
-
for (
|
|
263
|
-
|
|
267
|
+
for (
|
|
268
|
+
let currentStopIndex = hopOnStopIndex;
|
|
269
|
+
currentStopIndex < route.getNbStops();
|
|
270
|
+
currentStopIndex++
|
|
271
|
+
) {
|
|
272
|
+
const currentStop: StopId = route.stops[currentStopIndex]!;
|
|
264
273
|
// If we're currently on a trip,
|
|
265
274
|
// check if arrival at the stop improves the earliest arrival time
|
|
266
275
|
if (activeTrip !== undefined) {
|
|
267
|
-
const arrivalTime = route.arrivalAt(
|
|
276
|
+
const arrivalTime = route.arrivalAt(
|
|
277
|
+
currentStopIndex,
|
|
278
|
+
activeTrip.tripIndex,
|
|
279
|
+
);
|
|
268
280
|
const dropOffType = route.dropOffTypeAt(
|
|
269
|
-
|
|
281
|
+
currentStopIndex,
|
|
270
282
|
activeTrip.tripIndex,
|
|
271
283
|
);
|
|
272
284
|
const earliestArrivalAtCurrentStop =
|
|
@@ -280,8 +292,8 @@ export class Router {
|
|
|
280
292
|
arrival: arrivalTime,
|
|
281
293
|
routeId: route.id,
|
|
282
294
|
tripIndex: activeTrip.tripIndex,
|
|
283
|
-
from: activeTrip.
|
|
284
|
-
to:
|
|
295
|
+
from: activeTrip.hopOnStopIndex,
|
|
296
|
+
to: currentStopIndex,
|
|
285
297
|
} as VehicleEdge;
|
|
286
298
|
if (tripContinuation) {
|
|
287
299
|
// In case of continuous trip, we set a pointer to the previous edge
|
|
@@ -312,14 +324,14 @@ export class Router {
|
|
|
312
324
|
earliestArrivalOnPreviousRound !== undefined &&
|
|
313
325
|
(activeTrip === undefined ||
|
|
314
326
|
earliestArrivalOnPreviousRound.isBefore(
|
|
315
|
-
route.departureFrom(
|
|
327
|
+
route.departureFrom(currentStopIndex, activeTrip.tripIndex),
|
|
316
328
|
) ||
|
|
317
329
|
earliestArrivalOnPreviousRound.equals(
|
|
318
|
-
route.departureFrom(
|
|
330
|
+
route.departureFrom(currentStopIndex, activeTrip.tripIndex),
|
|
319
331
|
))
|
|
320
332
|
) {
|
|
321
333
|
const earliestTrip = route.findEarliestTrip(
|
|
322
|
-
|
|
334
|
+
currentStopIndex,
|
|
323
335
|
earliestArrivalOnPreviousRound,
|
|
324
336
|
activeTrip?.tripIndex,
|
|
325
337
|
);
|
|
@@ -327,7 +339,7 @@ export class Router {
|
|
|
327
339
|
activeTrip = {
|
|
328
340
|
routeId: route.id,
|
|
329
341
|
tripIndex: earliestTrip,
|
|
330
|
-
|
|
342
|
+
hopOnStopIndex: currentStopIndex,
|
|
331
343
|
};
|
|
332
344
|
}
|
|
333
345
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { REGULAR, Route } from '../route.js';
|
|
16
16
|
import { Time } from '../time.js';
|
|
17
17
|
import { ServiceRoute, StopAdjacency, TripBoarding } from '../timetable.js';
|
|
18
|
+
import { encode } from '../tripBoardingId.js';
|
|
18
19
|
|
|
19
20
|
describe('Timetable IO', () => {
|
|
20
21
|
const stopsAdjacency: StopAdjacency[] = [
|
|
@@ -63,7 +64,6 @@ describe('Timetable IO', () => {
|
|
|
63
64
|
{
|
|
64
65
|
transfers: [{ destination: 2, type: 0 }],
|
|
65
66
|
routes: [0],
|
|
66
|
-
tripContinuations: [],
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
transfers: [
|
|
@@ -74,7 +74,6 @@ describe('Timetable IO', () => {
|
|
|
74
74
|
},
|
|
75
75
|
],
|
|
76
76
|
routes: [1],
|
|
77
|
-
tripContinuations: [],
|
|
78
77
|
},
|
|
79
78
|
];
|
|
80
79
|
|
|
@@ -120,12 +119,14 @@ describe('Timetable IO', () => {
|
|
|
120
119
|
});
|
|
121
120
|
|
|
122
121
|
it('should serialize and deserialize tripContinuations correctly', () => {
|
|
123
|
-
const tripContinuations = new Map<
|
|
124
|
-
tripContinuations.set(0, [
|
|
125
|
-
{
|
|
126
|
-
{
|
|
122
|
+
const tripContinuations = new Map<bigint, TripBoarding[]>();
|
|
123
|
+
tripContinuations.set(encode(1, 0, 2), [
|
|
124
|
+
{ hopOnStopIndex: 1, routeId: 0, tripIndex: 2 },
|
|
125
|
+
{ hopOnStopIndex: 3, routeId: 1, tripIndex: 1 },
|
|
126
|
+
]);
|
|
127
|
+
tripContinuations.set(encode(2, 0, 0), [
|
|
128
|
+
{ hopOnStopIndex: 2, routeId: 0, tripIndex: 0 },
|
|
127
129
|
]);
|
|
128
|
-
tripContinuations.set(1, [{ hopOnStop: 2, routeId: 0, tripIndex: 0 }]);
|
|
129
130
|
|
|
130
131
|
const serialized = serializeTripContinuations(tripContinuations);
|
|
131
132
|
const deserialized = deserializeTripContinuations(serialized);
|
|
@@ -95,34 +95,6 @@ describe('Route', () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
describe('isBefore', () => {
|
|
99
|
-
it('should return true when stopA is before stopB', () => {
|
|
100
|
-
assert.strictEqual(route.isBefore(1001, 1002), true);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should return false when stopA is after stopB', () => {
|
|
104
|
-
assert.strictEqual(route.isBefore(1002, 1001), false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should return false when stopA equals stopB', () => {
|
|
108
|
-
assert.strictEqual(route.isBefore(1001, 1001), false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should throw error when stopA is not found', () => {
|
|
112
|
-
assert.throws(
|
|
113
|
-
() => route.isBefore(9999, 1002),
|
|
114
|
-
/Stop index not found for 9999 in route 0/,
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should throw error when stopB is not found', () => {
|
|
119
|
-
assert.throws(
|
|
120
|
-
() => route.isBefore(1001, 9999),
|
|
121
|
-
/Stop index not found for 9999 in route 0/,
|
|
122
|
-
);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
98
|
describe('getNbStops', () => {
|
|
127
99
|
it('should return correct number of stops', () => {
|
|
128
100
|
assert.strictEqual(route.getNbStops(), 2);
|
|
@@ -136,178 +108,192 @@ describe('Route', () => {
|
|
|
136
108
|
});
|
|
137
109
|
|
|
138
110
|
describe('arrivalAt', () => {
|
|
139
|
-
it('should return correct arrival time for trip 0 at stop
|
|
140
|
-
const arrival = route.arrivalAt(
|
|
111
|
+
it('should return correct arrival time for trip 0 at stop index 0', () => {
|
|
112
|
+
const arrival = route.arrivalAt(0, 0);
|
|
141
113
|
assert.strictEqual(
|
|
142
114
|
arrival.toMinutes(),
|
|
143
115
|
Time.fromHMS(8, 0, 0).toMinutes(),
|
|
144
116
|
);
|
|
145
117
|
});
|
|
146
118
|
|
|
147
|
-
it('should return correct arrival time for trip 1 at stop
|
|
148
|
-
const arrival = route.arrivalAt(
|
|
119
|
+
it('should return correct arrival time for trip 1 at stop index 1', () => {
|
|
120
|
+
const arrival = route.arrivalAt(1, 1);
|
|
149
121
|
assert.strictEqual(
|
|
150
122
|
arrival.toMinutes(),
|
|
151
123
|
Time.fromHMS(9, 30, 0).toMinutes(),
|
|
152
124
|
);
|
|
153
125
|
});
|
|
154
126
|
|
|
155
|
-
it('should throw error for invalid stop
|
|
127
|
+
it('should throw error for invalid stop index', () => {
|
|
156
128
|
assert.throws(
|
|
157
|
-
() => route.arrivalAt(
|
|
158
|
-
/
|
|
129
|
+
() => route.arrivalAt(999, 0),
|
|
130
|
+
/StopId for stop at index 999 not found/,
|
|
159
131
|
);
|
|
160
132
|
});
|
|
161
133
|
|
|
162
134
|
it('should throw error for invalid trip index', () => {
|
|
163
|
-
assert.throws(
|
|
164
|
-
() => route.arrivalAt(1001, 999),
|
|
165
|
-
/Arrival time not found for stop 1001 at trip index 999/,
|
|
166
|
-
);
|
|
135
|
+
assert.throws(() => route.arrivalAt(0, 999), /Arrival time not found/);
|
|
167
136
|
});
|
|
168
137
|
});
|
|
169
138
|
|
|
170
139
|
describe('departureFrom', () => {
|
|
171
|
-
it('should return correct departure time for trip 0 at stop
|
|
172
|
-
const departure = route.departureFrom(
|
|
140
|
+
it('should return correct departure time for trip 0 at stop index 0', () => {
|
|
141
|
+
const departure = route.departureFrom(0, 0);
|
|
173
142
|
assert.strictEqual(
|
|
174
143
|
departure.toMinutes(),
|
|
175
144
|
Time.fromHMS(8, 1, 0).toMinutes(),
|
|
176
145
|
);
|
|
177
146
|
});
|
|
178
147
|
|
|
179
|
-
it('should return correct departure time for trip 2 at stop
|
|
180
|
-
const departure = route.departureFrom(
|
|
148
|
+
it('should return correct departure time for trip 2 at stop index 1', () => {
|
|
149
|
+
const departure = route.departureFrom(1, 2);
|
|
181
150
|
assert.strictEqual(
|
|
182
151
|
departure.toMinutes(),
|
|
183
152
|
Time.fromHMS(10, 31, 0).toMinutes(),
|
|
184
153
|
);
|
|
185
154
|
});
|
|
186
155
|
|
|
187
|
-
it('should throw error for invalid stop
|
|
156
|
+
it('should throw error for invalid stop index', () => {
|
|
188
157
|
assert.throws(
|
|
189
|
-
() => route.departureFrom(
|
|
190
|
-
/
|
|
158
|
+
() => route.departureFrom(999, 0),
|
|
159
|
+
/StopId for stop at index 999 not found/,
|
|
191
160
|
);
|
|
192
161
|
});
|
|
193
162
|
|
|
194
163
|
it('should throw error for invalid trip index', () => {
|
|
195
164
|
assert.throws(
|
|
196
|
-
() => route.departureFrom(
|
|
197
|
-
/Departure time not found
|
|
165
|
+
() => route.departureFrom(0, 999),
|
|
166
|
+
/Departure time not found/,
|
|
198
167
|
);
|
|
199
168
|
});
|
|
200
169
|
});
|
|
201
170
|
|
|
202
171
|
describe('pickUpTypeFrom', () => {
|
|
203
|
-
it('should return REGULAR pickup type for trip 0 at stop
|
|
204
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
172
|
+
it('should return REGULAR pickup type for trip 0 at stop index 0', () => {
|
|
173
|
+
const pickUpType = route.pickUpTypeFrom(0, 0);
|
|
205
174
|
assert.strictEqual(pickUpType, 'REGULAR');
|
|
206
175
|
});
|
|
207
176
|
|
|
208
|
-
it('should return NOT_AVAILABLE pickup type for trip 0 at stop
|
|
209
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
177
|
+
it('should return NOT_AVAILABLE pickup type for trip 0 at stop index 1', () => {
|
|
178
|
+
const pickUpType = route.pickUpTypeFrom(1, 0);
|
|
210
179
|
assert.strictEqual(pickUpType, 'NOT_AVAILABLE');
|
|
211
180
|
});
|
|
212
181
|
|
|
213
|
-
it('should return MUST_PHONE_AGENCY pickup type for trip 2 at stop
|
|
214
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
182
|
+
it('should return MUST_PHONE_AGENCY pickup type for trip 2 at stop index 0', () => {
|
|
183
|
+
const pickUpType = route.pickUpTypeFrom(0, 2);
|
|
215
184
|
assert.strictEqual(pickUpType, 'MUST_PHONE_AGENCY');
|
|
216
185
|
});
|
|
217
186
|
|
|
218
|
-
it('should throw error for invalid stop
|
|
187
|
+
it('should throw error for invalid stop index', () => {
|
|
219
188
|
assert.throws(
|
|
220
|
-
() => route.pickUpTypeFrom(
|
|
221
|
-
/
|
|
189
|
+
() => route.pickUpTypeFrom(999, 0),
|
|
190
|
+
/StopId for stop at index 999 not found/,
|
|
222
191
|
);
|
|
223
192
|
});
|
|
224
193
|
|
|
225
194
|
it('should throw error for invalid trip index', () => {
|
|
226
195
|
assert.throws(
|
|
227
|
-
() => route.pickUpTypeFrom(
|
|
228
|
-
/Pick up type not found
|
|
196
|
+
() => route.pickUpTypeFrom(0, 999),
|
|
197
|
+
/Pick up type not found/,
|
|
229
198
|
);
|
|
230
199
|
});
|
|
231
200
|
});
|
|
232
201
|
|
|
233
202
|
describe('dropOffTypeAt', () => {
|
|
234
|
-
it('should return REGULAR drop off type for trip 0 at stop
|
|
235
|
-
const dropOffType = route.dropOffTypeAt(
|
|
203
|
+
it('should return REGULAR drop off type for trip 0 at stop index 0', () => {
|
|
204
|
+
const dropOffType = route.dropOffTypeAt(0, 0);
|
|
236
205
|
assert.strictEqual(dropOffType, 'REGULAR');
|
|
237
206
|
});
|
|
238
207
|
|
|
239
|
-
it('should return REGULAR drop off type for trip 1 at stop
|
|
240
|
-
const dropOffType = route.dropOffTypeAt(
|
|
208
|
+
it('should return REGULAR drop off type for trip 1 at stop index 1', () => {
|
|
209
|
+
const dropOffType = route.dropOffTypeAt(1, 1);
|
|
241
210
|
assert.strictEqual(dropOffType, 'REGULAR');
|
|
242
211
|
});
|
|
243
212
|
|
|
244
|
-
it('should throw error for invalid stop
|
|
213
|
+
it('should throw error for invalid stop index', () => {
|
|
245
214
|
assert.throws(
|
|
246
|
-
() => route.dropOffTypeAt(
|
|
247
|
-
/
|
|
215
|
+
() => route.dropOffTypeAt(999, 0),
|
|
216
|
+
/StopId for stop at index 999 not found/,
|
|
248
217
|
);
|
|
249
218
|
});
|
|
250
219
|
|
|
251
220
|
it('should throw error for invalid trip index', () => {
|
|
252
221
|
assert.throws(
|
|
253
|
-
() => route.dropOffTypeAt(
|
|
254
|
-
/Drop off type not found
|
|
222
|
+
() => route.dropOffTypeAt(0, 999),
|
|
223
|
+
/Drop off type not found/,
|
|
255
224
|
);
|
|
256
225
|
});
|
|
257
226
|
});
|
|
258
227
|
|
|
259
228
|
describe('findEarliestTrip', () => {
|
|
260
229
|
it('should find earliest trip without time constraint', () => {
|
|
261
|
-
const tripIndex = route.findEarliestTrip(
|
|
230
|
+
const tripIndex = route.findEarliestTrip(0);
|
|
262
231
|
assert.strictEqual(tripIndex, 0);
|
|
263
232
|
});
|
|
264
233
|
|
|
265
234
|
it('should find earliest trip after specified time', () => {
|
|
266
235
|
const afterTime = Time.fromHMS(8, 30, 0);
|
|
267
|
-
const tripIndex = route.findEarliestTrip(
|
|
236
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
268
237
|
assert.strictEqual(tripIndex, 1);
|
|
269
238
|
});
|
|
270
239
|
|
|
271
240
|
it('should find earliest trip with exact match time', () => {
|
|
272
241
|
const afterTime = Time.fromHMS(9, 1, 0);
|
|
273
|
-
const tripIndex = route.findEarliestTrip(
|
|
242
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
274
243
|
assert.strictEqual(tripIndex, 1);
|
|
275
244
|
});
|
|
276
245
|
|
|
277
246
|
it('should return undefined when no trip is available after specified time', () => {
|
|
278
247
|
const afterTime = Time.fromHMS(23, 0, 0);
|
|
279
|
-
const tripIndex = route.findEarliestTrip(
|
|
248
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
280
249
|
assert.strictEqual(tripIndex, undefined);
|
|
281
250
|
});
|
|
282
251
|
|
|
283
252
|
it('should skip trips where pickup is not available', () => {
|
|
284
|
-
const tripIndex = route.findEarliestTrip(
|
|
285
|
-
// Trip 0 has NOT_AVAILABLE pickup at stop
|
|
253
|
+
const tripIndex = route.findEarliestTrip(1);
|
|
254
|
+
// Trip 0 has NOT_AVAILABLE pickup at stop index 1, so should return trip 1
|
|
286
255
|
assert.strictEqual(tripIndex, 1);
|
|
287
256
|
});
|
|
288
257
|
|
|
289
258
|
it('should respect beforeTrip constraint', () => {
|
|
290
|
-
const tripIndex = route.findEarliestTrip(
|
|
259
|
+
const tripIndex = route.findEarliestTrip(0, Time.fromHMS(8, 2, 0), 1);
|
|
291
260
|
assert.strictEqual(tripIndex, undefined);
|
|
292
261
|
});
|
|
293
262
|
|
|
294
263
|
it('should return undefined when beforeTrip is 0', () => {
|
|
295
|
-
const tripIndex = route.findEarliestTrip(
|
|
264
|
+
const tripIndex = route.findEarliestTrip(0, Time.origin(), 0);
|
|
296
265
|
assert.strictEqual(tripIndex, undefined);
|
|
297
266
|
});
|
|
298
267
|
|
|
299
268
|
it('should handle MUST_PHONE_AGENCY pickup type', () => {
|
|
300
269
|
const afterTime = Time.fromHMS(9, 30, 0);
|
|
301
|
-
const tripIndex = route.findEarliestTrip(
|
|
270
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
302
271
|
// Should find trip 2 even though it requires phone agency
|
|
303
272
|
assert.strictEqual(tripIndex, 2);
|
|
304
273
|
});
|
|
305
274
|
|
|
306
|
-
it('should throw error for invalid stop
|
|
275
|
+
it('should throw error for invalid stop index', () => {
|
|
307
276
|
assert.throws(
|
|
308
|
-
() => route.findEarliestTrip(
|
|
309
|
-
/
|
|
277
|
+
() => route.findEarliestTrip(999),
|
|
278
|
+
/StopId for stop at index 999 not found/,
|
|
310
279
|
);
|
|
311
280
|
});
|
|
312
281
|
});
|
|
282
|
+
|
|
283
|
+
describe('stopRouteIndices', () => {
|
|
284
|
+
it('should return correct stop route indices for existing stop', () => {
|
|
285
|
+
const indices = route.stopRouteIndices(1001);
|
|
286
|
+
assert.deepStrictEqual(indices, [0]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return correct stop route indices for second stop', () => {
|
|
290
|
+
const indices = route.stopRouteIndices(1002);
|
|
291
|
+
assert.deepStrictEqual(indices, [1]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should return empty array for non-existent stop', () => {
|
|
295
|
+
const indices = route.stopRouteIndices(9999);
|
|
296
|
+
assert.deepStrictEqual(indices, []);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
313
299
|
});
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
Timetable,
|
|
12
12
|
TripBoarding,
|
|
13
13
|
} from '../timetable.js';
|
|
14
|
-
import { encode } from '../
|
|
14
|
+
import { encode } from '../tripBoardingId.js';
|
|
15
15
|
|
|
16
16
|
describe('Timetable', () => {
|
|
17
17
|
const stopsAdjacency: StopAdjacency[] = [
|
|
@@ -100,6 +100,7 @@ describe('Timetable', () => {
|
|
|
100
100
|
stopsAdjacency,
|
|
101
101
|
routesAdjacency,
|
|
102
102
|
routes,
|
|
103
|
+
new Map(),
|
|
103
104
|
);
|
|
104
105
|
|
|
105
106
|
it('should serialize a timetable to a Uint8Array', () => {
|
|
@@ -116,7 +117,7 @@ describe('Timetable', () => {
|
|
|
116
117
|
it('should find the earliest trip for stop1 on route1', () => {
|
|
117
118
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
118
119
|
const route = sampleTimetable.getRoute(0)!;
|
|
119
|
-
const tripIndex = route.findEarliestTrip(
|
|
120
|
+
const tripIndex = route.findEarliestTrip(0);
|
|
120
121
|
assert.strictEqual(tripIndex, 0);
|
|
121
122
|
});
|
|
122
123
|
|
|
@@ -124,7 +125,7 @@ describe('Timetable', () => {
|
|
|
124
125
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
125
126
|
const route = sampleTimetable.getRoute(0)!;
|
|
126
127
|
const afterTime = Time.fromHMS(17, 0, 0);
|
|
127
|
-
const tripIndex = route.findEarliestTrip(
|
|
128
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
128
129
|
assert.strictEqual(tripIndex, 1);
|
|
129
130
|
});
|
|
130
131
|
|
|
@@ -132,13 +133,13 @@ describe('Timetable', () => {
|
|
|
132
133
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
133
134
|
const route = sampleTimetable.getRoute(0)!;
|
|
134
135
|
const afterTime = Time.fromHMS(23, 40, 0);
|
|
135
|
-
const tripIndex = route.findEarliestTrip(
|
|
136
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
136
137
|
assert.strictEqual(tripIndex, undefined);
|
|
137
138
|
});
|
|
138
139
|
it('should return undefined if the stop on a trip has pick up not available', () => {
|
|
139
140
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
140
141
|
const route = sampleTimetable.getRoute(0)!;
|
|
141
|
-
const tripIndex = route.findEarliestTrip(
|
|
142
|
+
const tripIndex = route.findEarliestTrip(1);
|
|
142
143
|
assert.strictEqual(tripIndex, 1);
|
|
143
144
|
});
|
|
144
145
|
describe('findReachableRoutes', () => {
|
|
@@ -149,7 +150,7 @@ describe('Timetable', () => {
|
|
|
149
150
|
assert.deepStrictEqual(
|
|
150
151
|
reachableRoutes,
|
|
151
152
|
new Map([
|
|
152
|
-
[route1,
|
|
153
|
+
[route1, 0],
|
|
153
154
|
[route2, 1],
|
|
154
155
|
]),
|
|
155
156
|
);
|
|
@@ -163,8 +164,8 @@ describe('Timetable', () => {
|
|
|
163
164
|
assert.deepStrictEqual(
|
|
164
165
|
reachableRoutes,
|
|
165
166
|
new Map([
|
|
166
|
-
[route1,
|
|
167
|
-
[route2,
|
|
167
|
+
[route1, 0],
|
|
168
|
+
[route2, 0],
|
|
168
169
|
]),
|
|
169
170
|
);
|
|
170
171
|
});
|
|
@@ -201,7 +202,7 @@ describe('Timetable', () => {
|
|
|
201
202
|
assert.deepStrictEqual(
|
|
202
203
|
railRoutes,
|
|
203
204
|
new Map([
|
|
204
|
-
[route1,
|
|
205
|
+
[route1, 0],
|
|
205
206
|
[route2, 1],
|
|
206
207
|
]),
|
|
207
208
|
);
|
|
@@ -226,33 +227,34 @@ describe('Timetable', () => {
|
|
|
226
227
|
const fromStops = new Set([1, 2]); // Both stops are on route1
|
|
227
228
|
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
228
229
|
|
|
229
|
-
// route1 should use stop
|
|
230
|
-
// route2 should use stop
|
|
230
|
+
// route1 should use stop index 0 (stop 1 comes before stop 2 on route1)
|
|
231
|
+
// route2 should use stop index 0 (stop 2 comes before stop 1 on route2)
|
|
231
232
|
assert.strictEqual(reachableRoutes.size, 2);
|
|
232
233
|
assert.deepStrictEqual(
|
|
233
234
|
reachableRoutes,
|
|
234
235
|
new Map([
|
|
235
|
-
[route1,
|
|
236
|
-
[route2,
|
|
236
|
+
[route1, 0], // Stop index 0 (stop 1) comes before stop index 1 (stop 2) on route1
|
|
237
|
+
[route2, 0], // Stop index 0 (stop 2) comes before stop index 1 (stop 1) on route2
|
|
237
238
|
]),
|
|
238
239
|
);
|
|
239
240
|
});
|
|
240
241
|
|
|
241
242
|
describe('getContinuousTrips', () => {
|
|
242
243
|
it('should return empty array when stop has no trip continuations', () => {
|
|
243
|
-
const continuousTrips = sampleTimetable.getContinuousTrips(
|
|
244
|
+
const continuousTrips = sampleTimetable.getContinuousTrips(0, 0, 0);
|
|
244
245
|
assert.deepStrictEqual(continuousTrips, []);
|
|
245
246
|
});
|
|
246
247
|
|
|
247
248
|
it('should return empty array when stop has trip continuations but not for the specified trip', () => {
|
|
248
249
|
// Create a timetable with trip continuations that don't match the query
|
|
250
|
+
const tripContinuationsMap = new Map([
|
|
251
|
+
[encode(0, 0, 1), [{ hopOnStopIndex: 0, routeId: 1, tripIndex: 0 }]], // Different trip index
|
|
252
|
+
]);
|
|
253
|
+
|
|
249
254
|
const stopsWithContinuations: StopAdjacency[] = [
|
|
250
255
|
{ routes: [] },
|
|
251
256
|
{
|
|
252
257
|
routes: [0, 1],
|
|
253
|
-
tripContinuations: new Map([
|
|
254
|
-
[encode(0, 1), [{ hopOnStop: 2, routeId: 1, tripIndex: 0 }]], // Different trip index
|
|
255
|
-
]),
|
|
256
258
|
},
|
|
257
259
|
{ routes: [1] },
|
|
258
260
|
];
|
|
@@ -261,10 +263,11 @@ describe('Timetable', () => {
|
|
|
261
263
|
stopsWithContinuations,
|
|
262
264
|
routesAdjacency,
|
|
263
265
|
routes,
|
|
266
|
+
tripContinuationsMap,
|
|
264
267
|
);
|
|
265
268
|
|
|
266
269
|
const continuousTrips = timetableWithContinuations.getContinuousTrips(
|
|
267
|
-
|
|
270
|
+
0,
|
|
268
271
|
0,
|
|
269
272
|
0,
|
|
270
273
|
); // Query trip index 0, but continuations are for trip index 1
|
|
@@ -273,17 +276,18 @@ describe('Timetable', () => {
|
|
|
273
276
|
|
|
274
277
|
it('should return trip continuations when they exist for the specified trip', () => {
|
|
275
278
|
const expectedContinuations: TripBoarding[] = [
|
|
276
|
-
{
|
|
277
|
-
{
|
|
279
|
+
{ hopOnStopIndex: 0, routeId: 1, tripIndex: 0 },
|
|
280
|
+
{ hopOnStopIndex: 0, routeId: 1, tripIndex: 1 },
|
|
278
281
|
];
|
|
279
282
|
|
|
283
|
+
const tripContinuationsMap = new Map([
|
|
284
|
+
[encode(0, 0, 0), expectedContinuations],
|
|
285
|
+
]);
|
|
286
|
+
|
|
280
287
|
const stopsWithContinuations: StopAdjacency[] = [
|
|
281
288
|
{ routes: [] },
|
|
282
289
|
{
|
|
283
290
|
routes: [0, 1],
|
|
284
|
-
tripContinuations: new Map([
|
|
285
|
-
[encode(0, 0), expectedContinuations], // Trip continuations for route 0, trip 0
|
|
286
|
-
]),
|
|
287
291
|
},
|
|
288
292
|
{ routes: [1] },
|
|
289
293
|
];
|
|
@@ -292,21 +296,20 @@ describe('Timetable', () => {
|
|
|
292
296
|
stopsWithContinuations,
|
|
293
297
|
routesAdjacency,
|
|
294
298
|
routes,
|
|
299
|
+
tripContinuationsMap,
|
|
295
300
|
);
|
|
296
301
|
|
|
297
302
|
const continuousTrips = timetableWithContinuations.getContinuousTrips(
|
|
298
|
-
|
|
303
|
+
0,
|
|
299
304
|
0,
|
|
300
305
|
0,
|
|
301
306
|
);
|
|
302
307
|
assert.deepStrictEqual(continuousTrips, expectedContinuations);
|
|
303
308
|
});
|
|
304
309
|
|
|
305
|
-
it('should
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
/Stop ID 999 not found/,
|
|
309
|
-
);
|
|
310
|
+
it('should return empty array when querying with non-matching parameters', () => {
|
|
311
|
+
const continuousTrips = sampleTimetable.getContinuousTrips(999, 0, 0);
|
|
312
|
+
assert.deepStrictEqual(continuousTrips, []);
|
|
310
313
|
});
|
|
311
314
|
});
|
|
312
315
|
});
|