minotor 3.0.1 → 3.0.2
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/.cspell.json +12 -1
- package/.gitattributes +3 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +3 -0
- package/.github/workflows/minotor.yml +17 -1
- package/CHANGELOG.md +2 -2
- package/README.md +34 -14
- package/dist/__e2e__/router.test.d.ts +1 -0
- package/dist/cli/perf.d.ts +28 -0
- package/dist/cli/utils.d.ts +6 -2
- package/dist/cli.mjs +1967 -823
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/trips.d.ts +1 -0
- package/dist/gtfs/utils.d.ts +1 -1
- package/dist/parser.cjs.js +1030 -627
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.d.ts +4 -2
- package/dist/parser.esm.js +1030 -627
- 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.d.ts +10 -5
- 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/__tests__/result.test.d.ts +1 -0
- package/dist/routing/query.d.ts +27 -6
- package/dist/routing/result.d.ts +1 -1
- package/dist/routing/route.d.ts +47 -2
- package/dist/routing/router.d.ts +15 -1
- package/dist/stops/stopsIndex.d.ts +3 -3
- package/dist/timetable/__tests__/route.test.d.ts +1 -0
- package/dist/timetable/__tests__/time.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +7 -1
- package/dist/timetable/proto/timetable.d.ts +1 -1
- package/dist/timetable/route.d.ts +155 -0
- package/dist/timetable/time.d.ts +21 -0
- package/dist/timetable/timetable.d.ts +41 -61
- package/package.json +36 -34
- package/src/__e2e__/benchmark.json +22 -0
- package/src/__e2e__/router.test.ts +209 -0
- package/src/__e2e__/timetable/stops.bin +3 -0
- package/src/__e2e__/timetable/timetable.bin +3 -0
- package/src/cli/minotor.ts +51 -1
- package/src/cli/perf.ts +136 -0
- package/src/cli/repl.ts +26 -13
- package/src/cli/utils.ts +6 -28
- package/src/gtfs/__tests__/parser.test.ts +12 -15
- package/src/gtfs/__tests__/services.test.ts +1 -0
- package/src/gtfs/__tests__/transfers.test.ts +0 -1
- package/src/gtfs/__tests__/trips.test.ts +67 -74
- package/src/gtfs/profiles/ch.ts +1 -1
- package/src/gtfs/routes.ts +4 -4
- package/src/gtfs/services.ts +15 -2
- package/src/gtfs/stops.ts +7 -3
- package/src/gtfs/transfers.ts +6 -3
- package/src/gtfs/trips.ts +33 -16
- package/src/gtfs/utils.ts +13 -2
- package/src/parser.ts +4 -2
- package/src/router.ts +17 -11
- package/src/routing/__tests__/result.test.ts +392 -0
- package/src/routing/__tests__/router.test.ts +94 -137
- package/src/routing/query.ts +28 -7
- package/src/routing/result.ts +10 -5
- package/src/routing/route.ts +95 -9
- package/src/routing/router.ts +82 -66
- package/src/stops/__tests__/io.test.ts +1 -1
- package/src/stops/__tests__/stopFinder.test.ts +1 -1
- package/src/stops/proto/stops.ts +4 -4
- package/src/stops/stopsIndex.ts +3 -3
- package/src/timetable/__tests__/io.test.ts +16 -23
- package/src/timetable/__tests__/route.test.ts +317 -0
- package/src/timetable/__tests__/time.test.ts +494 -0
- package/src/timetable/__tests__/timetable.test.ts +64 -75
- package/src/timetable/io.ts +32 -26
- package/src/timetable/proto/timetable.proto +1 -1
- package/src/timetable/proto/timetable.ts +13 -13
- package/src/timetable/route.ts +347 -0
- package/src/timetable/time.ts +40 -8
- package/src/timetable/timetable.ts +74 -165
- package/tsconfig.build.json +1 -1
|
@@ -4,6 +4,7 @@ import { beforeEach, describe, it } from 'node:test';
|
|
|
4
4
|
import { StopsMap } 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
8
|
import { Time } from '../../timetable/time.js';
|
|
8
9
|
import {
|
|
9
10
|
RoutesAdjacency,
|
|
@@ -30,8 +31,8 @@ describe('Router', () => {
|
|
|
30
31
|
const routesAdjacency: RoutesAdjacency = new Map([
|
|
31
32
|
[
|
|
32
33
|
'route1',
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
new Route(
|
|
35
|
+
new Uint16Array([
|
|
35
36
|
Time.fromString('08:00:00').toMinutes(),
|
|
36
37
|
Time.fromString('08:10:00').toMinutes(),
|
|
37
38
|
Time.fromString('08:15:00').toMinutes(),
|
|
@@ -39,22 +40,17 @@ describe('Router', () => {
|
|
|
39
40
|
Time.fromString('08:35:00').toMinutes(),
|
|
40
41
|
Time.fromString('08:45:00').toMinutes(),
|
|
41
42
|
]),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
]),
|
|
50
|
-
stops: new Uint32Array([0, 1, 2]),
|
|
51
|
-
stopIndices: new Map([
|
|
52
|
-
[0, 0],
|
|
53
|
-
[1, 1],
|
|
54
|
-
[2, 2],
|
|
43
|
+
new Uint8Array([
|
|
44
|
+
REGULAR,
|
|
45
|
+
REGULAR,
|
|
46
|
+
REGULAR,
|
|
47
|
+
REGULAR,
|
|
48
|
+
REGULAR,
|
|
49
|
+
REGULAR,
|
|
55
50
|
]),
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
new Uint32Array([0, 1, 2]),
|
|
52
|
+
'service_route1',
|
|
53
|
+
),
|
|
58
54
|
],
|
|
59
55
|
]);
|
|
60
56
|
|
|
@@ -147,7 +143,7 @@ describe('Router', () => {
|
|
|
147
143
|
|
|
148
144
|
const timeToStop3 = result.arrivalAt('stop3');
|
|
149
145
|
assert.strictEqual(
|
|
150
|
-
timeToStop3?.
|
|
146
|
+
timeToStop3?.arrival.toMinutes(),
|
|
151
147
|
Time.fromString('08:35:00').toMinutes(),
|
|
152
148
|
);
|
|
153
149
|
});
|
|
@@ -167,8 +163,8 @@ describe('Router', () => {
|
|
|
167
163
|
const routesAdjacency: RoutesAdjacency = new Map([
|
|
168
164
|
[
|
|
169
165
|
'route1',
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
new Route(
|
|
167
|
+
new Uint16Array([
|
|
172
168
|
Time.fromString('08:00:00').toMinutes(),
|
|
173
169
|
Time.fromString('08:15:00').toMinutes(),
|
|
174
170
|
Time.fromString('08:30:00').toMinutes(),
|
|
@@ -176,27 +172,22 @@ describe('Router', () => {
|
|
|
176
172
|
Time.fromString('09:00:00').toMinutes(),
|
|
177
173
|
Time.fromString('09:10:00').toMinutes(),
|
|
178
174
|
]),
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
new Uint8Array([
|
|
176
|
+
REGULAR,
|
|
177
|
+
REGULAR,
|
|
178
|
+
REGULAR,
|
|
179
|
+
REGULAR,
|
|
180
|
+
REGULAR,
|
|
181
|
+
REGULAR,
|
|
186
182
|
]),
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
[1, 1],
|
|
191
|
-
[2, 2],
|
|
192
|
-
]),
|
|
193
|
-
serviceRouteId: 'service_route1',
|
|
194
|
-
},
|
|
183
|
+
new Uint32Array([0, 1, 2]),
|
|
184
|
+
'service_route1',
|
|
185
|
+
),
|
|
195
186
|
],
|
|
196
187
|
[
|
|
197
188
|
'route2',
|
|
198
|
-
|
|
199
|
-
|
|
189
|
+
new Route(
|
|
190
|
+
new Uint16Array([
|
|
200
191
|
Time.fromString('08:05:00').toMinutes(),
|
|
201
192
|
Time.fromString('08:20:00').toMinutes(),
|
|
202
193
|
Time.fromString('09:00:00').toMinutes(),
|
|
@@ -204,22 +195,17 @@ describe('Router', () => {
|
|
|
204
195
|
Time.fromString('09:20:00').toMinutes(),
|
|
205
196
|
Time.fromString('09:35:00').toMinutes(),
|
|
206
197
|
]),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
new Uint8Array([
|
|
199
|
+
REGULAR,
|
|
200
|
+
REGULAR,
|
|
201
|
+
REGULAR,
|
|
202
|
+
REGULAR,
|
|
203
|
+
REGULAR,
|
|
204
|
+
REGULAR,
|
|
214
205
|
]),
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
[1, 1],
|
|
219
|
-
[4, 2],
|
|
220
|
-
]),
|
|
221
|
-
serviceRouteId: 'service_route2',
|
|
222
|
-
},
|
|
206
|
+
new Uint32Array([3, 1, 4]),
|
|
207
|
+
'service_route2',
|
|
208
|
+
),
|
|
223
209
|
],
|
|
224
210
|
]);
|
|
225
211
|
|
|
@@ -333,7 +319,7 @@ describe('Router', () => {
|
|
|
333
319
|
|
|
334
320
|
const timeToStop5 = result.arrivalAt('stop5');
|
|
335
321
|
assert.strictEqual(
|
|
336
|
-
timeToStop5?.
|
|
322
|
+
timeToStop5?.arrival.toMinutes(),
|
|
337
323
|
Time.fromString('09:20:00').toMinutes(),
|
|
338
324
|
);
|
|
339
325
|
});
|
|
@@ -367,8 +353,8 @@ describe('Router', () => {
|
|
|
367
353
|
const routesAdjacency: RoutesAdjacency = new Map([
|
|
368
354
|
[
|
|
369
355
|
'route1',
|
|
370
|
-
|
|
371
|
-
|
|
356
|
+
new Route(
|
|
357
|
+
new Uint16Array([
|
|
372
358
|
Time.fromString('08:00:00').toMinutes(),
|
|
373
359
|
Time.fromString('08:15:00').toMinutes(),
|
|
374
360
|
Time.fromString('08:25:00').toMinutes(),
|
|
@@ -376,27 +362,22 @@ describe('Router', () => {
|
|
|
376
362
|
Time.fromString('08:45:00').toMinutes(),
|
|
377
363
|
Time.fromString('08:55:00').toMinutes(),
|
|
378
364
|
]),
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
365
|
+
new Uint8Array([
|
|
366
|
+
REGULAR,
|
|
367
|
+
REGULAR,
|
|
368
|
+
REGULAR,
|
|
369
|
+
REGULAR,
|
|
370
|
+
REGULAR,
|
|
371
|
+
REGULAR,
|
|
386
372
|
]),
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
[1, 1],
|
|
391
|
-
[2, 2],
|
|
392
|
-
]),
|
|
393
|
-
serviceRouteId: 'service_route1',
|
|
394
|
-
},
|
|
373
|
+
new Uint32Array([0, 1, 2]),
|
|
374
|
+
'service_route1',
|
|
375
|
+
),
|
|
395
376
|
],
|
|
396
377
|
[
|
|
397
378
|
'route2',
|
|
398
|
-
|
|
399
|
-
|
|
379
|
+
new Route(
|
|
380
|
+
new Uint16Array([
|
|
400
381
|
Time.fromString('08:10:00').toMinutes(),
|
|
401
382
|
Time.fromString('08:20:00').toMinutes(),
|
|
402
383
|
Time.fromString('08:40:00').toMinutes(),
|
|
@@ -404,22 +385,17 @@ describe('Router', () => {
|
|
|
404
385
|
Time.fromString('09:00:00').toMinutes(),
|
|
405
386
|
Time.fromString('09:10:00').toMinutes(),
|
|
406
387
|
]),
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
388
|
+
new Uint8Array([
|
|
389
|
+
REGULAR,
|
|
390
|
+
REGULAR,
|
|
391
|
+
REGULAR,
|
|
392
|
+
REGULAR,
|
|
393
|
+
REGULAR,
|
|
394
|
+
REGULAR,
|
|
414
395
|
]),
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
[4, 1],
|
|
419
|
-
[5, 2],
|
|
420
|
-
]),
|
|
421
|
-
serviceRouteId: 'service_route2',
|
|
422
|
-
},
|
|
396
|
+
new Uint32Array([3, 4, 5]),
|
|
397
|
+
'service_route2',
|
|
398
|
+
),
|
|
423
399
|
],
|
|
424
400
|
]);
|
|
425
401
|
|
|
@@ -543,7 +519,7 @@ describe('Router', () => {
|
|
|
543
519
|
|
|
544
520
|
const timeToStop5 = result.arrivalAt('stop5');
|
|
545
521
|
assert.strictEqual(
|
|
546
|
-
timeToStop5?.
|
|
522
|
+
timeToStop5?.arrival.toMinutes(),
|
|
547
523
|
Time.fromString('08:30:00').toMinutes(),
|
|
548
524
|
);
|
|
549
525
|
});
|
|
@@ -564,8 +540,8 @@ describe('Router', () => {
|
|
|
564
540
|
const routesAdjacency: RoutesAdjacency = new Map([
|
|
565
541
|
[
|
|
566
542
|
'route1',
|
|
567
|
-
|
|
568
|
-
|
|
543
|
+
new Route(
|
|
544
|
+
new Uint16Array([
|
|
569
545
|
Time.fromString('08:00:00').toMinutes(),
|
|
570
546
|
Time.fromString('08:15:00').toMinutes(),
|
|
571
547
|
Time.fromString('08:30:00').toMinutes(),
|
|
@@ -573,27 +549,22 @@ describe('Router', () => {
|
|
|
573
549
|
Time.fromString('09:00:00').toMinutes(),
|
|
574
550
|
Time.fromString('09:15:00').toMinutes(),
|
|
575
551
|
]),
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
552
|
+
new Uint8Array([
|
|
553
|
+
REGULAR,
|
|
554
|
+
REGULAR,
|
|
555
|
+
REGULAR,
|
|
556
|
+
REGULAR,
|
|
557
|
+
REGULAR,
|
|
558
|
+
REGULAR,
|
|
583
559
|
]),
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
[1, 1],
|
|
588
|
-
[2, 2],
|
|
589
|
-
]),
|
|
590
|
-
serviceRouteId: 'service_route1',
|
|
591
|
-
},
|
|
560
|
+
new Uint32Array([0, 1, 2]),
|
|
561
|
+
'service_route1',
|
|
562
|
+
),
|
|
592
563
|
],
|
|
593
564
|
[
|
|
594
565
|
'route2',
|
|
595
|
-
|
|
596
|
-
|
|
566
|
+
new Route(
|
|
567
|
+
new Uint16Array([
|
|
597
568
|
Time.fromString('08:10:00').toMinutes(),
|
|
598
569
|
Time.fromString('08:25:00').toMinutes(),
|
|
599
570
|
Time.fromString('08:50:00').toMinutes(),
|
|
@@ -601,45 +572,31 @@ describe('Router', () => {
|
|
|
601
572
|
Time.fromString('09:10:00').toMinutes(),
|
|
602
573
|
Time.fromString('09:25:00').toMinutes(),
|
|
603
574
|
]),
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
575
|
+
new Uint8Array([
|
|
576
|
+
REGULAR,
|
|
577
|
+
REGULAR,
|
|
578
|
+
REGULAR,
|
|
579
|
+
REGULAR,
|
|
580
|
+
REGULAR,
|
|
581
|
+
REGULAR,
|
|
611
582
|
]),
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
[1, 1],
|
|
616
|
-
[4, 2],
|
|
617
|
-
]),
|
|
618
|
-
serviceRouteId: 'service_route2',
|
|
619
|
-
},
|
|
583
|
+
new Uint32Array([3, 1, 4]),
|
|
584
|
+
'service_route2',
|
|
585
|
+
),
|
|
620
586
|
],
|
|
621
587
|
[
|
|
622
588
|
'route3',
|
|
623
|
-
|
|
624
|
-
|
|
589
|
+
new Route(
|
|
590
|
+
new Uint16Array([
|
|
625
591
|
Time.fromString('08:00:00').toMinutes(),
|
|
626
592
|
Time.fromString('08:15:00').toMinutes(),
|
|
627
593
|
Time.fromString('09:45:00').toMinutes(),
|
|
628
594
|
Time.fromString('10:00:00').toMinutes(),
|
|
629
595
|
]),
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
0, // REGULAR
|
|
635
|
-
]),
|
|
636
|
-
stops: new Uint32Array([0, 4]),
|
|
637
|
-
stopIndices: new Map([
|
|
638
|
-
[0, 0],
|
|
639
|
-
[4, 1],
|
|
640
|
-
]),
|
|
641
|
-
serviceRouteId: 'service_route3',
|
|
642
|
-
},
|
|
596
|
+
new Uint8Array([REGULAR, REGULAR, REGULAR, REGULAR]),
|
|
597
|
+
new Uint32Array([0, 4]),
|
|
598
|
+
'service_route3',
|
|
599
|
+
),
|
|
643
600
|
],
|
|
644
601
|
]);
|
|
645
602
|
|
package/src/routing/query.ts
CHANGED
|
@@ -5,13 +5,13 @@ import { ALL_TRANSPORT_MODES, RouteType } from '../timetable/timetable.js';
|
|
|
5
5
|
|
|
6
6
|
export class Query {
|
|
7
7
|
from: SourceStopId;
|
|
8
|
-
to: SourceStopId
|
|
8
|
+
to: Set<SourceStopId>;
|
|
9
9
|
departureTime: Time;
|
|
10
10
|
lastDepartureTime?: Time;
|
|
11
11
|
options: {
|
|
12
12
|
maxTransfers: number;
|
|
13
13
|
minTransferTime: Duration;
|
|
14
|
-
transportModes: RouteType
|
|
14
|
+
transportModes: Set<RouteType>;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
constructor(builder: typeof Query.Builder.prototype) {
|
|
@@ -23,46 +23,67 @@ export class Query {
|
|
|
23
23
|
|
|
24
24
|
static Builder = class {
|
|
25
25
|
fromValue!: SourceStopId;
|
|
26
|
-
toValue: SourceStopId
|
|
26
|
+
toValue: Set<SourceStopId> = new Set();
|
|
27
27
|
departureTimeValue!: Time;
|
|
28
28
|
// lastDepartureTimeValue?: Date;
|
|
29
29
|
// via: StopId[] = [];
|
|
30
30
|
optionsValue: {
|
|
31
31
|
maxTransfers: number;
|
|
32
32
|
minTransferTime: Duration;
|
|
33
|
-
transportModes: RouteType
|
|
33
|
+
transportModes: Set<RouteType>;
|
|
34
34
|
} = {
|
|
35
35
|
maxTransfers: 5,
|
|
36
36
|
minTransferTime: Duration.fromSeconds(120),
|
|
37
37
|
transportModes: ALL_TRANSPORT_MODES,
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Sets the starting stop.
|
|
42
|
+
*/
|
|
40
43
|
from(from: SourceStopId): this {
|
|
41
44
|
this.fromValue = from;
|
|
42
45
|
return this;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Sets the destination stops(s), routing will stop when all the provided stops are reached.
|
|
50
|
+
*/
|
|
51
|
+
to(to: SourceStopId | Set<SourceStopId>): this {
|
|
52
|
+
this.toValue = to instanceof Set ? to : new Set([to]);
|
|
47
53
|
return this;
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Sets the departure time for the query.
|
|
58
|
+
* Note that the router will favor routes that depart shortly after the provided departure time,
|
|
59
|
+
* even if a later route might arrive at the same time.
|
|
60
|
+
* Range queries will allow to specify a range of departure times in the future.
|
|
61
|
+
*/
|
|
50
62
|
departureTime(departureTime: Time): this {
|
|
51
63
|
this.departureTimeValue = departureTime;
|
|
52
64
|
return this;
|
|
53
65
|
}
|
|
54
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Sets the maximum number of transfers allowed.
|
|
69
|
+
*/
|
|
55
70
|
maxTransfers(maxTransfers: number): this {
|
|
56
71
|
this.optionsValue.maxTransfers = maxTransfers;
|
|
57
72
|
return this;
|
|
58
73
|
}
|
|
59
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Sets the minimum transfer time to use when no transfer time is provided in the data.
|
|
77
|
+
*/
|
|
60
78
|
minTransferTime(minTransferTime: Duration): this {
|
|
61
79
|
this.optionsValue.minTransferTime = minTransferTime;
|
|
62
80
|
return this;
|
|
63
81
|
}
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Sets the transport modes to consider.
|
|
85
|
+
*/
|
|
86
|
+
transportModes(transportModes: Set<RouteType>): this {
|
|
66
87
|
this.optionsValue.transportModes = transportModes;
|
|
67
88
|
return this;
|
|
68
89
|
}
|
package/src/routing/result.ts
CHANGED
|
@@ -29,8 +29,13 @@ export class Result {
|
|
|
29
29
|
* @param to The destination stop. Defaults to the destination of the original query.
|
|
30
30
|
* @returns a route to the destination stop if it exists.
|
|
31
31
|
*/
|
|
32
|
-
bestRoute(to?: SourceStopId | SourceStopId
|
|
33
|
-
const destinationList =
|
|
32
|
+
bestRoute(to?: SourceStopId | Set<SourceStopId>): Route | undefined {
|
|
33
|
+
const destinationList =
|
|
34
|
+
to instanceof Set
|
|
35
|
+
? Array.from(to)
|
|
36
|
+
: to
|
|
37
|
+
? [to]
|
|
38
|
+
: Array.from(this.query.to);
|
|
34
39
|
const destinations = destinationList.flatMap((destination) =>
|
|
35
40
|
this.stopsIndex.equivalentStops(destination),
|
|
36
41
|
);
|
|
@@ -42,7 +47,7 @@ export class Result {
|
|
|
42
47
|
if (arrivalTime !== undefined) {
|
|
43
48
|
if (
|
|
44
49
|
fastestTime === undefined ||
|
|
45
|
-
arrivalTime.
|
|
50
|
+
arrivalTime.arrival.isBefore(fastestTime.arrival)
|
|
46
51
|
) {
|
|
47
52
|
fastestDestination = destination.id;
|
|
48
53
|
fastestTime = arrivalTime;
|
|
@@ -91,7 +96,7 @@ export class Result {
|
|
|
91
96
|
const relevantArrivals =
|
|
92
97
|
maxTransfers !== undefined
|
|
93
98
|
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
94
|
-
this.earliestArrivalsPerRound[maxTransfers
|
|
99
|
+
this.earliestArrivalsPerRound[maxTransfers + 1]!
|
|
95
100
|
: this.earliestArrivals;
|
|
96
101
|
|
|
97
102
|
for (const equivalentStop of equivalentStops) {
|
|
@@ -99,7 +104,7 @@ export class Result {
|
|
|
99
104
|
if (arrivalTime !== undefined) {
|
|
100
105
|
if (
|
|
101
106
|
earliestArrival === undefined ||
|
|
102
|
-
arrivalTime.
|
|
107
|
+
arrivalTime.arrival.isBefore(earliestArrival.arrival)
|
|
103
108
|
) {
|
|
104
109
|
earliestArrival = arrivalTime;
|
|
105
110
|
}
|
package/src/routing/route.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { Stop } from '../stops/stops.js';
|
|
1
|
+
import { SourceStopId, Stop } from '../stops/stops.js';
|
|
2
2
|
import { Duration } from '../timetable/duration.js';
|
|
3
3
|
import { Time } from '../timetable/time.js';
|
|
4
4
|
import { ServiceRoute, TransferType } from '../timetable/timetable.js';
|
|
5
5
|
|
|
6
|
+
export type JsonLeg = {
|
|
7
|
+
from: SourceStopId;
|
|
8
|
+
to: SourceStopId;
|
|
9
|
+
} & (
|
|
10
|
+
| {
|
|
11
|
+
departure: string;
|
|
12
|
+
arrival: string;
|
|
13
|
+
route: ServiceRoute;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
type: TransferType;
|
|
17
|
+
minTransferTime?: string;
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
6
21
|
export type PickUpDropOffType =
|
|
7
22
|
| 'REGULAR'
|
|
8
23
|
| 'NOT_AVAILABLE'
|
|
@@ -32,6 +47,10 @@ export type VehicleLeg = BaseLeg & {
|
|
|
32
47
|
|
|
33
48
|
export type Leg = Transfer | VehicleLeg;
|
|
34
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Represents a resolved route consisting of multiple legs,
|
|
52
|
+
* which can be either vehicle legs or transfer legs.
|
|
53
|
+
*/
|
|
35
54
|
export class Route {
|
|
36
55
|
legs: Leg[];
|
|
37
56
|
|
|
@@ -39,6 +58,12 @@ export class Route {
|
|
|
39
58
|
this.legs = legs;
|
|
40
59
|
}
|
|
41
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Calculates the departure time of the route.
|
|
63
|
+
*
|
|
64
|
+
* @returns The departure time of the route.
|
|
65
|
+
* @throws If no vehicle leg is found in the route.
|
|
66
|
+
*/
|
|
42
67
|
departureTime(): Time {
|
|
43
68
|
const cumulativeTransferTime: Duration = Duration.zero();
|
|
44
69
|
for (let i = 0; i < this.legs.length; i++) {
|
|
@@ -54,6 +79,12 @@ export class Route {
|
|
|
54
79
|
throw new Error('No vehicle leg found in route');
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Calculates the arrival time of the route.
|
|
84
|
+
*
|
|
85
|
+
* @returns The arrival time of the route.
|
|
86
|
+
* @throws If no vehicle leg is found in the route.
|
|
87
|
+
*/
|
|
57
88
|
arrivalTime(): Time {
|
|
58
89
|
let lastVehicleArrivalTime: Time = Time.origin();
|
|
59
90
|
const totalTransferTime: Duration = Duration.zero();
|
|
@@ -82,22 +113,77 @@ export class Route {
|
|
|
82
113
|
return lastVehicleArrivalTime.plus(totalTransferTime);
|
|
83
114
|
}
|
|
84
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Calculates the total duration of the route.
|
|
118
|
+
*
|
|
119
|
+
* @returns The total duration of the route.
|
|
120
|
+
*/
|
|
85
121
|
totalDuration(): Duration {
|
|
86
122
|
if (this.legs.length === 0) return Duration.zero();
|
|
87
123
|
return this.arrivalTime().diff(this.departureTime());
|
|
88
124
|
}
|
|
89
125
|
|
|
90
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Generates a human-readable string representation of the route.
|
|
128
|
+
*
|
|
129
|
+
* @returns A formatted string describing each leg of the route.
|
|
130
|
+
*/
|
|
131
|
+
toString(): string {
|
|
91
132
|
return this.legs
|
|
92
133
|
.map((leg, index) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
const fromStop = `From: ${leg.from.name}${leg.from.platform ? ` (Pl. ${leg.from.platform})` : ''}`;
|
|
135
|
+
const toStop = `To: ${leg.to.name}${leg.to.platform ? ` (Pl. ${leg.to.platform})` : ''}`;
|
|
136
|
+
const transferDetails =
|
|
137
|
+
'minTransferTime' in leg
|
|
138
|
+
? `Minimum Transfer Time: ${leg.minTransferTime?.toString()}`
|
|
139
|
+
: '';
|
|
140
|
+
const travelDetails =
|
|
141
|
+
'route' in leg && 'departureTime' in leg && 'arrivalTime' in leg
|
|
142
|
+
? `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`
|
|
143
|
+
: '';
|
|
144
|
+
|
|
145
|
+
return [
|
|
146
|
+
`Leg ${index + 1}:`,
|
|
147
|
+
` ${fromStop}`,
|
|
148
|
+
` ${toStop}`,
|
|
149
|
+
transferDetails ? ` ${transferDetails}` : '',
|
|
150
|
+
travelDetails ? ` ${travelDetails}` : '',
|
|
151
|
+
]
|
|
152
|
+
.filter((line) => line.trim() !== '')
|
|
153
|
+
.join('\n');
|
|
100
154
|
})
|
|
101
155
|
.join('\n');
|
|
102
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Generates a concise JSON representation of the route.
|
|
160
|
+
* This is particularly useful for generating regression tests
|
|
161
|
+
* to verify the correctness of route calculations.
|
|
162
|
+
*
|
|
163
|
+
* @returns A JSON representation of the route.
|
|
164
|
+
*/
|
|
165
|
+
asJson(): JsonLeg[] {
|
|
166
|
+
const jsonLegs: JsonLeg[] = this.legs.map((leg: Leg) => {
|
|
167
|
+
if ('route' in leg) {
|
|
168
|
+
return {
|
|
169
|
+
from: leg.from.sourceStopId,
|
|
170
|
+
to: leg.to.sourceStopId,
|
|
171
|
+
departure: leg.departureTime.toString(),
|
|
172
|
+
arrival: leg.arrivalTime.toString(),
|
|
173
|
+
route: leg.route,
|
|
174
|
+
};
|
|
175
|
+
} else {
|
|
176
|
+
return {
|
|
177
|
+
from: leg.from.sourceStopId,
|
|
178
|
+
to: leg.to.sourceStopId,
|
|
179
|
+
type: leg.type,
|
|
180
|
+
...(leg.minTransferTime !== undefined && {
|
|
181
|
+
minTransferTime: leg.minTransferTime.toString(),
|
|
182
|
+
}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return jsonLegs;
|
|
188
|
+
}
|
|
103
189
|
}
|