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
@@ -5,7 +5,7 @@ import { Stop } from '../../stops/stops.js';
5
5
  import { Duration } from '../../timetable/duration.js';
6
6
  import { Time } from '../../timetable/time.js';
7
7
  import { ServiceRouteInfo, TransferType } from '../../timetable/timetable.js';
8
- import { Route } from '../route.js';
8
+ import { Route, VehicleLeg } from '../route.js';
9
9
 
10
10
  describe('Route', () => {
11
11
  const stopA: Stop = {
@@ -50,12 +50,14 @@ describe('Route', () => {
50
50
  name: 'Route 2',
51
51
  };
52
52
 
53
- const vehicleLeg = {
53
+ const vehicleLeg: VehicleLeg = {
54
54
  from: stopA,
55
55
  to: stopB,
56
56
  route: serviceRoute,
57
57
  departureTime: Time.fromHMS(8, 0, 0),
58
58
  arrivalTime: Time.fromHMS(8, 30, 0),
59
+ pickUpType: 'REGULAR',
60
+ dropOffType: 'REGULAR',
59
61
  };
60
62
 
61
63
  const transferLeg = {
@@ -65,12 +67,14 @@ describe('Route', () => {
65
67
  minTransferTime: Duration.fromMinutes(5),
66
68
  };
67
69
 
68
- const secondVehicleLeg = {
70
+ const secondVehicleLeg: VehicleLeg = {
69
71
  from: stopC,
70
72
  to: stopD,
71
73
  route: serviceRoute2,
72
74
  departureTime: Time.fromHMS(8, 40, 0),
73
75
  arrivalTime: Time.fromHMS(9, 0, 0),
76
+ pickUpType: 'REGULAR',
77
+ dropOffType: 'REGULAR',
74
78
  };
75
79
 
76
80
  it('should calculate the correct departure time', () => {
@@ -4,13 +4,15 @@ import { beforeEach, describe, it } from 'node:test';
4
4
  import { Stop } from '../../stops/stops.js';
5
5
  import { StopsIndex } from '../../stops/stopsIndex.js';
6
6
  import { Duration } from '../../timetable/duration.js';
7
- import { REGULAR, Route } from '../../timetable/route.js';
7
+ import { Route } from '../../timetable/route.js';
8
8
  import { Time } from '../../timetable/time.js';
9
9
  import {
10
10
  ServiceRoute,
11
11
  StopAdjacency,
12
12
  Timetable,
13
+ TripContinuations,
13
14
  } from '../../timetable/timetable.js';
15
+ import { encode } from '../../timetable/tripBoardingId.js';
14
16
  import { Query } from '../query.js';
15
17
  import { Result } from '../result.js';
16
18
  import { Router } from '../router.js';
@@ -22,32 +24,37 @@ describe('Router', () => {
22
24
 
23
25
  beforeEach(() => {
24
26
  const stopsAdjacency: StopAdjacency[] = [
25
- { transfers: [], routes: [0] },
26
- { transfers: [], routes: [0] },
27
- { transfers: [], routes: [0] },
27
+ { routes: [0] },
28
+ { routes: [0] },
29
+ { routes: [0] },
28
30
  ];
29
31
 
30
32
  const routesAdjacency = [
31
- new Route(
32
- new Uint16Array([
33
- Time.fromString('08:00:00').toMinutes(),
34
- Time.fromString('08:10:00').toMinutes(),
35
- Time.fromString('08:15:00').toMinutes(),
36
- Time.fromString('08:25:00').toMinutes(),
37
- Time.fromString('08:35:00').toMinutes(),
38
- Time.fromString('08:45:00').toMinutes(),
39
- ]),
40
- new Uint8Array([
41
- REGULAR,
42
- REGULAR,
43
- REGULAR,
44
- REGULAR,
45
- REGULAR,
46
- REGULAR,
47
- ]),
48
- new Uint32Array([0, 1, 2]),
49
- 0,
50
- ),
33
+ Route.of({
34
+ id: 0,
35
+ serviceRouteId: 0,
36
+ trips: [
37
+ {
38
+ stops: [
39
+ {
40
+ id: 0,
41
+ arrivalTime: Time.fromString('08:00:00'),
42
+ departureTime: Time.fromString('08:10:00'),
43
+ },
44
+ {
45
+ id: 1,
46
+ arrivalTime: Time.fromString('08:15:00'),
47
+ departureTime: Time.fromString('08:25:00'),
48
+ },
49
+ {
50
+ id: 2,
51
+ arrivalTime: Time.fromString('08:35:00'),
52
+ departureTime: Time.fromString('08:45:00'),
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ }),
51
58
  ];
52
59
 
53
60
  const routes: ServiceRoute[] = [
@@ -100,7 +107,6 @@ describe('Router', () => {
100
107
  .build();
101
108
 
102
109
  const result: Result = router.route(query);
103
-
104
110
  const bestRoute = result.bestRoute();
105
111
  assert.strictEqual(bestRoute?.legs.length, 1);
106
112
  });
@@ -139,53 +145,63 @@ describe('Router', () => {
139
145
 
140
146
  beforeEach(() => {
141
147
  const stopsAdjacency: StopAdjacency[] = [
142
- { transfers: [], routes: [0] },
143
- { transfers: [], routes: [0, 1] },
144
- { transfers: [], routes: [0] },
145
- { transfers: [], routes: [1] },
146
- { transfers: [], routes: [1] },
148
+ { routes: [0] },
149
+ { routes: [0, 1] },
150
+ { routes: [0] },
151
+ { routes: [1] },
152
+ { routes: [1] },
147
153
  ];
148
154
  const routesAdjacency = [
149
- new Route(
150
- new Uint16Array([
151
- Time.fromString('08:00:00').toMinutes(),
152
- Time.fromString('08:15:00').toMinutes(),
153
- Time.fromString('08:30:00').toMinutes(),
154
- Time.fromString('08:45:00').toMinutes(),
155
- Time.fromString('09:00:00').toMinutes(),
156
- Time.fromString('09:10:00').toMinutes(),
157
- ]),
158
- new Uint8Array([
159
- REGULAR,
160
- REGULAR,
161
- REGULAR,
162
- REGULAR,
163
- REGULAR,
164
- REGULAR,
165
- ]),
166
- new Uint32Array([0, 1, 2]),
167
- 0,
168
- ),
169
- new Route(
170
- new Uint16Array([
171
- Time.fromString('08:05:00').toMinutes(),
172
- Time.fromString('08:20:00').toMinutes(),
173
- Time.fromString('09:00:00').toMinutes(),
174
- Time.fromString('09:15:00').toMinutes(),
175
- Time.fromString('09:20:00').toMinutes(),
176
- Time.fromString('09:35:00').toMinutes(),
177
- ]),
178
- new Uint8Array([
179
- REGULAR,
180
- REGULAR,
181
- REGULAR,
182
- REGULAR,
183
- REGULAR,
184
- REGULAR,
185
- ]),
186
- new Uint32Array([3, 1, 4]),
187
- 1,
188
- ),
155
+ Route.of({
156
+ id: 0,
157
+ serviceRouteId: 0,
158
+ trips: [
159
+ {
160
+ stops: [
161
+ {
162
+ id: 0,
163
+ arrivalTime: Time.fromString('08:00:00'),
164
+ departureTime: Time.fromString('08:15:00'),
165
+ },
166
+ {
167
+ id: 1,
168
+ arrivalTime: Time.fromString('08:30:00'),
169
+ departureTime: Time.fromString('08:45:00'),
170
+ },
171
+ {
172
+ id: 2,
173
+ arrivalTime: Time.fromString('09:00:00'),
174
+ departureTime: Time.fromString('09:10:00'),
175
+ },
176
+ ],
177
+ },
178
+ ],
179
+ }),
180
+ Route.of({
181
+ id: 1,
182
+ serviceRouteId: 1,
183
+ trips: [
184
+ {
185
+ stops: [
186
+ {
187
+ id: 3,
188
+ arrivalTime: Time.fromString('08:05:00'),
189
+ departureTime: Time.fromString('08:20:00'),
190
+ },
191
+ {
192
+ id: 1,
193
+ arrivalTime: Time.fromString('09:00:00'),
194
+ departureTime: Time.fromString('09:15:00'),
195
+ },
196
+ {
197
+ id: 4,
198
+ arrivalTime: Time.fromString('09:20:00'),
199
+ departureTime: Time.fromString('09:35:00'),
200
+ },
201
+ ],
202
+ },
203
+ ],
204
+ }),
189
205
  ];
190
206
 
191
207
  const routes: ServiceRoute[] = [
@@ -263,7 +279,6 @@ describe('Router', () => {
263
279
  .build();
264
280
 
265
281
  const result: Result = router.route(query);
266
-
267
282
  const bestRoute = result.bestRoute();
268
283
  assert.strictEqual(bestRoute?.legs.length, 2);
269
284
  });
@@ -290,7 +305,7 @@ describe('Router', () => {
290
305
 
291
306
  beforeEach(() => {
292
307
  const stopsAdjacency: StopAdjacency[] = [
293
- { transfers: [], routes: [0] },
308
+ { routes: [0] },
294
309
  {
295
310
  transfers: [
296
311
  {
@@ -301,53 +316,63 @@ describe('Router', () => {
301
316
  ],
302
317
  routes: [0],
303
318
  },
304
- { transfers: [], routes: [0] },
305
- { transfers: [], routes: [1] },
306
- { transfers: [], routes: [1] },
307
- { transfers: [], routes: [1] },
319
+ { routes: [0] },
320
+ { routes: [1] },
321
+ { routes: [1] },
322
+ { routes: [1] },
308
323
  ];
309
324
 
310
325
  const routesAdjacency = [
311
- new Route(
312
- new Uint16Array([
313
- Time.fromString('08:00:00').toMinutes(),
314
- Time.fromString('08:15:00').toMinutes(),
315
- Time.fromString('08:25:00').toMinutes(),
316
- Time.fromString('08:35:00').toMinutes(),
317
- Time.fromString('08:45:00').toMinutes(),
318
- Time.fromString('08:55:00').toMinutes(),
319
- ]),
320
- new Uint8Array([
321
- REGULAR,
322
- REGULAR,
323
- REGULAR,
324
- REGULAR,
325
- REGULAR,
326
- REGULAR,
327
- ]),
328
- new Uint32Array([0, 1, 2]),
329
- 0,
330
- ),
331
- new Route(
332
- new Uint16Array([
333
- Time.fromString('08:10:00').toMinutes(),
334
- Time.fromString('08:20:00').toMinutes(),
335
- Time.fromString('08:40:00').toMinutes(),
336
- Time.fromString('08:50:00').toMinutes(),
337
- Time.fromString('09:00:00').toMinutes(),
338
- Time.fromString('09:10:00').toMinutes(),
339
- ]),
340
- new Uint8Array([
341
- REGULAR,
342
- REGULAR,
343
- REGULAR,
344
- REGULAR,
345
- REGULAR,
346
- REGULAR,
347
- ]),
348
- new Uint32Array([3, 4, 5]),
349
- 1,
350
- ),
326
+ Route.of({
327
+ id: 0,
328
+ serviceRouteId: 0,
329
+ trips: [
330
+ {
331
+ stops: [
332
+ {
333
+ id: 0,
334
+ arrivalTime: Time.fromString('08:00:00'),
335
+ departureTime: Time.fromString('08:15:00'),
336
+ },
337
+ {
338
+ id: 1,
339
+ arrivalTime: Time.fromString('08:25:00'),
340
+ departureTime: Time.fromString('08:35:00'),
341
+ },
342
+ {
343
+ id: 2,
344
+ arrivalTime: Time.fromString('08:45:00'),
345
+ departureTime: Time.fromString('08:55:00'),
346
+ },
347
+ ],
348
+ },
349
+ ],
350
+ }),
351
+ Route.of({
352
+ id: 1,
353
+ serviceRouteId: 1,
354
+ trips: [
355
+ {
356
+ stops: [
357
+ {
358
+ id: 3,
359
+ arrivalTime: Time.fromString('08:10:00'),
360
+ departureTime: Time.fromString('08:20:00'),
361
+ },
362
+ {
363
+ id: 4,
364
+ arrivalTime: Time.fromString('08:40:00'),
365
+ departureTime: Time.fromString('08:50:00'),
366
+ },
367
+ {
368
+ id: 5,
369
+ arrivalTime: Time.fromString('09:10:00'),
370
+ departureTime: Time.fromString('09:10:00'),
371
+ },
372
+ ],
373
+ },
374
+ ],
375
+ }),
351
376
  ];
352
377
 
353
378
  const routes: ServiceRoute[] = [
@@ -459,65 +484,84 @@ describe('Router', () => {
459
484
 
460
485
  beforeEach(() => {
461
486
  const stopsAdjacency: StopAdjacency[] = [
462
- { transfers: [], routes: [0, 2] },
463
- { transfers: [], routes: [0, 1] },
464
- { transfers: [], routes: [0] },
465
- { transfers: [], routes: [1] },
466
- { transfers: [], routes: [1, 2] },
487
+ { routes: [0, 2] },
488
+ { routes: [0, 1] },
489
+ { routes: [0] },
490
+ { routes: [1] },
491
+ { routes: [1, 2] },
467
492
  ];
468
493
 
469
494
  const routesAdjacency = [
470
- new Route(
471
- new Uint16Array([
472
- Time.fromString('08:00:00').toMinutes(),
473
- Time.fromString('08:15:00').toMinutes(),
474
- Time.fromString('08:30:00').toMinutes(),
475
- Time.fromString('08:45:00').toMinutes(),
476
- Time.fromString('09:00:00').toMinutes(),
477
- Time.fromString('09:15:00').toMinutes(),
478
- ]),
479
- new Uint8Array([
480
- REGULAR,
481
- REGULAR,
482
- REGULAR,
483
- REGULAR,
484
- REGULAR,
485
- REGULAR,
486
- ]),
487
- new Uint32Array([0, 1, 2]),
488
- 0,
489
- ),
490
- new Route(
491
- new Uint16Array([
492
- Time.fromString('08:10:00').toMinutes(),
493
- Time.fromString('08:25:00').toMinutes(),
494
- Time.fromString('08:50:00').toMinutes(),
495
- Time.fromString('09:05:00').toMinutes(),
496
- Time.fromString('09:10:00').toMinutes(),
497
- Time.fromString('09:25:00').toMinutes(),
498
- ]),
499
- new Uint8Array([
500
- REGULAR,
501
- REGULAR,
502
- REGULAR,
503
- REGULAR,
504
- REGULAR,
505
- REGULAR,
506
- ]),
507
- new Uint32Array([3, 1, 4]),
508
- 1,
509
- ),
510
- new Route(
511
- new Uint16Array([
512
- Time.fromString('08:00:00').toMinutes(),
513
- Time.fromString('08:15:00').toMinutes(),
514
- Time.fromString('09:45:00').toMinutes(),
515
- Time.fromString('10:00:00').toMinutes(),
516
- ]),
517
- new Uint8Array([REGULAR, REGULAR, REGULAR, REGULAR]),
518
- new Uint32Array([0, 4]),
519
- 2,
520
- ),
495
+ Route.of({
496
+ id: 0,
497
+ serviceRouteId: 0,
498
+ trips: [
499
+ {
500
+ stops: [
501
+ {
502
+ id: 0,
503
+ arrivalTime: Time.fromString('08:00:00'),
504
+ departureTime: Time.fromString('08:15:00'),
505
+ },
506
+ {
507
+ id: 1,
508
+ arrivalTime: Time.fromString('08:30:00'),
509
+ departureTime: Time.fromString('08:45:00'),
510
+ },
511
+ {
512
+ id: 2,
513
+ arrivalTime: Time.fromString('09:00:00'),
514
+ departureTime: Time.fromString('09:15:00'),
515
+ },
516
+ ],
517
+ },
518
+ ],
519
+ }),
520
+ Route.of({
521
+ id: 1,
522
+ serviceRouteId: 1,
523
+ trips: [
524
+ {
525
+ stops: [
526
+ {
527
+ id: 3,
528
+ arrivalTime: Time.fromString('08:10:00'),
529
+ departureTime: Time.fromString('08:25:00'),
530
+ },
531
+ {
532
+ id: 1,
533
+ arrivalTime: Time.fromString('08:50:00'),
534
+ departureTime: Time.fromString('09:05:00'),
535
+ },
536
+ {
537
+ id: 4,
538
+ arrivalTime: Time.fromString('09:10:00'),
539
+ departureTime: Time.fromString('09:25:00'),
540
+ },
541
+ ],
542
+ },
543
+ ],
544
+ }),
545
+ Route.of({
546
+ id: 2,
547
+ serviceRouteId: 2,
548
+ trips: [
549
+ {
550
+ stops: [
551
+ {
552
+ id: 0,
553
+ arrivalTime: Time.fromString('08:00:00'),
554
+ departureTime: Time.fromString('08:15:00'),
555
+ },
556
+ {
557
+ id: 4,
558
+ arrivalTime: Time.fromString('09:45:00'),
559
+ departureTime: Time.fromString('10:00:00'),
560
+ },
561
+ ],
562
+ },
563
+ ],
564
+ }),
521
565
  ];
522
566
 
523
567
  const routes: ServiceRoute[] = [
@@ -603,4 +647,168 @@ describe('Router', () => {
603
647
  assert.strictEqual(bestRoute?.legs.length, 2);
604
648
  });
605
649
  });
650
+
651
+ describe('with route continuation (in-seat transfer)', () => {
652
+ let router: Router;
653
+ let timetable: Timetable;
654
+
655
+ beforeEach(() => {
656
+ // Setup: Route 0 continues as Route 1 at stop 1
657
+ const tripContinuations: TripContinuations = new Map([
658
+ [encode(1, 0, 0), [{ hopOnStopIndex: 1, routeId: 1, tripIndex: 0 }]],
659
+ ]);
660
+
661
+ const stopsAdjacency: StopAdjacency[] = [
662
+ { routes: [0] },
663
+ {
664
+ routes: [0, 1],
665
+ },
666
+ { routes: [1] },
667
+ { routes: [1] },
668
+ ];
669
+
670
+ const routesAdjacency = [
671
+ // Route 0: stops 0 -> 1
672
+ Route.of({
673
+ id: 0,
674
+ serviceRouteId: 0,
675
+ trips: [
676
+ {
677
+ stops: [
678
+ {
679
+ id: 0,
680
+ arrivalTime: Time.fromString('08:00:00'),
681
+ departureTime: Time.fromString('08:10:00'),
682
+ },
683
+ {
684
+ id: 1,
685
+ arrivalTime: Time.fromString('08:15:00'),
686
+ departureTime: Time.fromString('08:25:00'),
687
+ },
688
+ ],
689
+ },
690
+ ],
691
+ }),
692
+ // Route 1: stops 1 -> 2 -> 3 (continuation from route 0)
693
+ Route.of({
694
+ id: 1,
695
+ serviceRouteId: 1,
696
+ trips: [
697
+ {
698
+ stops: [
699
+ {
700
+ id: 1,
701
+ arrivalTime: Time.fromString('08:15:00'),
702
+ departureTime: Time.fromString('08:25:00'),
703
+ },
704
+ {
705
+ id: 2,
706
+ arrivalTime: Time.fromString('08:35:00'),
707
+ departureTime: Time.fromString('08:45:00'),
708
+ },
709
+ {
710
+ id: 3,
711
+ arrivalTime: Time.fromString('08:55:00'),
712
+ departureTime: Time.fromString('09:05:00'),
713
+ },
714
+ ],
715
+ },
716
+ ],
717
+ }),
718
+ ];
719
+
720
+ const routes: ServiceRoute[] = [
721
+ {
722
+ type: 'BUS',
723
+ name: 'Line 1',
724
+ routes: [0],
725
+ },
726
+ {
727
+ type: 'BUS',
728
+ name: 'Line 2',
729
+ routes: [1],
730
+ },
731
+ ];
732
+
733
+ timetable = new Timetable(
734
+ stopsAdjacency,
735
+ routesAdjacency,
736
+ routes,
737
+ tripContinuations,
738
+ );
739
+
740
+ const stops: Stop[] = [
741
+ {
742
+ id: 0,
743
+ sourceStopId: 'stop1',
744
+ name: 'Stop 1',
745
+ lat: 1.0,
746
+ lon: 1.0,
747
+ children: [],
748
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
749
+ },
750
+ {
751
+ id: 1,
752
+ sourceStopId: 'stop2',
753
+ name: 'Stop 2',
754
+ lat: 2.0,
755
+ lon: 2.0,
756
+ children: [],
757
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
758
+ },
759
+ {
760
+ id: 2,
761
+ sourceStopId: 'stop3',
762
+ name: 'Stop 3',
763
+ lat: 3.0,
764
+ lon: 3.0,
765
+ children: [],
766
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
767
+ },
768
+ {
769
+ id: 3,
770
+ sourceStopId: 'stop4',
771
+ name: 'Stop 4',
772
+ lat: 4.0,
773
+ lon: 4.0,
774
+ children: [],
775
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
776
+ },
777
+ ];
778
+
779
+ const stopsIndex = new StopsIndex(stops);
780
+ router = new Router(timetable, stopsIndex);
781
+ });
782
+
783
+ it('should find a route using continuation (in-seat transfer)', () => {
784
+ const query = new Query.Builder()
785
+ .from('stop1')
786
+ .to('stop4')
787
+ .departureTime(Time.fromString('08:00:00'))
788
+ .build();
789
+
790
+ const result: Result = router.route(query);
791
+ const bestRoute = result.bestRoute();
792
+
793
+ // Should find a route with only 1 leg because the continuation allows
794
+ // staying on the same vehicle when it changes route numbers
795
+ assert.strictEqual(bestRoute?.legs.length, 1);
796
+ });
797
+
798
+ it('should correctly calculate arrival time with continuation', () => {
799
+ const query = new Query.Builder()
800
+ .from('stop1')
801
+ .to('stop4')
802
+ .departureTime(Time.fromString('08:00:00'))
803
+ .build();
804
+
805
+ const result: Result = router.route(query);
806
+
807
+ const timeToStop4 = result.arrivalAt('stop4');
808
+ assert.strictEqual(
809
+ timeToStop4?.arrival.toMinutes(),
810
+ Time.fromString('08:55:00').toMinutes(),
811
+ );
812
+ });
813
+ });
606
814
  });