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.
Files changed (50) hide show
  1. package/.github/workflows/minotor.yml +1 -1
  2. package/CHANGELOG.md +3 -8
  3. package/README.md +1 -1
  4. package/dist/cli.mjs +352 -256
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +21 -6
  7. package/dist/gtfs/trips.d.ts +2 -2
  8. package/dist/parser.cjs.js +296 -188
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +296 -188
  11. package/dist/parser.esm.js.map +1 -1
  12. package/dist/router.cjs.js +1 -1
  13. package/dist/router.cjs.js.map +1 -1
  14. package/dist/router.esm.js +1 -1
  15. package/dist/router.esm.js.map +1 -1
  16. package/dist/router.umd.js +1 -1
  17. package/dist/router.umd.js.map +1 -1
  18. package/dist/routing/router.d.ts +4 -4
  19. package/dist/timetable/io.d.ts +3 -3
  20. package/dist/timetable/proto/timetable.d.ts +6 -4
  21. package/dist/timetable/route.d.ts +13 -21
  22. package/dist/timetable/timetable.d.ts +13 -11
  23. package/dist/timetable/tripBoardingId.d.ts +34 -0
  24. package/package.json +1 -1
  25. package/src/__e2e__/timetable/timetable.bin +2 -2
  26. package/src/cli/repl.ts +53 -67
  27. package/src/gtfs/__tests__/parser.test.ts +19 -4
  28. package/src/gtfs/__tests__/transfers.test.ts +598 -318
  29. package/src/gtfs/__tests__/trips.test.ts +3 -44
  30. package/src/gtfs/parser.ts +26 -8
  31. package/src/gtfs/transfers.ts +151 -20
  32. package/src/gtfs/trips.ts +1 -39
  33. package/src/routing/__tests__/result.test.ts +10 -10
  34. package/src/routing/__tests__/router.test.ts +11 -9
  35. package/src/routing/result.ts +2 -2
  36. package/src/routing/router.ts +34 -22
  37. package/src/timetable/__tests__/io.test.ts +8 -7
  38. package/src/timetable/__tests__/route.test.ts +66 -80
  39. package/src/timetable/__tests__/timetable.test.ts +32 -29
  40. package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
  41. package/src/timetable/io.ts +21 -20
  42. package/src/timetable/proto/timetable.proto +6 -4
  43. package/src/timetable/proto/timetable.ts +84 -48
  44. package/src/timetable/route.ts +39 -56
  45. package/src/timetable/timetable.ts +37 -26
  46. package/src/timetable/tripBoardingId.ts +94 -0
  47. package/dist/timetable/tripId.d.ts +0 -15
  48. package/src/timetable/__tests__/tripId.test.ts +0 -27
  49. package/src/timetable/tripId.ts +0 -29
  50. /package/dist/timetable/__tests__/{tripId.test.d.ts → tripBoardingId.test.d.ts} +0 -0
@@ -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 { Route, RouteId, TripRouteIndex } from '../timetable/route.js';
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: StopId;
22
- to: StopId;
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, hopOnStop] of reachableRoutes) {
104
+ for (const [route, hopOnStopIndex] of reachableRoutes) {
100
105
  const newlyMarkedStops = this.scanRoute(
101
106
  route,
102
- hopOnStop,
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.hopOnStop,
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
- stopId,
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
- hopOnStop: trip.hopOnStop,
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 hopOnStop The stop ID where passengers can board the route
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
- hopOnStop: StopId,
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
- hopOnStop,
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 (let j = startIndex; j < route.getNbStops(); j++) {
263
- const currentStop = route.stops[j]!;
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(currentStop, activeTrip.tripIndex);
276
+ const arrivalTime = route.arrivalAt(
277
+ currentStopIndex,
278
+ activeTrip.tripIndex,
279
+ );
268
280
  const dropOffType = route.dropOffTypeAt(
269
- currentStop,
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.hopOnStop,
284
- to: currentStop,
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(currentStop, activeTrip.tripIndex),
327
+ route.departureFrom(currentStopIndex, activeTrip.tripIndex),
316
328
  ) ||
317
329
  earliestArrivalOnPreviousRound.equals(
318
- route.departureFrom(currentStop, activeTrip.tripIndex),
330
+ route.departureFrom(currentStopIndex, activeTrip.tripIndex),
319
331
  ))
320
332
  ) {
321
333
  const earliestTrip = route.findEarliestTrip(
322
- currentStop,
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
- hopOnStop: currentStop,
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<number, TripBoarding[]>();
124
- tripContinuations.set(0, [
125
- { hopOnStop: 1, routeId: 0, tripIndex: 2 },
126
- { hopOnStop: 3, routeId: 1, tripIndex: 1 },
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 1001', () => {
140
- const arrival = route.arrivalAt(1001, 0);
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 1002', () => {
148
- const arrival = route.arrivalAt(1002, 1);
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 ID', () => {
127
+ it('should throw error for invalid stop index', () => {
156
128
  assert.throws(
157
- () => route.arrivalAt(9999, 0),
158
- /Stop index for 9999 not found in route 0/,
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 1001', () => {
172
- const departure = route.departureFrom(1001, 0);
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 1002', () => {
180
- const departure = route.departureFrom(1002, 2);
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 ID', () => {
156
+ it('should throw error for invalid stop index', () => {
188
157
  assert.throws(
189
- () => route.departureFrom(9999, 0),
190
- /Stop index for 9999 not found in route 0/,
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(1001, 999),
197
- /Departure time not found for stop 1001 at trip index 999/,
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 1001', () => {
204
- const pickUpType = route.pickUpTypeFrom(1001, 0);
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 1002', () => {
209
- const pickUpType = route.pickUpTypeFrom(1002, 0);
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 1001', () => {
214
- const pickUpType = route.pickUpTypeFrom(1001, 2);
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 ID', () => {
187
+ it('should throw error for invalid stop index', () => {
219
188
  assert.throws(
220
- () => route.pickUpTypeFrom(9999, 0),
221
- /Stop index for 9999 not found in route 0/,
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(1001, 999),
228
- /Pick up type not found for stop 1001 at trip index 999/,
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 1001', () => {
235
- const dropOffType = route.dropOffTypeAt(1001, 0);
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 1002', () => {
240
- const dropOffType = route.dropOffTypeAt(1002, 1);
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 ID', () => {
213
+ it('should throw error for invalid stop index', () => {
245
214
  assert.throws(
246
- () => route.dropOffTypeAt(9999, 0),
247
- /Stop index for 9999 not found in route 0/,
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(1001, 999),
254
- /Drop off type not found for stop 1001 at trip index 999/,
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(1001);
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(1001, afterTime);
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(1001, afterTime);
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(1001, afterTime);
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(1002);
285
- // Trip 0 has NOT_AVAILABLE pickup at stop 1002, so should return trip 1
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(1001, Time.fromHMS(8, 2, 0), 1);
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(1001, Time.origin(), 0);
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(1001, afterTime);
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 ID', () => {
275
+ it('should throw error for invalid stop index', () => {
307
276
  assert.throws(
308
- () => route.findEarliestTrip(9999),
309
- /Stop index for 9999 not found in route 0/,
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 '../tripId.js';
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(1);
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(1, afterTime);
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(1, afterTime);
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(2);
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, 1],
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, 1],
167
- [route2, 2],
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, 1],
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 1 (earlier on the route than stop 2)
230
- // route2 should use stop 2 (earlier on route2 which has [2, 1])
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, 1], // Stop 1 comes before stop 2 on route1
236
- [route2, 2], // Stop 2 comes before stop 1 on 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(1, 0, 0);
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
- 1,
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
- { hopOnStop: 2, routeId: 1, tripIndex: 0 },
277
- { hopOnStop: 2, routeId: 1, tripIndex: 1 },
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
- 1,
303
+ 0,
299
304
  0,
300
305
  0,
301
306
  );
302
307
  assert.deepStrictEqual(continuousTrips, expectedContinuations);
303
308
  });
304
309
 
305
- it('should throw error when querying non-existent stop', () => {
306
- assert.throws(
307
- () => sampleTimetable.getContinuousTrips(999, 0, 0),
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
  });