minotor 7.0.1 → 8.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 (59) 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 +1268 -290
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +13 -4
  7. package/dist/gtfs/trips.d.ts +12 -7
  8. package/dist/parser.cjs.js +519 -95
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +519 -95
  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__/tripId.test.d.ts +1 -0
  25. package/dist/timetable/io.d.ts +4 -2
  26. package/dist/timetable/proto/timetable.d.ts +13 -1
  27. package/dist/timetable/route.d.ts +41 -8
  28. package/dist/timetable/timetable.d.ts +18 -3
  29. package/dist/timetable/tripId.d.ts +15 -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 +259 -1
  35. package/src/gtfs/__tests__/transfers.test.ts +468 -12
  36. package/src/gtfs/__tests__/trips.test.ts +350 -28
  37. package/src/gtfs/parser.ts +16 -4
  38. package/src/gtfs/transfers.ts +61 -18
  39. package/src/gtfs/trips.ts +97 -22
  40. package/src/router.ts +2 -2
  41. package/src/routing/__tests__/plotter.test.ts +230 -0
  42. package/src/routing/__tests__/result.test.ts +486 -125
  43. package/src/routing/__tests__/route.test.ts +7 -3
  44. package/src/routing/__tests__/router.test.ts +378 -172
  45. package/src/routing/plotter.ts +279 -48
  46. package/src/routing/result.ts +114 -34
  47. package/src/routing/route.ts +0 -3
  48. package/src/routing/router.ts +332 -209
  49. package/src/timetable/__tests__/io.test.ts +33 -1
  50. package/src/timetable/__tests__/route.test.ts +10 -3
  51. package/src/timetable/__tests__/timetable.test.ts +225 -57
  52. package/src/timetable/__tests__/tripId.test.ts +27 -0
  53. package/src/timetable/io.ts +71 -10
  54. package/src/timetable/proto/timetable.proto +14 -2
  55. package/src/timetable/proto/timetable.ts +218 -20
  56. package/src/timetable/route.ts +152 -19
  57. package/src/timetable/time.ts +23 -9
  58. package/src/timetable/timetable.ts +46 -9
  59. package/src/timetable/tripId.ts +29 -0
@@ -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:019e5a579ce9e464269f1d80a9fc3fb6152e284f6dfbbf98e55a9cda6f2471b1
3
+ size 17595355
package/src/cli/repl.ts CHANGED
@@ -3,6 +3,9 @@ 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';
8
+ import type { TripBoarding } from '../timetable/timetable.js';
6
9
  import { plotGraphToDotFile } from './utils.js';
7
10
 
8
11
  export const startRepl = (stopsPath: string, timetablePath: string) => {
@@ -113,7 +116,7 @@ export const startRepl = (stopsPath: string, timetablePath: string) => {
113
116
  console.log(`Destination not reachable`);
114
117
  } else {
115
118
  console.log(
116
- `Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${stopsIndex.findStopById(arrivalTime.origin)?.name}.`,
119
+ `Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${fromStop.name}.`,
117
120
  );
118
121
  }
119
122
  const bestRoute = result.bestRoute(toStop.sourceStopId);
@@ -121,6 +124,7 @@ export const startRepl = (stopsPath: string, timetablePath: string) => {
121
124
  if (bestRoute) {
122
125
  console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
123
126
  console.log(bestRoute.toString());
127
+ console.log(bestRoute.asJson());
124
128
  } else {
125
129
  console.log('No route found');
126
130
  }
@@ -212,4 +216,258 @@ export const startRepl = (stopsPath: string, timetablePath: string) => {
212
216
  this.displayPrompt();
213
217
  },
214
218
  });
219
+
220
+ const formatPickupDropoffType = (type: string): string => {
221
+ switch (type) {
222
+ case 'REGULAR':
223
+ return 'R';
224
+ case 'NOT_AVAILABLE':
225
+ return 'N';
226
+ case 'MUST_PHONE_AGENCY':
227
+ return 'A';
228
+ case 'MUST_COORDINATE_WITH_DRIVER':
229
+ return 'D';
230
+ default:
231
+ return '?';
232
+ }
233
+ };
234
+
235
+ replServer.defineCommand('inspect', {
236
+ help: 'Inspect a route or stop using .inspect route <routeId> or .inspect stop <stopId>',
237
+ action(inspectQuery: string) {
238
+ this.clearBufferedCommand();
239
+
240
+ const parts = inspectQuery.trim().split(' ');
241
+ if (parts.length !== 2) {
242
+ console.log(
243
+ 'Usage: .inspect route <routeId> or .inspect stop <stopId>',
244
+ );
245
+ this.displayPrompt();
246
+ return;
247
+ }
248
+
249
+ const [type, idStr] = parts;
250
+ if (type !== 'route' && type !== 'stop') {
251
+ console.log(
252
+ 'Usage: .inspect route <routeId> or .inspect stop <stopId>',
253
+ );
254
+ this.displayPrompt();
255
+ return;
256
+ }
257
+
258
+ const inspectRoute = (routeIdStr: string) => {
259
+ const routeId = parseInt(routeIdStr.trim());
260
+ if (isNaN(routeId)) {
261
+ console.log('Usage: .inspect route <routeId>');
262
+ return;
263
+ }
264
+
265
+ const route = timetable.getRoute(routeId);
266
+ if (!route) {
267
+ console.log(`Route ${routeId} not found`);
268
+ return;
269
+ }
270
+
271
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
272
+ const routeName = serviceRouteInfo.name;
273
+ const routeType = serviceRouteInfo.type;
274
+
275
+ console.log(`\n=== Route ${routeId} ===`);
276
+ console.log(`Service Route: ${routeName}`);
277
+ console.log(`Type: ${routeType}`);
278
+ console.log(`Number of stops: ${route.getNbStops()}`);
279
+ console.log(`Number of trips: ${route.getNbTrips()}`);
280
+
281
+ console.log('\n--- Stops ---');
282
+ for (let i = 0; i < route.stops.length; i++) {
283
+ const stopId = route.stopId(i);
284
+ const stop = stopsIndex.findStopById(stopId);
285
+ const platform = stop?.platform ? ` (Pl. ${stop.platform})` : '';
286
+ console.log(
287
+ `${i + 1}. ${stop?.name ?? 'Unknown'}${platform} (${stopId}, ${stop?.sourceStopId ?? 'N/A'})`,
288
+ );
289
+ }
290
+
291
+ console.log('\n--- Trips ---');
292
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
293
+ console.log(`\nTrip ${tripIndex}:`);
294
+ for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
295
+ const stopId = route.stopId(stopIndex);
296
+ const stop = stopsIndex.findStopById(stopId);
297
+
298
+ const departure = route.departureFrom(stopId, tripIndex);
299
+ const arrival = route.arrivalAt(stopId, tripIndex);
300
+ const pickupType = route.pickUpTypeFrom(stopId, tripIndex);
301
+ const dropOffType = route.dropOffTypeAt(stopId, tripIndex);
302
+
303
+ const pickupStr = formatPickupDropoffType(pickupType);
304
+ const dropOffStr = formatPickupDropoffType(dropOffType);
305
+
306
+ console.log(
307
+ ` ${stopIndex + 1}. ${stop?.name ?? 'Unknown'}: 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
+ const continuationsByTrip = new Map<
396
+ string,
397
+ {
398
+ route: Route;
399
+ tripIndex: number;
400
+ continuations: TripBoarding[];
401
+ }
402
+ >();
403
+
404
+ routes.forEach((route: Route) => {
405
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
406
+ const continuations = timetable.getContinuousTrips(
407
+ stop.id,
408
+ route.id,
409
+ tripIndex,
410
+ );
411
+ if (continuations.length > 0) {
412
+ totalContinuations += continuations.length;
413
+ const tripKey = `${route.id}-${tripIndex}`;
414
+ continuationsByTrip.set(tripKey, {
415
+ route,
416
+ tripIndex,
417
+ continuations,
418
+ });
419
+ }
420
+ }
421
+ });
422
+
423
+ console.log(`Number of trip continuations: ${totalContinuations}`);
424
+
425
+ if (totalContinuations > 0) {
426
+ console.log('\n--- Trip Continuations ---');
427
+ let continuationIndex = 1;
428
+ for (const [, value] of continuationsByTrip) {
429
+ const { route, tripIndex, continuations } = value;
430
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
431
+
432
+ for (const continuation of continuations) {
433
+ const destStop = stopsIndex.findStopById(continuation.hopOnStop);
434
+ const destPlatform = destStop?.platform
435
+ ? ` (Pl. ${destStop.platform})`
436
+ : '';
437
+
438
+ const destRoute = timetable.getRoute(continuation.routeId);
439
+ const destServiceRouteInfo = destRoute
440
+ ? timetable.getServiceRouteInfo(destRoute)
441
+ : null;
442
+
443
+ const originTime = route.departureFrom(stop.id, tripIndex);
444
+ const continuationTime = destRoute?.departureFrom(
445
+ continuation.hopOnStop,
446
+ continuation.tripIndex,
447
+ );
448
+ const continuationTimeStr = continuationTime
449
+ ? ` at ${continuationTime.toString()}`
450
+ : '';
451
+ console.log(
452
+ `${continuationIndex}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
453
+ `Route ${continuation.routeId} (${destServiceRouteInfo?.name ?? 'Unknown'}) Trip ${continuation.tripIndex}${continuationTimeStr} ` +
454
+ `at ${destStop?.name ?? 'Unknown'}${destPlatform} (${continuation.hopOnStop}, ${destStop?.sourceStopId ?? 'N/A'})`,
455
+ );
456
+ continuationIndex++;
457
+ }
458
+ }
459
+ }
460
+
461
+ console.log();
462
+ };
463
+
464
+ if (type === 'route') {
465
+ inspectRoute(idStr ?? '');
466
+ } else {
467
+ inspectStop(idStr ?? '');
468
+ }
469
+
470
+ this.displayPrompt();
471
+ },
472
+ });
215
473
  };