minotor 7.0.2 → 9.0.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.
Files changed (60) hide show
  1. package/.cspell.json +11 -1
  2. package/CHANGELOG.md +8 -3
  3. package/README.md +26 -24
  4. package/dist/cli.mjs +1786 -791
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +29 -5
  7. package/dist/gtfs/trips.d.ts +10 -5
  8. package/dist/parser.cjs.js +972 -525
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +972 -525
  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.d.ts +2 -2
  15. package/dist/router.esm.js +1 -1
  16. package/dist/router.esm.js.map +1 -1
  17. package/dist/router.umd.js +1 -1
  18. package/dist/router.umd.js.map +1 -1
  19. package/dist/routing/__tests__/plotter.test.d.ts +1 -0
  20. package/dist/routing/plotter.d.ts +42 -3
  21. package/dist/routing/result.d.ts +23 -7
  22. package/dist/routing/route.d.ts +2 -0
  23. package/dist/routing/router.d.ts +78 -19
  24. package/dist/timetable/__tests__/tripBoardingId.test.d.ts +1 -0
  25. package/dist/timetable/io.d.ts +4 -2
  26. package/dist/timetable/proto/timetable.d.ts +15 -1
  27. package/dist/timetable/route.d.ts +48 -23
  28. package/dist/timetable/timetable.d.ts +24 -7
  29. package/dist/timetable/tripBoardingId.d.ts +34 -0
  30. package/package.json +1 -1
  31. package/src/__e2e__/router.test.ts +114 -105
  32. package/src/__e2e__/timetable/stops.bin +2 -2
  33. package/src/__e2e__/timetable/timetable.bin +2 -2
  34. package/src/cli/repl.ts +245 -1
  35. package/src/gtfs/__tests__/parser.test.ts +19 -4
  36. package/src/gtfs/__tests__/transfers.test.ts +773 -37
  37. package/src/gtfs/__tests__/trips.test.ts +308 -27
  38. package/src/gtfs/parser.ts +36 -6
  39. package/src/gtfs/transfers.ts +193 -19
  40. package/src/gtfs/trips.ts +58 -21
  41. package/src/router.ts +2 -2
  42. package/src/routing/__tests__/plotter.test.ts +230 -0
  43. package/src/routing/__tests__/result.test.ts +486 -125
  44. package/src/routing/__tests__/route.test.ts +7 -3
  45. package/src/routing/__tests__/router.test.ts +380 -172
  46. package/src/routing/plotter.ts +279 -48
  47. package/src/routing/result.ts +114 -34
  48. package/src/routing/route.ts +0 -3
  49. package/src/routing/router.ts +344 -211
  50. package/src/timetable/__tests__/io.test.ts +34 -1
  51. package/src/timetable/__tests__/route.test.ts +74 -81
  52. package/src/timetable/__tests__/timetable.test.ts +232 -61
  53. package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
  54. package/src/timetable/io.ts +72 -10
  55. package/src/timetable/proto/timetable.proto +16 -2
  56. package/src/timetable/proto/timetable.ts +256 -22
  57. package/src/timetable/route.ts +174 -58
  58. package/src/timetable/timetable.ts +66 -16
  59. package/src/timetable/tripBoardingId.ts +94 -0
  60. package/tsconfig.json +2 -2
@@ -7,160 +7,169 @@ import { Query, Router, StopsIndex, Time, Timetable } from '../router.js';
7
7
 
8
8
  const routes = [
9
9
  {
10
- from: 'Parent8504100', // Fribourg/Freiburg
11
- to: 'Parent8504748', // Le Moléson
10
+ from: 'Parent8504100',
11
+ to: 'Parent8504880',
12
12
  at: '08:30',
13
13
  route: [
14
14
  {
15
- from: '8504100:0:2', // Fribourg/Freiburg, Pl. 2
16
- to: '8504086:0:2', // Bulle, Pl. 2
15
+ from: '8504100:0:2',
16
+ to: '8504086:0:2',
17
17
  departure: '08:34',
18
18
  arrival: '09:11',
19
- route: {
20
- type: 'RAIL',
21
- name: 'RE2',
22
- },
19
+ route: { type: 'RAIL', name: 'RE2' },
23
20
  },
24
21
  {
25
- from: '8504086:0:2', // Bulle, Pl. 2
26
- to: '8504086:0:4', // Bulle, Pl. 4
22
+ from: '8504086:0:2',
23
+ to: '8504086:0:4',
27
24
  type: 'REQUIRES_MINIMAL_TIME',
28
25
  minTransferTime: '03:00',
29
26
  },
30
27
  {
31
- from: '8504086:0:4', // Bulle, Pl. 4
32
- to: '8504077:0:1', // Gruyères, Pl. 1
28
+ from: '8504086:0:4',
29
+ to: '8504077:0:1',
33
30
  departure: '09:20',
34
31
  arrival: '09:28',
35
- route: {
36
- type: 'RAIL',
37
- name: 'S51',
38
- },
32
+ route: { type: 'RAIL', name: 'S51' },
39
33
  },
40
34
  {
41
- from: '8504077:0:1', // Gruyères, Pl. 1
42
- to: '8577737', // Gruyères, gare
35
+ from: '8504077:0:1',
36
+ to: '8577737',
43
37
  type: 'REQUIRES_MINIMAL_TIME',
44
38
  minTransferTime: '02:00',
45
39
  },
46
40
  {
47
- from: '8577737', // Gruyères, gare
48
- to: '8504880', // Moléson-sur-Gruyères
41
+ from: '8577737',
42
+ to: '8504880',
49
43
  departure: '09:33',
50
44
  arrival: '09:44',
51
- route: {
52
- type: 'BUS',
53
- name: '263',
54
- },
55
- },
56
- {
57
- from: '8504880', // Moléson-sur-Gruyères
58
- to: '8530024', // Moléson-sur-Gruyères (funi)
59
- type: 'REQUIRES_MINIMAL_TIME',
60
- minTransferTime: '02:00',
61
- },
62
- {
63
- from: '8530024', // Moléson-sur-Gruyères (funi)
64
- to: '8504749', // Plan-Francey
65
- departure: '10:00',
66
- arrival: '10:05',
67
- route: {
68
- type: 'FUNICULAR',
69
- name: 'FUN',
70
- },
71
- },
72
- {
73
- from: '8504749', // Plan-Francey
74
- to: '8531209', // Plan-Francey (téléphérique)
75
- type: 'REQUIRES_MINIMAL_TIME',
76
- minTransferTime: '02:00',
77
- },
78
- {
79
- from: '8531209', // Plan-Francey (téléphérique)
80
- to: '8504748', // Le Moléson
81
- departure: '10:10',
82
- arrival: '10:15',
83
- route: {
84
- type: 'AERIAL_LIFT',
85
- name: 'PB',
86
- },
45
+ route: { type: 'BUS', name: '263' },
87
46
  },
88
47
  ],
89
48
  },
90
49
  {
91
- from: 'Parent8507000', // Bern
92
- to: 'Parent8509253', // St. Moritz
50
+ from: 'Parent8507000',
51
+ to: 'Parent8509253',
93
52
  at: '12:30',
94
53
  route: [
95
54
  {
96
- from: '8507000:0:8', // Bern, Pl.8
97
- to: '8503000:0:33', // Zürich HB, Pl. 33
55
+ from: '8507000:0:8',
56
+ to: '8503000:0:33',
98
57
  departure: '12:31',
99
58
  arrival: '13:28',
100
- route: {
101
- type: 'RAIL',
102
- name: 'IC1',
103
- },
59
+ route: { type: 'RAIL', name: 'IC1' },
104
60
  },
105
61
  {
106
- from: '8503000:0:33', // Zürich HB, Pl. 33
107
- to: '8503000:0:9', // Zürich HB, Pl. 9
62
+ from: '8503000:0:33',
63
+ to: '8503000:0:9',
108
64
  type: 'REQUIRES_MINIMAL_TIME',
109
65
  minTransferTime: '07:00',
110
66
  },
111
67
  {
112
- from: '8503000:0:9', // Zürich HB, Pl. 9
113
- to: '8509000:0:9', // Chur, Pl. 9
68
+ from: '8503000:0:9',
69
+ to: '8509002:0:2',
114
70
  departure: '13:38',
115
- arrival: '14:52',
116
- route: {
117
- type: 'RAIL',
118
- name: 'IC3',
119
- },
71
+ arrival: '14:41',
72
+ route: { type: 'RAIL', name: 'IC3' },
120
73
  },
121
74
  {
122
- from: '8509000:0:9', // Chur, Pl. 9
123
- to: '8509000:0:10', // Chur, Pl. 10
75
+ from: '8509002:0:2',
76
+ to: '8509002:0:6',
124
77
  type: 'REQUIRES_MINIMAL_TIME',
125
- minTransferTime: '03:00',
78
+ minTransferTime: '04:00',
79
+ },
80
+ {
81
+ from: '8509002:0:6',
82
+ to: '8509269:0:3',
83
+ departure: '14:49',
84
+ arrival: '15:52',
85
+ route: { type: 'RAIL', name: 'RE24' },
126
86
  },
127
87
  {
128
- from: '8509000:0:10', // Chur, Pl. 10
129
- to: '8509253:0:2', // St-Moritz, Pl. 2
130
- departure: '14:58',
131
- arrival: '16:56',
132
- route: {
133
- type: 'RAIL',
134
- name: 'IR38',
135
- },
88
+ from: '8509269:0:3',
89
+ to: '8509269:0:4',
90
+ type: 'REQUIRES_MINIMAL_TIME',
91
+ minTransferTime: '01:00',
92
+ },
93
+ {
94
+ from: '8509269:0:4',
95
+ to: '8509251:0:3',
96
+ departure: '15:54',
97
+ arrival: '16:43',
98
+ route: { type: 'RAIL', name: 'R15' },
99
+ },
100
+ {
101
+ from: '8509251:0:3',
102
+ to: '8509251:0:2',
103
+ type: 'REQUIRES_MINIMAL_TIME',
104
+ minTransferTime: '01:00',
105
+ },
106
+ {
107
+ from: '8509251:0:2',
108
+ to: '8509253:0:1',
109
+ departure: '16:48',
110
+ arrival: '17:00',
111
+ route: { type: 'RAIL', name: 'IR38' },
136
112
  },
137
113
  ],
138
114
  },
139
115
  {
140
- // Cross-border train as two routes (TODO, check if there is an in-seat transfer modeled for it)
141
- from: 'Parent8500010', // Basel SBB
142
- to: 'Parent8721202', // Strasbourg
116
+ from: 'Parent8500010',
117
+ to: 'Parent8721202',
143
118
  at: '16:50',
144
119
  route: [
145
120
  {
146
- from: '8500010:0:31', // Basel SBB, Pl. 31
147
- to: '8718213', // Saint-Louis (Haut-Rhin)
148
- departure: '16:50',
149
- arrival: '16:58',
150
- route: {
151
- type: 'RAIL',
152
- name: 'TER',
153
- },
154
- },
155
- {
156
- from: '8718213', // Saint-Louis (Haut-Rhin)
157
- to: '8721202', // Strasbourg
158
- departure: '16:59',
159
- arrival: '18:09',
160
- route: {
161
- type: 'RAIL',
162
- name: 'K200',
163
- },
121
+ from: '8500010:0:33',
122
+ to: '8718213',
123
+ departure: '17:08',
124
+ arrival: '17:15',
125
+ route: { type: 'RAIL', name: 'TER' },
126
+ },
127
+ {
128
+ from: '8718213',
129
+ to: '8721202',
130
+ departure: '17:30',
131
+ arrival: '18:39',
132
+ route: { type: 'RAIL', name: 'K200' },
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ from: 'Parent8504100',
138
+ to: 'Parent8509073',
139
+ at: '08:30',
140
+ route: [
141
+ {
142
+ from: '8504100:0:3',
143
+ to: '8503000:0:33',
144
+ departure: '09:03',
145
+ arrival: '10:28',
146
+ route: { type: 'RAIL', name: 'IC1' },
147
+ },
148
+ {
149
+ from: '8503000:0:33',
150
+ to: '8503000:0:10',
151
+ type: 'REQUIRES_MINIMAL_TIME',
152
+ minTransferTime: '07:00',
153
+ },
154
+ {
155
+ from: '8503000:0:10',
156
+ to: '8509002:0:2',
157
+ departure: '10:38',
158
+ arrival: '11:41',
159
+ route: { type: 'RAIL', name: 'IC3' },
160
+ },
161
+ {
162
+ from: '8509002:0:2',
163
+ to: '8509002:0:6',
164
+ type: 'REQUIRES_MINIMAL_TIME',
165
+ minTransferTime: '04:00',
166
+ },
167
+ {
168
+ from: '8509002:0:6',
169
+ to: '8509073:0:1',
170
+ departure: '11:49',
171
+ arrival: '13:03',
172
+ route: { type: 'RAIL', name: 'RE24' },
164
173
  },
165
174
  ],
166
175
  },
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9d01e1988e595441682589f20612343670f6c01d18df037c389d0946a4ca6e61
3
- size 5121353
2
+ oid sha256:18eaba61a6014cc31041103c3bcc74a4bebd37c234781c57238113a1de61ffd1
3
+ size 5147203
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c7552c57f15104abaa7a67adc4b2c12177cd5d34f2e7f826860b1d073b1b1f26
3
- size 18173715
2
+ oid sha256:b8c1539545653fd5a1e8d83f52f6b8383c8f1ed6087947105e3093334b046c0b
3
+ size 17521380
package/src/cli/repl.ts CHANGED
@@ -3,6 +3,8 @@ import repl from 'node:repl';
3
3
  import fs from 'fs';
4
4
 
5
5
  import { Query, Router, StopsIndex, Time, Timetable } from '../router.js';
6
+ import type { Stop } from '../stops/stops.js';
7
+ import { Route } from '../timetable/route.js';
6
8
  import { plotGraphToDotFile } from './utils.js';
7
9
 
8
10
  export const startRepl = (stopsPath: string, timetablePath: string) => {
@@ -113,7 +115,7 @@ export const startRepl = (stopsPath: string, timetablePath: string) => {
113
115
  console.log(`Destination not reachable`);
114
116
  } else {
115
117
  console.log(
116
- `Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${stopsIndex.findStopById(arrivalTime.origin)?.name}.`,
118
+ `Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${fromStop.name}.`,
117
119
  );
118
120
  }
119
121
  const bestRoute = result.bestRoute(toStop.sourceStopId);
@@ -212,4 +214,246 @@ export const startRepl = (stopsPath: string, timetablePath: string) => {
212
214
  this.displayPrompt();
213
215
  },
214
216
  });
217
+
218
+ const formatPickupDropoffType = (type: string): string => {
219
+ switch (type) {
220
+ case 'REGULAR':
221
+ return 'R';
222
+ case 'NOT_AVAILABLE':
223
+ return 'N';
224
+ case 'MUST_PHONE_AGENCY':
225
+ return 'A';
226
+ case 'MUST_COORDINATE_WITH_DRIVER':
227
+ return 'D';
228
+ default:
229
+ return '?';
230
+ }
231
+ };
232
+
233
+ replServer.defineCommand('inspect', {
234
+ help: 'Inspect a route or stop using .inspect route <routeId> or .inspect stop <stopId>',
235
+ action(inspectQuery: string) {
236
+ this.clearBufferedCommand();
237
+
238
+ const parts = inspectQuery.trim().split(' ');
239
+ if (parts.length !== 2) {
240
+ console.log(
241
+ 'Usage: .inspect route <routeId> or .inspect stop <stopId>',
242
+ );
243
+ this.displayPrompt();
244
+ return;
245
+ }
246
+
247
+ const [type, idStr] = parts;
248
+ if (type !== 'route' && type !== 'stop') {
249
+ console.log(
250
+ 'Usage: .inspect route <routeId> or .inspect stop <stopId>',
251
+ );
252
+ this.displayPrompt();
253
+ return;
254
+ }
255
+
256
+ const inspectRoute = (routeIdStr: string) => {
257
+ const routeId = parseInt(routeIdStr.trim());
258
+ if (isNaN(routeId)) {
259
+ console.log('Usage: .inspect route <routeId>');
260
+ return;
261
+ }
262
+
263
+ const route = timetable.getRoute(routeId);
264
+ if (!route) {
265
+ console.log(`Route ${routeId} not found`);
266
+ return;
267
+ }
268
+
269
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
270
+ const routeName = serviceRouteInfo.name;
271
+ const routeType = serviceRouteInfo.type;
272
+
273
+ console.log(`\n=== Route ${routeId} ===`);
274
+ console.log(`Service Route: ${routeName}`);
275
+ console.log(`Type: ${routeType}`);
276
+ console.log(`Number of stops: ${route.getNbStops()}`);
277
+ console.log(`Number of trips: ${route.getNbTrips()}`);
278
+
279
+ console.log('\n--- Stops ---');
280
+ for (let i = 0; i < route.stops.length; i++) {
281
+ const stopId = route.stopId(i);
282
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
283
+ const stop = stopsIndex.findStopById(stopId)!;
284
+ const platform = stop.platform ? ` (Pl. ${stop.platform})` : '';
285
+ console.log(
286
+ `${i + 1}. ${stop.name}${platform} (${stopId}, ${stop.sourceStopId})`,
287
+ );
288
+ }
289
+
290
+ console.log('\n--- Trips ---');
291
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
292
+ console.log(`\nTrip ${tripIndex}:`);
293
+ for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
294
+ const stopId = route.stopId(stopIndex);
295
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
296
+ const stop = stopsIndex.findStopById(stopId)!;
297
+
298
+ const departure = route.departureFrom(stopIndex, tripIndex);
299
+ const arrival = route.arrivalAt(stopIndex, tripIndex);
300
+ const pickupType = route.pickUpTypeFrom(stopIndex, tripIndex);
301
+ const dropOffType = route.dropOffTypeAt(stopIndex, tripIndex);
302
+
303
+ const pickupStr = formatPickupDropoffType(pickupType);
304
+ const dropOffStr = formatPickupDropoffType(dropOffType);
305
+
306
+ console.log(
307
+ ` ${stopIndex + 1}. ${stop.name}: arr ${arrival.toString()} (${pickupStr}) → dep ${departure.toString()} (${dropOffStr})`,
308
+ );
309
+ }
310
+ }
311
+
312
+ console.log();
313
+ };
314
+
315
+ const inspectStop = (stopIdStr: string) => {
316
+ let stop: Stop | undefined;
317
+ const stopBySourceId = stopsIndex.findStopBySourceStopId(stopIdStr);
318
+ if (stopBySourceId !== undefined) {
319
+ stop = stopBySourceId;
320
+ } else if (!isNaN(Number(stopIdStr))) {
321
+ const stopById = stopsIndex.findStopById(Number(stopIdStr));
322
+ if (stopById !== undefined) {
323
+ stop = stopById;
324
+ }
325
+ } else {
326
+ const stops = stopsIndex.findStopsByName(stopIdStr);
327
+ if (stops.length > 0) {
328
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
329
+ stop = stops[0]!;
330
+ }
331
+ }
332
+
333
+ if (!stop) {
334
+ console.log(`Stop not found: ${stopIdStr}`);
335
+ return;
336
+ }
337
+
338
+ console.log(`\n=== Stop ${stop.id} ===`);
339
+ console.log(`Name: ${stop.name}`);
340
+ if (stop.platform) {
341
+ console.log(`Platform: ${stop.platform}`);
342
+ }
343
+ console.log(`Source ID: ${stop.sourceStopId}`);
344
+
345
+ const routes: Route[] = timetable.routesPassingThrough(stop.id);
346
+ console.log(`Number of routes: ${routes.length}`);
347
+
348
+ const equivalentStops = stopsIndex
349
+ .equivalentStops(stop.sourceStopId)
350
+ .filter((equivStop) => equivStop.id !== stop.id);
351
+ console.log(`Number of equivalent stops: ${equivalentStops.length}`);
352
+
353
+ if (equivalentStops.length > 0) {
354
+ console.log('\n--- Equivalent Stops ---');
355
+ equivalentStops.forEach((equivStop, index) => {
356
+ const platform = equivStop.platform
357
+ ? ` (Pl. ${equivStop.platform})`
358
+ : '';
359
+ console.log(
360
+ `${index + 1}. ${equivStop.name}${platform} (${equivStop.id}, ${equivStop.sourceStopId})`,
361
+ );
362
+ });
363
+ }
364
+
365
+ if (routes.length > 0) {
366
+ console.log('\n--- Routes ---');
367
+ routes.forEach((route, index) => {
368
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
369
+ console.log(
370
+ `${index + 1}. Route ${route.id}: ${serviceRouteInfo.name} (${serviceRouteInfo.type})`,
371
+ );
372
+ });
373
+ }
374
+
375
+ const transfers = timetable.getTransfers(stop.id);
376
+ console.log(`Number of transfers: ${transfers.length}`);
377
+
378
+ if (transfers.length > 0) {
379
+ console.log('\n--- Transfers ---');
380
+ transfers.forEach((transfer, index) => {
381
+ const destStop = stopsIndex.findStopById(transfer.destination);
382
+ const platform = destStop?.platform
383
+ ? ` (Pl. ${destStop.platform})`
384
+ : '';
385
+ const minTime = transfer.minTransferTime
386
+ ? ` (min: ${Math.floor(transfer.minTransferTime.toSeconds() / 60)}min)`
387
+ : '';
388
+ console.log(
389
+ `${index + 1}. ${transfer.type} to ${destStop?.name ?? 'Unknown'}${platform} (${transfer.destination}, ${destStop?.sourceStopId ?? 'N/A'})${minTime}`,
390
+ );
391
+ });
392
+ }
393
+
394
+ let totalContinuations = 0;
395
+ console.log('\n--- Trip Continuations ---');
396
+
397
+ routes.forEach((route: Route) => {
398
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
399
+ const stopIndices = route.stopRouteIndices(stop.id);
400
+
401
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
402
+ for (const stopIndex of stopIndices) {
403
+ const continuations = timetable.getContinuousTrips(
404
+ stopIndex,
405
+ route.id,
406
+ tripIndex,
407
+ );
408
+
409
+ for (const continuation of continuations) {
410
+ totalContinuations++;
411
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
412
+ const destRoute = timetable.getRoute(continuation.routeId)!;
413
+ const destStopId = destRoute.stopId(
414
+ continuation.hopOnStopIndex,
415
+ );
416
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
417
+ const destStop = stopsIndex.findStopById(destStopId)!;
418
+ const destPlatform = destStop.platform
419
+ ? ` (Pl. ${destStop.platform})`
420
+ : '';
421
+
422
+ const destServiceRouteInfo =
423
+ timetable.getServiceRouteInfo(destRoute);
424
+
425
+ const originTime = route.departureFrom(stopIndex, tripIndex);
426
+ const continuationTime = destRoute.departureFrom(
427
+ continuation.hopOnStopIndex,
428
+ continuation.tripIndex,
429
+ );
430
+
431
+ console.log(
432
+ `${totalContinuations}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
433
+ `Route ${continuation.routeId} (${destServiceRouteInfo.name}) Trip ${continuation.tripIndex} at ${continuationTime.toString()} ` +
434
+ `at ${destStop.name}${destPlatform} (${destStopId}, ${destStop.sourceStopId})`,
435
+ );
436
+ }
437
+ }
438
+ }
439
+ });
440
+
441
+ if (totalContinuations === 0) {
442
+ console.log('No trip continuations found.');
443
+ } else {
444
+ console.log(`\nTotal trip continuations: ${totalContinuations}`);
445
+ }
446
+
447
+ console.log();
448
+ };
449
+
450
+ if (type === 'route') {
451
+ inspectRoute(idStr ?? '');
452
+ } else {
453
+ inspectStop(idStr ?? '');
454
+ }
455
+
456
+ this.displayPrompt();
457
+ },
458
+ });
215
459
  };
@@ -38,12 +38,27 @@ describe('GTFS parser', () => {
38
38
  const bullfrogId = stopsIndex.findStopBySourceStopId('BULLFROG')?.id;
39
39
  assert(beattyAirportId !== undefined && bullfrogId !== undefined);
40
40
  assert.strictEqual(route.getNbStops(), 2);
41
- assert(route.arrivalAt(beattyAirportId, 0).equals(Time.fromHMS(8, 0, 0)));
41
+
42
+ const beattyAirportStopIndex = route.stopRouteIndices(beattyAirportId)[0];
43
+ const bullfrogStopIndex = route.stopRouteIndices(bullfrogId)[0];
44
+ assert(
45
+ beattyAirportStopIndex !== undefined && bullfrogStopIndex !== undefined,
46
+ );
47
+
48
+ assert(
49
+ route.arrivalAt(beattyAirportStopIndex, 0).equals(Time.fromHMS(8, 0, 0)),
50
+ );
51
+ assert(
52
+ route
53
+ .departureFrom(beattyAirportStopIndex, 0)
54
+ .equals(Time.fromHMS(8, 0, 0)),
55
+ );
56
+ assert(
57
+ route.arrivalAt(bullfrogStopIndex, 0).equals(Time.fromHMS(8, 10, 0)),
58
+ );
42
59
  assert(
43
- route.departureFrom(beattyAirportId, 0).equals(Time.fromHMS(8, 0, 0)),
60
+ route.departureFrom(bullfrogStopIndex, 0).equals(Time.fromHMS(8, 15, 0)),
44
61
  );
45
- assert(route.arrivalAt(bullfrogId, 0).equals(Time.fromHMS(8, 10, 0)));
46
- assert(route.departureFrom(bullfrogId, 0).equals(Time.fromHMS(8, 15, 0)));
47
62
 
48
63
  const routes = timetable.routesPassingThrough(furCreekResId);
49
64
  assert.strictEqual(routes.length, 2);