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.
- package/.cspell.json +11 -1
- package/CHANGELOG.md +8 -3
- package/README.md +26 -24
- package/dist/cli.mjs +1268 -290
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +13 -4
- package/dist/gtfs/trips.d.ts +12 -7
- package/dist/parser.cjs.js +519 -95
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +519 -95
- 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 +2 -2
- 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__/plotter.test.d.ts +1 -0
- package/dist/routing/plotter.d.ts +42 -3
- package/dist/routing/result.d.ts +23 -7
- package/dist/routing/route.d.ts +2 -0
- package/dist/routing/router.d.ts +78 -19
- package/dist/timetable/__tests__/tripId.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +4 -2
- package/dist/timetable/proto/timetable.d.ts +13 -1
- package/dist/timetable/route.d.ts +41 -8
- package/dist/timetable/timetable.d.ts +18 -3
- package/dist/timetable/tripId.d.ts +15 -0
- package/package.json +1 -1
- package/src/__e2e__/router.test.ts +114 -105
- package/src/__e2e__/timetable/stops.bin +2 -2
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/repl.ts +259 -1
- package/src/gtfs/__tests__/transfers.test.ts +468 -12
- package/src/gtfs/__tests__/trips.test.ts +350 -28
- package/src/gtfs/parser.ts +16 -4
- package/src/gtfs/transfers.ts +61 -18
- package/src/gtfs/trips.ts +97 -22
- package/src/router.ts +2 -2
- package/src/routing/__tests__/plotter.test.ts +230 -0
- package/src/routing/__tests__/result.test.ts +486 -125
- package/src/routing/__tests__/route.test.ts +7 -3
- package/src/routing/__tests__/router.test.ts +378 -172
- package/src/routing/plotter.ts +279 -48
- package/src/routing/result.ts +114 -34
- package/src/routing/route.ts +0 -3
- package/src/routing/router.ts +332 -209
- package/src/timetable/__tests__/io.test.ts +33 -1
- package/src/timetable/__tests__/route.test.ts +10 -3
- package/src/timetable/__tests__/timetable.test.ts +225 -57
- package/src/timetable/__tests__/tripId.test.ts +27 -0
- package/src/timetable/io.ts +71 -10
- package/src/timetable/proto/timetable.proto +14 -2
- package/src/timetable/proto/timetable.ts +218 -20
- package/src/timetable/route.ts +152 -19
- package/src/timetable/time.ts +23 -9
- package/src/timetable/timetable.ts +46 -9
- package/src/timetable/tripId.ts +29 -0
package/dist/cli.mjs
CHANGED
|
@@ -30,18 +30,6 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
30
30
|
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
function __rest(s, e) {
|
|
34
|
-
var t = {};
|
|
35
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
36
|
-
t[p] = s[p];
|
|
37
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
38
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
39
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
40
|
-
t[p[i]] = s[p[i]];
|
|
41
|
-
}
|
|
42
|
-
return t;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
33
|
function __awaiter(thisArg, _arguments, P, generator) {
|
|
46
34
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
47
35
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -16618,41 +16606,191 @@ const Transfer = {
|
|
|
16618
16606
|
return message;
|
|
16619
16607
|
},
|
|
16620
16608
|
};
|
|
16621
|
-
function
|
|
16622
|
-
return {
|
|
16609
|
+
function createBaseTripBoarding() {
|
|
16610
|
+
return { hopOnStop: 0, routeId: 0, tripIndex: 0 };
|
|
16623
16611
|
}
|
|
16624
|
-
const
|
|
16612
|
+
const TripBoarding = {
|
|
16625
16613
|
encode(message, writer = new BinaryWriter()) {
|
|
16626
|
-
|
|
16627
|
-
|
|
16614
|
+
if (message.hopOnStop !== 0) {
|
|
16615
|
+
writer.uint32(8).uint32(message.hopOnStop);
|
|
16628
16616
|
}
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16617
|
+
if (message.routeId !== 0) {
|
|
16618
|
+
writer.uint32(16).uint32(message.routeId);
|
|
16619
|
+
}
|
|
16620
|
+
if (message.tripIndex !== 0) {
|
|
16621
|
+
writer.uint32(24).uint32(message.tripIndex);
|
|
16632
16622
|
}
|
|
16633
|
-
writer.join();
|
|
16634
16623
|
return writer;
|
|
16635
16624
|
},
|
|
16636
16625
|
decode(input, length) {
|
|
16637
16626
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16638
16627
|
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16639
|
-
const message =
|
|
16628
|
+
const message = createBaseTripBoarding();
|
|
16640
16629
|
while (reader.pos < end) {
|
|
16641
16630
|
const tag = reader.uint32();
|
|
16642
16631
|
switch (tag >>> 3) {
|
|
16643
16632
|
case 1: {
|
|
16644
|
-
if (tag !==
|
|
16633
|
+
if (tag !== 8) {
|
|
16645
16634
|
break;
|
|
16646
16635
|
}
|
|
16647
|
-
message.
|
|
16636
|
+
message.hopOnStop = reader.uint32();
|
|
16637
|
+
continue;
|
|
16638
|
+
}
|
|
16639
|
+
case 2: {
|
|
16640
|
+
if (tag !== 16) {
|
|
16641
|
+
break;
|
|
16642
|
+
}
|
|
16643
|
+
message.routeId = reader.uint32();
|
|
16644
|
+
continue;
|
|
16645
|
+
}
|
|
16646
|
+
case 3: {
|
|
16647
|
+
if (tag !== 24) {
|
|
16648
|
+
break;
|
|
16649
|
+
}
|
|
16650
|
+
message.tripIndex = reader.uint32();
|
|
16651
|
+
continue;
|
|
16652
|
+
}
|
|
16653
|
+
}
|
|
16654
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
16655
|
+
break;
|
|
16656
|
+
}
|
|
16657
|
+
reader.skip(tag & 7);
|
|
16658
|
+
}
|
|
16659
|
+
return message;
|
|
16660
|
+
},
|
|
16661
|
+
fromJSON(object) {
|
|
16662
|
+
return {
|
|
16663
|
+
hopOnStop: isSet(object.hopOnStop) ? globalThis.Number(object.hopOnStop) : 0,
|
|
16664
|
+
routeId: isSet(object.routeId) ? globalThis.Number(object.routeId) : 0,
|
|
16665
|
+
tripIndex: isSet(object.tripIndex) ? globalThis.Number(object.tripIndex) : 0,
|
|
16666
|
+
};
|
|
16667
|
+
},
|
|
16668
|
+
toJSON(message) {
|
|
16669
|
+
const obj = {};
|
|
16670
|
+
if (message.hopOnStop !== 0) {
|
|
16671
|
+
obj.hopOnStop = Math.round(message.hopOnStop);
|
|
16672
|
+
}
|
|
16673
|
+
if (message.routeId !== 0) {
|
|
16674
|
+
obj.routeId = Math.round(message.routeId);
|
|
16675
|
+
}
|
|
16676
|
+
if (message.tripIndex !== 0) {
|
|
16677
|
+
obj.tripIndex = Math.round(message.tripIndex);
|
|
16678
|
+
}
|
|
16679
|
+
return obj;
|
|
16680
|
+
},
|
|
16681
|
+
create(base) {
|
|
16682
|
+
return TripBoarding.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
16683
|
+
},
|
|
16684
|
+
fromPartial(object) {
|
|
16685
|
+
var _a, _b, _c;
|
|
16686
|
+
const message = createBaseTripBoarding();
|
|
16687
|
+
message.hopOnStop = (_a = object.hopOnStop) !== null && _a !== void 0 ? _a : 0;
|
|
16688
|
+
message.routeId = (_b = object.routeId) !== null && _b !== void 0 ? _b : 0;
|
|
16689
|
+
message.tripIndex = (_c = object.tripIndex) !== null && _c !== void 0 ? _c : 0;
|
|
16690
|
+
return message;
|
|
16691
|
+
},
|
|
16692
|
+
};
|
|
16693
|
+
function createBaseTripContinuationEntry() {
|
|
16694
|
+
return { key: 0, value: [] };
|
|
16695
|
+
}
|
|
16696
|
+
const TripContinuationEntry = {
|
|
16697
|
+
encode(message, writer = new BinaryWriter()) {
|
|
16698
|
+
if (message.key !== 0) {
|
|
16699
|
+
writer.uint32(8).uint32(message.key);
|
|
16700
|
+
}
|
|
16701
|
+
for (const v of message.value) {
|
|
16702
|
+
TripBoarding.encode(v, writer.uint32(18).fork()).join();
|
|
16703
|
+
}
|
|
16704
|
+
return writer;
|
|
16705
|
+
},
|
|
16706
|
+
decode(input, length) {
|
|
16707
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16708
|
+
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16709
|
+
const message = createBaseTripContinuationEntry();
|
|
16710
|
+
while (reader.pos < end) {
|
|
16711
|
+
const tag = reader.uint32();
|
|
16712
|
+
switch (tag >>> 3) {
|
|
16713
|
+
case 1: {
|
|
16714
|
+
if (tag !== 8) {
|
|
16715
|
+
break;
|
|
16716
|
+
}
|
|
16717
|
+
message.key = reader.uint32();
|
|
16648
16718
|
continue;
|
|
16649
16719
|
}
|
|
16650
16720
|
case 2: {
|
|
16651
|
-
if (tag
|
|
16721
|
+
if (tag !== 18) {
|
|
16722
|
+
break;
|
|
16723
|
+
}
|
|
16724
|
+
message.value.push(TripBoarding.decode(reader, reader.uint32()));
|
|
16725
|
+
continue;
|
|
16726
|
+
}
|
|
16727
|
+
}
|
|
16728
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
16729
|
+
break;
|
|
16730
|
+
}
|
|
16731
|
+
reader.skip(tag & 7);
|
|
16732
|
+
}
|
|
16733
|
+
return message;
|
|
16734
|
+
},
|
|
16735
|
+
fromJSON(object) {
|
|
16736
|
+
return {
|
|
16737
|
+
key: isSet(object.key) ? globalThis.Number(object.key) : 0,
|
|
16738
|
+
value: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.value) ? object.value.map((e) => TripBoarding.fromJSON(e)) : [],
|
|
16739
|
+
};
|
|
16740
|
+
},
|
|
16741
|
+
toJSON(message) {
|
|
16742
|
+
var _a;
|
|
16743
|
+
const obj = {};
|
|
16744
|
+
if (message.key !== 0) {
|
|
16745
|
+
obj.key = Math.round(message.key);
|
|
16746
|
+
}
|
|
16747
|
+
if ((_a = message.value) === null || _a === void 0 ? void 0 : _a.length) {
|
|
16748
|
+
obj.value = message.value.map((e) => TripBoarding.toJSON(e));
|
|
16749
|
+
}
|
|
16750
|
+
return obj;
|
|
16751
|
+
},
|
|
16752
|
+
create(base) {
|
|
16753
|
+
return TripContinuationEntry.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
16754
|
+
},
|
|
16755
|
+
fromPartial(object) {
|
|
16756
|
+
var _a, _b;
|
|
16757
|
+
const message = createBaseTripContinuationEntry();
|
|
16758
|
+
message.key = (_a = object.key) !== null && _a !== void 0 ? _a : 0;
|
|
16759
|
+
message.value = ((_b = object.value) === null || _b === void 0 ? void 0 : _b.map((e) => TripBoarding.fromPartial(e))) || [];
|
|
16760
|
+
return message;
|
|
16761
|
+
},
|
|
16762
|
+
};
|
|
16763
|
+
function createBaseStopAdjacency() {
|
|
16764
|
+
return { routes: [], transfers: [], tripContinuations: [] };
|
|
16765
|
+
}
|
|
16766
|
+
const StopAdjacency = {
|
|
16767
|
+
encode(message, writer = new BinaryWriter()) {
|
|
16768
|
+
writer.uint32(10).fork();
|
|
16769
|
+
for (const v of message.routes) {
|
|
16770
|
+
writer.uint32(v);
|
|
16771
|
+
}
|
|
16772
|
+
writer.join();
|
|
16773
|
+
for (const v of message.transfers) {
|
|
16774
|
+
Transfer.encode(v, writer.uint32(18).fork()).join();
|
|
16775
|
+
}
|
|
16776
|
+
for (const v of message.tripContinuations) {
|
|
16777
|
+
TripContinuationEntry.encode(v, writer.uint32(26).fork()).join();
|
|
16778
|
+
}
|
|
16779
|
+
return writer;
|
|
16780
|
+
},
|
|
16781
|
+
decode(input, length) {
|
|
16782
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16783
|
+
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16784
|
+
const message = createBaseStopAdjacency();
|
|
16785
|
+
while (reader.pos < end) {
|
|
16786
|
+
const tag = reader.uint32();
|
|
16787
|
+
switch (tag >>> 3) {
|
|
16788
|
+
case 1: {
|
|
16789
|
+
if (tag === 8) {
|
|
16652
16790
|
message.routes.push(reader.uint32());
|
|
16653
16791
|
continue;
|
|
16654
16792
|
}
|
|
16655
|
-
if (tag ===
|
|
16793
|
+
if (tag === 10) {
|
|
16656
16794
|
const end2 = reader.uint32() + reader.pos;
|
|
16657
16795
|
while (reader.pos < end2) {
|
|
16658
16796
|
message.routes.push(reader.uint32());
|
|
@@ -16661,6 +16799,20 @@ const StopAdjacency = {
|
|
|
16661
16799
|
}
|
|
16662
16800
|
break;
|
|
16663
16801
|
}
|
|
16802
|
+
case 2: {
|
|
16803
|
+
if (tag !== 18) {
|
|
16804
|
+
break;
|
|
16805
|
+
}
|
|
16806
|
+
message.transfers.push(Transfer.decode(reader, reader.uint32()));
|
|
16807
|
+
continue;
|
|
16808
|
+
}
|
|
16809
|
+
case 3: {
|
|
16810
|
+
if (tag !== 26) {
|
|
16811
|
+
break;
|
|
16812
|
+
}
|
|
16813
|
+
message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
|
|
16814
|
+
continue;
|
|
16815
|
+
}
|
|
16664
16816
|
}
|
|
16665
16817
|
if ((tag & 7) === 4 || tag === 0) {
|
|
16666
16818
|
break;
|
|
@@ -16671,20 +16823,26 @@ const StopAdjacency = {
|
|
|
16671
16823
|
},
|
|
16672
16824
|
fromJSON(object) {
|
|
16673
16825
|
return {
|
|
16826
|
+
routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
|
|
16674
16827
|
transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
|
|
16675
16828
|
? object.transfers.map((e) => Transfer.fromJSON(e))
|
|
16676
16829
|
: [],
|
|
16677
|
-
|
|
16830
|
+
tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
|
|
16831
|
+
? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
|
|
16832
|
+
: [],
|
|
16678
16833
|
};
|
|
16679
16834
|
},
|
|
16680
16835
|
toJSON(message) {
|
|
16681
|
-
var _a, _b;
|
|
16836
|
+
var _a, _b, _c;
|
|
16682
16837
|
const obj = {};
|
|
16683
|
-
if ((_a = message.
|
|
16838
|
+
if ((_a = message.routes) === null || _a === void 0 ? void 0 : _a.length) {
|
|
16839
|
+
obj.routes = message.routes.map((e) => Math.round(e));
|
|
16840
|
+
}
|
|
16841
|
+
if ((_b = message.transfers) === null || _b === void 0 ? void 0 : _b.length) {
|
|
16684
16842
|
obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
|
|
16685
16843
|
}
|
|
16686
|
-
if ((
|
|
16687
|
-
obj.
|
|
16844
|
+
if ((_c = message.tripContinuations) === null || _c === void 0 ? void 0 : _c.length) {
|
|
16845
|
+
obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
|
|
16688
16846
|
}
|
|
16689
16847
|
return obj;
|
|
16690
16848
|
},
|
|
@@ -16692,10 +16850,11 @@ const StopAdjacency = {
|
|
|
16692
16850
|
return StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
16693
16851
|
},
|
|
16694
16852
|
fromPartial(object) {
|
|
16695
|
-
var _a, _b;
|
|
16853
|
+
var _a, _b, _c;
|
|
16696
16854
|
const message = createBaseStopAdjacency();
|
|
16697
|
-
message.
|
|
16698
|
-
message.
|
|
16855
|
+
message.routes = ((_a = object.routes) === null || _a === void 0 ? void 0 : _a.map((e) => e)) || [];
|
|
16856
|
+
message.transfers = ((_b = object.transfers) === null || _b === void 0 ? void 0 : _b.map((e) => Transfer.fromPartial(e))) || [];
|
|
16857
|
+
message.tripContinuations = ((_c = object.tripContinuations) === null || _c === void 0 ? void 0 : _c.map((e) => TripContinuationEntry.fromPartial(e))) || [];
|
|
16699
16858
|
return message;
|
|
16700
16859
|
},
|
|
16701
16860
|
};
|
|
@@ -17094,9 +17253,16 @@ class Time {
|
|
|
17094
17253
|
if (times.length === 0) {
|
|
17095
17254
|
throw new Error('At least one Time instance is required.');
|
|
17096
17255
|
}
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17256
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17257
|
+
let maxTime = times[0];
|
|
17258
|
+
for (let i = 1; i < times.length; i++) {
|
|
17259
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17260
|
+
if (times[i].minutesSinceMidnight > maxTime.minutesSinceMidnight) {
|
|
17261
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17262
|
+
maxTime = times[i];
|
|
17263
|
+
}
|
|
17264
|
+
}
|
|
17265
|
+
return maxTime;
|
|
17100
17266
|
}
|
|
17101
17267
|
/**
|
|
17102
17268
|
* Computes the minimum Time instance among the provided Time instances.
|
|
@@ -17108,9 +17274,16 @@ class Time {
|
|
|
17108
17274
|
if (times.length === 0) {
|
|
17109
17275
|
throw new Error('At least one Time instance is required.');
|
|
17110
17276
|
}
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17277
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17278
|
+
let minTime = times[0];
|
|
17279
|
+
for (let i = 1; i < times.length; i++) {
|
|
17280
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17281
|
+
if (times[i].minutesSinceMidnight < minTime.minutesSinceMidnight) {
|
|
17282
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17283
|
+
minTime = times[i];
|
|
17284
|
+
}
|
|
17285
|
+
}
|
|
17286
|
+
return minTime;
|
|
17114
17287
|
}
|
|
17115
17288
|
/**
|
|
17116
17289
|
* Determines if the current Time instance is after another Time instance.
|
|
@@ -17119,7 +17292,7 @@ class Time {
|
|
|
17119
17292
|
* @returns True if the current Time instance is after the other Time instance, otherwise false.
|
|
17120
17293
|
*/
|
|
17121
17294
|
isAfter(otherTime) {
|
|
17122
|
-
return this.minutesSinceMidnight > otherTime.
|
|
17295
|
+
return this.minutesSinceMidnight > otherTime.minutesSinceMidnight;
|
|
17123
17296
|
}
|
|
17124
17297
|
/**
|
|
17125
17298
|
* Determines if the current Time instance is before another Time instance.
|
|
@@ -17128,7 +17301,7 @@ class Time {
|
|
|
17128
17301
|
* @returns True if the current Time instance is before the other Time instance, otherwise false.
|
|
17129
17302
|
*/
|
|
17130
17303
|
isBefore(otherTime) {
|
|
17131
|
-
return this.minutesSinceMidnight < otherTime.
|
|
17304
|
+
return this.minutesSinceMidnight < otherTime.minutesSinceMidnight;
|
|
17132
17305
|
}
|
|
17133
17306
|
/**
|
|
17134
17307
|
* Determines if the current Time instance is equal to another Time instance.
|
|
@@ -17137,7 +17310,7 @@ class Time {
|
|
|
17137
17310
|
* @returns True if the current Time instance is equal to the other Time instance, otherwise false.
|
|
17138
17311
|
*/
|
|
17139
17312
|
equals(otherTime) {
|
|
17140
|
-
return this.minutesSinceMidnight === otherTime.
|
|
17313
|
+
return this.minutesSinceMidnight === otherTime.minutesSinceMidnight;
|
|
17141
17314
|
}
|
|
17142
17315
|
}
|
|
17143
17316
|
|
|
@@ -17170,7 +17343,8 @@ const toPickupDropOffType = (numericalType) => {
|
|
|
17170
17343
|
* A route identifies all trips of a given service route sharing the same list of stops.
|
|
17171
17344
|
*/
|
|
17172
17345
|
let Route$1 = class Route {
|
|
17173
|
-
constructor(stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
|
|
17346
|
+
constructor(id, stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
|
|
17347
|
+
this.id = id;
|
|
17174
17348
|
this.stopTimes = stopTimes;
|
|
17175
17349
|
this.pickUpDropOffTypes = pickUpDropOffTypes;
|
|
17176
17350
|
this.stops = stops;
|
|
@@ -17183,6 +17357,78 @@ let Route$1 = class Route {
|
|
|
17183
17357
|
this.stopIndices.set(stops[i], i);
|
|
17184
17358
|
}
|
|
17185
17359
|
}
|
|
17360
|
+
/**
|
|
17361
|
+
* Creates a new route from multiple trips with their stops.
|
|
17362
|
+
*
|
|
17363
|
+
* @param params The route parameters including ID, service route ID, and trips.
|
|
17364
|
+
* @returns The new route.
|
|
17365
|
+
*/
|
|
17366
|
+
static of(params) {
|
|
17367
|
+
var _a, _b;
|
|
17368
|
+
const { id, serviceRouteId, trips } = params;
|
|
17369
|
+
if (trips.length === 0) {
|
|
17370
|
+
throw new Error('At least one trip must be provided');
|
|
17371
|
+
}
|
|
17372
|
+
// All trips must have the same stops in the same order
|
|
17373
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17374
|
+
const firstTrip = trips[0];
|
|
17375
|
+
const stopIds = new Uint32Array(firstTrip.stops.map((stop) => stop.id));
|
|
17376
|
+
const numStops = stopIds.length;
|
|
17377
|
+
// Validate all trips have the same stops
|
|
17378
|
+
for (let tripIndex = 1; tripIndex < trips.length; tripIndex++) {
|
|
17379
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17380
|
+
const trip = trips[tripIndex];
|
|
17381
|
+
if (trip.stops.length !== numStops) {
|
|
17382
|
+
throw new Error(`Trip ${tripIndex} has ${trip.stops.length} stops, expected ${numStops}`);
|
|
17383
|
+
}
|
|
17384
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17385
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17386
|
+
if (trip.stops[stopIndex].id !== stopIds[stopIndex]) {
|
|
17387
|
+
throw new Error(`Trip ${tripIndex} has different stop at index ${stopIndex}`);
|
|
17388
|
+
}
|
|
17389
|
+
}
|
|
17390
|
+
}
|
|
17391
|
+
// Create stopTimes array with arrivals and departures for all trips
|
|
17392
|
+
const stopTimes = new Uint16Array(trips.length * numStops * 2);
|
|
17393
|
+
for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
|
|
17394
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17395
|
+
const trip = trips[tripIndex];
|
|
17396
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17397
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17398
|
+
const stop = trip.stops[stopIndex];
|
|
17399
|
+
const baseIndex = (tripIndex * numStops + stopIndex) * 2;
|
|
17400
|
+
stopTimes[baseIndex] = stop.arrivalTime.toMinutes();
|
|
17401
|
+
stopTimes[baseIndex + 1] = stop.departureTime.toMinutes();
|
|
17402
|
+
}
|
|
17403
|
+
}
|
|
17404
|
+
// Create pickUpDropOffTypes array (2-bit encoded) for all trips
|
|
17405
|
+
const totalStopEntries = trips.length * numStops;
|
|
17406
|
+
const pickUpDropOffTypes = new Uint8Array(Math.ceil(totalStopEntries / 2));
|
|
17407
|
+
for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
|
|
17408
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17409
|
+
const trip = trips[tripIndex];
|
|
17410
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17411
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17412
|
+
const stop = trip.stops[stopIndex];
|
|
17413
|
+
const globalIndex = tripIndex * numStops + stopIndex;
|
|
17414
|
+
const pickUp = (_a = stop.pickUpType) !== null && _a !== void 0 ? _a : REGULAR;
|
|
17415
|
+
const dropOff = (_b = stop.dropOffType) !== null && _b !== void 0 ? _b : REGULAR;
|
|
17416
|
+
const byteIndex = Math.floor(globalIndex / 2);
|
|
17417
|
+
const isSecondPair = globalIndex % 2 === 1;
|
|
17418
|
+
if (isSecondPair) {
|
|
17419
|
+
// Second pair: pickup in upper 2 bits, dropOff in bits 4-5
|
|
17420
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17421
|
+
pickUpDropOffTypes[byteIndex] |= (pickUp << 6) | (dropOff << 4);
|
|
17422
|
+
}
|
|
17423
|
+
else {
|
|
17424
|
+
// First pair: pickup in bits 2-3, dropOff in lower 2 bits
|
|
17425
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17426
|
+
pickUpDropOffTypes[byteIndex] |= (pickUp << 2) | dropOff;
|
|
17427
|
+
}
|
|
17428
|
+
}
|
|
17429
|
+
}
|
|
17430
|
+
return new Route(id, stopTimes, pickUpDropOffTypes, stopIds, serviceRouteId);
|
|
17431
|
+
}
|
|
17186
17432
|
/**
|
|
17187
17433
|
* Serializes the Route into binary arrays.
|
|
17188
17434
|
*
|
|
@@ -17206,11 +17452,11 @@ let Route$1 = class Route {
|
|
|
17206
17452
|
isBefore(stopA, stopB) {
|
|
17207
17453
|
const stopAIndex = this.stopIndices.get(stopA);
|
|
17208
17454
|
if (stopAIndex === undefined) {
|
|
17209
|
-
throw new Error(`Stop index ${
|
|
17455
|
+
throw new Error(`Stop index not found for ${stopA} in route ${this.serviceRouteId}`);
|
|
17210
17456
|
}
|
|
17211
17457
|
const stopBIndex = this.stopIndices.get(stopB);
|
|
17212
17458
|
if (stopBIndex === undefined) {
|
|
17213
|
-
throw new Error(`Stop index ${
|
|
17459
|
+
throw new Error(`Stop index not found for ${stopB} in route ${this.serviceRouteId}`);
|
|
17214
17460
|
}
|
|
17215
17461
|
return stopAIndex < stopBIndex;
|
|
17216
17462
|
}
|
|
@@ -17222,6 +17468,14 @@ let Route$1 = class Route {
|
|
|
17222
17468
|
getNbStops() {
|
|
17223
17469
|
return this.nbStops;
|
|
17224
17470
|
}
|
|
17471
|
+
/**
|
|
17472
|
+
* Retrieves the number of trips in the route.
|
|
17473
|
+
*
|
|
17474
|
+
* @returns The total number of trips in the route.
|
|
17475
|
+
*/
|
|
17476
|
+
getNbTrips() {
|
|
17477
|
+
return this.nbTrips;
|
|
17478
|
+
}
|
|
17225
17479
|
/**
|
|
17226
17480
|
* Finds the ServiceRouteId of the route. It corresponds the identifier
|
|
17227
17481
|
* of the service shown to the end user as a route.
|
|
@@ -17239,7 +17493,7 @@ let Route$1 = class Route {
|
|
|
17239
17493
|
* @returns The arrival time at the specified stop and trip as a Time object.
|
|
17240
17494
|
*/
|
|
17241
17495
|
arrivalAt(stopId, tripIndex) {
|
|
17242
|
-
const arrivalIndex = (tripIndex * this.stops.length + this.
|
|
17496
|
+
const arrivalIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2;
|
|
17243
17497
|
const arrival = this.stopTimes[arrivalIndex];
|
|
17244
17498
|
if (arrival === undefined) {
|
|
17245
17499
|
throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
@@ -17254,7 +17508,7 @@ let Route$1 = class Route {
|
|
|
17254
17508
|
* @returns The departure time at the specified stop and trip as a Time object.
|
|
17255
17509
|
*/
|
|
17256
17510
|
departureFrom(stopId, tripIndex) {
|
|
17257
|
-
const departureIndex = (tripIndex * this.stops.length + this.
|
|
17511
|
+
const departureIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2 + 1;
|
|
17258
17512
|
const departure = this.stopTimes[departureIndex];
|
|
17259
17513
|
if (departure === undefined) {
|
|
17260
17514
|
throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
@@ -17269,7 +17523,7 @@ let Route$1 = class Route {
|
|
|
17269
17523
|
* @returns The pick-up type at the specified stop and trip.
|
|
17270
17524
|
*/
|
|
17271
17525
|
pickUpTypeFrom(stopId, tripIndex) {
|
|
17272
|
-
const globalIndex = tripIndex * this.stops.length + this.
|
|
17526
|
+
const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
|
|
17273
17527
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17274
17528
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17275
17529
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
@@ -17289,7 +17543,7 @@ let Route$1 = class Route {
|
|
|
17289
17543
|
* @returns The drop-off type at the specified stop and trip.
|
|
17290
17544
|
*/
|
|
17291
17545
|
dropOffTypeAt(stopId, tripIndex) {
|
|
17292
|
-
const globalIndex = tripIndex * this.stops.length + this.
|
|
17546
|
+
const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
|
|
17293
17547
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17294
17548
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17295
17549
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
@@ -17349,13 +17603,25 @@ let Route$1 = class Route {
|
|
|
17349
17603
|
* @param stopId The StopId of the stop to locate in the route.
|
|
17350
17604
|
* @returns The index of the stop in the route.
|
|
17351
17605
|
*/
|
|
17352
|
-
|
|
17606
|
+
stopRouteIndex(stopId) {
|
|
17353
17607
|
const stopIndex = this.stopIndices.get(stopId);
|
|
17354
17608
|
if (stopIndex === undefined) {
|
|
17355
17609
|
throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
|
|
17356
17610
|
}
|
|
17357
17611
|
return stopIndex;
|
|
17358
17612
|
}
|
|
17613
|
+
/**
|
|
17614
|
+
* Retrieves the id of a stop at a given index in a route.
|
|
17615
|
+
* @param stopRouteIndex The route index of the stop.
|
|
17616
|
+
* @returns The id of the stop at the given index in the route.
|
|
17617
|
+
*/
|
|
17618
|
+
stopId(stopRouteIndex) {
|
|
17619
|
+
const stopId = this.stops[stopRouteIndex];
|
|
17620
|
+
if (stopId === undefined) {
|
|
17621
|
+
throw new Error(`StopId for stop at index ${stopRouteIndex} not found in route ${this.serviceRouteId}`);
|
|
17622
|
+
}
|
|
17623
|
+
return stopId;
|
|
17624
|
+
}
|
|
17359
17625
|
};
|
|
17360
17626
|
|
|
17361
17627
|
const isLittleEndian = (() => {
|
|
@@ -17426,10 +17692,15 @@ const bytesToUint16Array = (bytes) => {
|
|
|
17426
17692
|
const serializeStopsAdjacency = (stopsAdjacency) => {
|
|
17427
17693
|
return stopsAdjacency.map((value) => {
|
|
17428
17694
|
return {
|
|
17429
|
-
transfers: value.transfers
|
|
17430
|
-
|
|
17431
|
-
|
|
17695
|
+
transfers: value.transfers
|
|
17696
|
+
? value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: serializeTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
|
|
17697
|
+
minTransferTime: transfer.minTransferTime.toSeconds(),
|
|
17698
|
+
}))))
|
|
17699
|
+
: [],
|
|
17432
17700
|
routes: value.routes,
|
|
17701
|
+
tripContinuations: value.tripContinuations
|
|
17702
|
+
? serializeTripContinuations(value.tripContinuations)
|
|
17703
|
+
: [],
|
|
17433
17704
|
};
|
|
17434
17705
|
});
|
|
17435
17706
|
};
|
|
@@ -17469,10 +17740,17 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
|
|
|
17469
17740
|
}));
|
|
17470
17741
|
transfers.push(newTransfer);
|
|
17471
17742
|
}
|
|
17472
|
-
|
|
17473
|
-
transfers: transfers,
|
|
17743
|
+
const stopAdjacency = {
|
|
17474
17744
|
routes: value.routes,
|
|
17475
|
-
}
|
|
17745
|
+
};
|
|
17746
|
+
if (transfers.length > 0) {
|
|
17747
|
+
stopAdjacency.transfers = transfers;
|
|
17748
|
+
}
|
|
17749
|
+
const deserializedTripContinuations = deserializeTripContinuations(value.tripContinuations);
|
|
17750
|
+
if (deserializedTripContinuations.size > 0) {
|
|
17751
|
+
stopAdjacency.tripContinuations = deserializedTripContinuations;
|
|
17752
|
+
}
|
|
17753
|
+
result.push(stopAdjacency);
|
|
17476
17754
|
}
|
|
17477
17755
|
return result;
|
|
17478
17756
|
};
|
|
@@ -17482,7 +17760,7 @@ const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
|
|
|
17482
17760
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17483
17761
|
const value = protoRoutesAdjacency[i];
|
|
17484
17762
|
const stops = bytesToUint32Array(value.stops);
|
|
17485
|
-
routesAdjacency.push(new Route$1(bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
|
|
17763
|
+
routesAdjacency.push(new Route$1(i, bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
|
|
17486
17764
|
}
|
|
17487
17765
|
return routesAdjacency;
|
|
17488
17766
|
};
|
|
@@ -17576,7 +17854,48 @@ const serializeRouteType = (type) => {
|
|
|
17576
17854
|
return RouteType.MONORAIL;
|
|
17577
17855
|
}
|
|
17578
17856
|
};
|
|
17857
|
+
const serializeTripContinuations = (tripContinuations) => {
|
|
17858
|
+
const result = [];
|
|
17859
|
+
for (const [key, value] of tripContinuations.entries()) {
|
|
17860
|
+
result.push({
|
|
17861
|
+
key: key,
|
|
17862
|
+
value: value.map((tripBoarding) => ({
|
|
17863
|
+
hopOnStop: tripBoarding.hopOnStop,
|
|
17864
|
+
routeId: tripBoarding.routeId,
|
|
17865
|
+
tripIndex: tripBoarding.tripIndex,
|
|
17866
|
+
})),
|
|
17867
|
+
});
|
|
17868
|
+
}
|
|
17869
|
+
return result;
|
|
17870
|
+
};
|
|
17871
|
+
const deserializeTripContinuations = (protoTripContinuations) => {
|
|
17872
|
+
const result = new Map();
|
|
17873
|
+
for (let i = 0; i < protoTripContinuations.length; i++) {
|
|
17874
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17875
|
+
const entry = protoTripContinuations[i];
|
|
17876
|
+
const tripBoardings = entry.value.map((protoTripBoarding) => ({
|
|
17877
|
+
hopOnStop: protoTripBoarding.hopOnStop,
|
|
17878
|
+
routeId: protoTripBoarding.routeId,
|
|
17879
|
+
tripIndex: protoTripBoarding.tripIndex,
|
|
17880
|
+
}));
|
|
17881
|
+
result.set(entry.key, tripBoardings);
|
|
17882
|
+
}
|
|
17883
|
+
return result;
|
|
17884
|
+
};
|
|
17885
|
+
|
|
17886
|
+
// const ROUTE_ID_BITS = 17;
|
|
17887
|
+
const TRIP_INDEX_BITS = 15;
|
|
17888
|
+
/**
|
|
17889
|
+
* Encodes a route ID and trip index into a single trip ID.
|
|
17890
|
+
* @param routeId - The route identifier, needs to fit on 17 bits
|
|
17891
|
+
* @param tripIndex - The index of the trip within the route, needs to fit on 15 bits
|
|
17892
|
+
* @returns The encoded trip ID
|
|
17893
|
+
*/
|
|
17894
|
+
const encode = (routeId, tripIndex) => {
|
|
17895
|
+
return (routeId << TRIP_INDEX_BITS) | tripIndex;
|
|
17896
|
+
};
|
|
17579
17897
|
|
|
17898
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
17580
17899
|
const ALL_TRANSPORT_MODES = new Set([
|
|
17581
17900
|
'TRAM',
|
|
17582
17901
|
'SUBWAY',
|
|
@@ -17589,7 +17908,8 @@ const ALL_TRANSPORT_MODES = new Set([
|
|
|
17589
17908
|
'TROLLEYBUS',
|
|
17590
17909
|
'MONORAIL',
|
|
17591
17910
|
]);
|
|
17592
|
-
const
|
|
17911
|
+
const EMPTY_TRIP_CONTINUATIONS = [];
|
|
17912
|
+
const CURRENT_VERSION = '0.0.8';
|
|
17593
17913
|
/**
|
|
17594
17914
|
* The internal transit timetable format.
|
|
17595
17915
|
*/
|
|
@@ -17600,9 +17920,10 @@ class Timetable {
|
|
|
17600
17920
|
this.serviceRoutes = routes;
|
|
17601
17921
|
this.activeStops = new Set();
|
|
17602
17922
|
for (let i = 0; i < stopsAdjacency.length; i++) {
|
|
17603
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17604
17923
|
const stop = stopsAdjacency[i];
|
|
17605
|
-
if (stop.routes.length > 0 ||
|
|
17924
|
+
if (stop.routes.length > 0 ||
|
|
17925
|
+
(stop.transfers && stop.transfers.length > 0) ||
|
|
17926
|
+
(stop.tripContinuations && stop.tripContinuations.size > 0)) {
|
|
17606
17927
|
this.activeStops.add(i);
|
|
17607
17928
|
}
|
|
17608
17929
|
}
|
|
@@ -17665,8 +17986,27 @@ class Timetable {
|
|
|
17665
17986
|
* @returns An array of transfer options available at the stop.
|
|
17666
17987
|
*/
|
|
17667
17988
|
getTransfers(stopId) {
|
|
17668
|
-
|
|
17669
|
-
|
|
17989
|
+
const stopAdjacency = this.stopsAdjacency[stopId];
|
|
17990
|
+
if (!stopAdjacency) {
|
|
17991
|
+
throw new Error(`Stop ID ${stopId} not found`);
|
|
17992
|
+
}
|
|
17993
|
+
return stopAdjacency.transfers || [];
|
|
17994
|
+
}
|
|
17995
|
+
/**
|
|
17996
|
+
* Retrieves all trip continuation options available at the specified stop for a given trip.
|
|
17997
|
+
*
|
|
17998
|
+
* @param stopId - The ID of the stop to get trip continuations for.
|
|
17999
|
+
* @param tripIndex - The index of the trip to get continuations for.
|
|
18000
|
+
* @returns An array of trip continuation options available at the stop for the specified trip.
|
|
18001
|
+
*/
|
|
18002
|
+
getContinuousTrips(stopId, routeId, tripIndex) {
|
|
18003
|
+
var _a;
|
|
18004
|
+
const stopAdjacency = this.stopsAdjacency[stopId];
|
|
18005
|
+
if (!stopAdjacency) {
|
|
18006
|
+
throw new Error(`Stop ID ${stopId} not found`);
|
|
18007
|
+
}
|
|
18008
|
+
return (((_a = stopAdjacency.tripContinuations) === null || _a === void 0 ? void 0 : _a.get(encode(routeId, tripIndex))) ||
|
|
18009
|
+
EMPTY_TRIP_CONTINUATIONS);
|
|
17670
18010
|
}
|
|
17671
18011
|
/**
|
|
17672
18012
|
* Retrieves the service route associated with the given route.
|
|
@@ -17681,9 +18021,7 @@ class Timetable {
|
|
|
17681
18021
|
if (!serviceRoute) {
|
|
17682
18022
|
throw new Error(`Service route not found for route ID: ${route.serviceRoute()}`);
|
|
17683
18023
|
}
|
|
17684
|
-
|
|
17685
|
-
const { routes } = serviceRoute, serviceRouteInfo = __rest(serviceRoute, ["routes"]);
|
|
17686
|
-
return serviceRouteInfo;
|
|
18024
|
+
return { type: serviceRoute.type, name: serviceRoute.name };
|
|
17687
18025
|
}
|
|
17688
18026
|
/**
|
|
17689
18027
|
* Finds all routes passing through a stop.
|
|
@@ -19954,6 +20292,7 @@ const parseGtfsLocationType = (gtfsLocationType) => {
|
|
|
19954
20292
|
const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19955
20293
|
var _a, e_1, _b, _c;
|
|
19956
20294
|
const transfers = new Map();
|
|
20295
|
+
const tripContinuations = new Map();
|
|
19957
20296
|
try {
|
|
19958
20297
|
for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
|
|
19959
20298
|
'transfer_type',
|
|
@@ -19967,25 +20306,48 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
|
|
|
19967
20306
|
transferEntry.transfer_type === 5) {
|
|
19968
20307
|
continue;
|
|
19969
20308
|
}
|
|
19970
|
-
if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
|
|
19971
|
-
console.warn(`Unsupported transfer between trips ${transferEntry.from_trip_id} and ${transferEntry.to_trip_id}.`);
|
|
19972
|
-
continue;
|
|
19973
|
-
}
|
|
19974
|
-
if (transferEntry.from_route_id && transferEntry.to_route_id) {
|
|
19975
|
-
console.warn(`Unsupported transfer between routes ${transferEntry.from_route_id} and ${transferEntry.to_route_id}.`);
|
|
19976
|
-
continue;
|
|
19977
|
-
}
|
|
19978
20309
|
if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
|
|
19979
20310
|
console.warn(`Missing transfer origin or destination stop.`);
|
|
19980
20311
|
continue;
|
|
19981
20312
|
}
|
|
19982
|
-
if (transferEntry.transfer_type === 2 && !transferEntry.min_transfer_time) {
|
|
19983
|
-
console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
|
|
19984
|
-
}
|
|
19985
20313
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19986
20314
|
const fromStop = stopsMap.get(transferEntry.from_stop_id);
|
|
19987
20315
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19988
20316
|
const toStop = stopsMap.get(transferEntry.to_stop_id);
|
|
20317
|
+
if (transferEntry.transfer_type === 4) {
|
|
20318
|
+
if (transferEntry.from_trip_id === undefined ||
|
|
20319
|
+
transferEntry.from_trip_id === '' ||
|
|
20320
|
+
transferEntry.to_trip_id === undefined ||
|
|
20321
|
+
transferEntry.to_trip_id === '') {
|
|
20322
|
+
console.warn(`Unsupported in-seat transfer, missing from_trip_id and/or to_trip_id.`);
|
|
20323
|
+
continue;
|
|
20324
|
+
}
|
|
20325
|
+
const tripBoardingEntry = {
|
|
20326
|
+
fromTrip: transferEntry.from_trip_id,
|
|
20327
|
+
toTrip: transferEntry.to_trip_id,
|
|
20328
|
+
hopOnStop: toStop.id,
|
|
20329
|
+
};
|
|
20330
|
+
const existingBoardings = tripContinuations.get(fromStop.id);
|
|
20331
|
+
if (existingBoardings) {
|
|
20332
|
+
existingBoardings.push(tripBoardingEntry);
|
|
20333
|
+
}
|
|
20334
|
+
else {
|
|
20335
|
+
tripContinuations.set(fromStop.id, [tripBoardingEntry]);
|
|
20336
|
+
}
|
|
20337
|
+
continue;
|
|
20338
|
+
}
|
|
20339
|
+
if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
|
|
20340
|
+
console.warn(`Unsupported transfer of type ${transferEntry.transfer_type} between trips ${transferEntry.from_trip_id} and ${transferEntry.to_trip_id}.`);
|
|
20341
|
+
continue;
|
|
20342
|
+
}
|
|
20343
|
+
if (transferEntry.from_route_id && transferEntry.to_route_id) {
|
|
20344
|
+
console.warn(`Unsupported transfer of type ${transferEntry.transfer_type} between routes ${transferEntry.from_route_id} and ${transferEntry.to_route_id}.`);
|
|
20345
|
+
continue;
|
|
20346
|
+
}
|
|
20347
|
+
if (transferEntry.transfer_type === 2 &&
|
|
20348
|
+
transferEntry.min_transfer_time === undefined) {
|
|
20349
|
+
console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
|
|
20350
|
+
}
|
|
19989
20351
|
const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
|
|
19990
20352
|
minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
|
|
19991
20353
|
}));
|
|
@@ -20001,7 +20363,10 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
|
|
|
20001
20363
|
}
|
|
20002
20364
|
finally { if (e_1) throw e_1.error; }
|
|
20003
20365
|
}
|
|
20004
|
-
return
|
|
20366
|
+
return {
|
|
20367
|
+
transfers,
|
|
20368
|
+
tripContinuations,
|
|
20369
|
+
};
|
|
20005
20370
|
});
|
|
20006
20371
|
const parseGtfsTransferType = (gtfsTransferType) => {
|
|
20007
20372
|
switch (gtfsTransferType) {
|
|
@@ -20064,11 +20429,13 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20064
20429
|
const stopTimesArray = new Uint16Array(stopsCount * tripsCount * 2);
|
|
20065
20430
|
const allPickUpTypes = [];
|
|
20066
20431
|
const allDropOffTypes = [];
|
|
20432
|
+
const gtfsTripIds = [];
|
|
20067
20433
|
for (let tripIndex = 0; tripIndex < tripsCount; tripIndex++) {
|
|
20068
20434
|
const trip = builder.trips[tripIndex];
|
|
20069
20435
|
if (!trip) {
|
|
20070
20436
|
throw new Error(`Missing trip data at index ${tripIndex}`);
|
|
20071
20437
|
}
|
|
20438
|
+
gtfsTripIds.push(trip.gtfsTripId);
|
|
20072
20439
|
const baseIndex = tripIndex * stopsCount * 2;
|
|
20073
20440
|
for (let stopIndex = 0; stopIndex < stopsCount; stopIndex++) {
|
|
20074
20441
|
const timeIndex = baseIndex + stopIndex * 2;
|
|
@@ -20090,12 +20457,15 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20090
20457
|
}
|
|
20091
20458
|
// Use 2-bit encoding for pickup/drop-off types
|
|
20092
20459
|
const pickUpDropOffTypesArray = encodePickUpDropOffTypes(allPickUpTypes, allDropOffTypes);
|
|
20093
|
-
return
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20460
|
+
return [
|
|
20461
|
+
{
|
|
20462
|
+
serviceRouteId: builder.serviceRouteId,
|
|
20463
|
+
stops: stopsArray,
|
|
20464
|
+
stopTimes: stopTimesArray,
|
|
20465
|
+
pickUpDropOffTypes: pickUpDropOffTypesArray,
|
|
20466
|
+
},
|
|
20467
|
+
gtfsTripIds,
|
|
20468
|
+
];
|
|
20099
20469
|
};
|
|
20100
20470
|
/**
|
|
20101
20471
|
* Parses the trips.txt file from a GTFS feed
|
|
@@ -20134,11 +20504,12 @@ const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) => __awaiter(void
|
|
|
20134
20504
|
}
|
|
20135
20505
|
return trips;
|
|
20136
20506
|
});
|
|
20137
|
-
const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
|
|
20138
|
-
// TODO somehow works when it's a map
|
|
20507
|
+
const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, transfersMap, tripContinuationsMap, nbStops, activeStops) => {
|
|
20139
20508
|
const stopsAdjacency = new Array(nbStops);
|
|
20140
20509
|
for (let i = 0; i < nbStops; i++) {
|
|
20141
|
-
stopsAdjacency[i] = {
|
|
20510
|
+
stopsAdjacency[i] = {
|
|
20511
|
+
routes: [],
|
|
20512
|
+
};
|
|
20142
20513
|
}
|
|
20143
20514
|
for (let index = 0; index < routes.length; index++) {
|
|
20144
20515
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -20163,12 +20534,50 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
|
|
|
20163
20534
|
const transfer = transfers[i];
|
|
20164
20535
|
if (activeStops.has(stop) || activeStops.has(transfer.destination)) {
|
|
20165
20536
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20166
|
-
stopsAdjacency[stop]
|
|
20537
|
+
const stopAdj = stopsAdjacency[stop];
|
|
20538
|
+
if (!stopAdj.transfers) {
|
|
20539
|
+
stopAdj.transfers = [];
|
|
20540
|
+
}
|
|
20541
|
+
stopAdj.transfers.push(transfer);
|
|
20167
20542
|
activeStops.add(transfer.destination);
|
|
20168
20543
|
activeStops.add(stop);
|
|
20169
20544
|
}
|
|
20170
20545
|
}
|
|
20171
20546
|
}
|
|
20547
|
+
for (const [stop, tripContinuations] of tripContinuationsMap) {
|
|
20548
|
+
for (let i = 0; i < tripContinuations.length; i++) {
|
|
20549
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20550
|
+
const tripContinuation = tripContinuations[i];
|
|
20551
|
+
if (activeStops.has(stop) ||
|
|
20552
|
+
activeStops.has(tripContinuation.hopOnStop)) {
|
|
20553
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20554
|
+
const stopAdj = stopsAdjacency[stop];
|
|
20555
|
+
if (!stopAdj.tripContinuations) {
|
|
20556
|
+
stopAdj.tripContinuations = new Map();
|
|
20557
|
+
}
|
|
20558
|
+
const originTrip = tripsMapping.get(tripContinuation.fromTrip);
|
|
20559
|
+
const destinationTrip = tripsMapping.get(tripContinuation.toTrip);
|
|
20560
|
+
if (destinationTrip === undefined || originTrip === undefined) {
|
|
20561
|
+
continue;
|
|
20562
|
+
}
|
|
20563
|
+
const tripBoarding = {
|
|
20564
|
+
hopOnStop: tripContinuation.hopOnStop,
|
|
20565
|
+
routeId: destinationTrip.routeId,
|
|
20566
|
+
tripIndex: destinationTrip.tripRouteIndex,
|
|
20567
|
+
};
|
|
20568
|
+
const tripId = encode(originTrip.routeId, originTrip.tripRouteIndex);
|
|
20569
|
+
const existingContinuations = stopAdj.tripContinuations.get(tripId);
|
|
20570
|
+
if (existingContinuations) {
|
|
20571
|
+
existingContinuations.push(tripBoarding);
|
|
20572
|
+
}
|
|
20573
|
+
else {
|
|
20574
|
+
stopAdj.tripContinuations.set(tripId, [tripBoarding]);
|
|
20575
|
+
}
|
|
20576
|
+
activeStops.add(tripContinuation.hopOnStop);
|
|
20577
|
+
activeStops.add(stop);
|
|
20578
|
+
}
|
|
20579
|
+
}
|
|
20580
|
+
}
|
|
20172
20581
|
return stopsAdjacency;
|
|
20173
20582
|
};
|
|
20174
20583
|
/**
|
|
@@ -20227,6 +20636,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20227
20636
|
}
|
|
20228
20637
|
routeBuilder.trips.push({
|
|
20229
20638
|
firstDeparture,
|
|
20639
|
+
gtfsTripId: currentTripId,
|
|
20230
20640
|
arrivalTimes: arrivalTimes,
|
|
20231
20641
|
departureTimes: departureTimes,
|
|
20232
20642
|
pickUpTypes: pickUpTypes,
|
|
@@ -20264,6 +20674,9 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20264
20674
|
continue;
|
|
20265
20675
|
}
|
|
20266
20676
|
if (line.pickup_type === '1' && line.drop_off_type === '1') {
|
|
20677
|
+
// Warning: could potentially lead to issues if there is an in-seat transfer
|
|
20678
|
+
// at this stop - it can be not boardable nor alightable but still useful for an in-seat transfer.
|
|
20679
|
+
// This doesn't seem to happen in practice for now so keeping this condition to save memory.
|
|
20267
20680
|
continue;
|
|
20268
20681
|
}
|
|
20269
20682
|
if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
|
|
@@ -20300,11 +20713,19 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20300
20713
|
addTrip(currentTripId);
|
|
20301
20714
|
}
|
|
20302
20715
|
const routesAdjacency = [];
|
|
20716
|
+
const tripsMapping = new Map();
|
|
20303
20717
|
for (const [, routeBuilder] of routeBuilders) {
|
|
20304
|
-
const routeData = finalizeRouteFromBuilder(routeBuilder);
|
|
20305
|
-
|
|
20718
|
+
const [routeData, gtfsTripIds] = finalizeRouteFromBuilder(routeBuilder);
|
|
20719
|
+
const routeId = routesAdjacency.length;
|
|
20720
|
+
routesAdjacency.push(new Route$1(routeId, routeData.stopTimes, routeData.pickUpDropOffTypes, routeData.stops, routeData.serviceRouteId));
|
|
20721
|
+
gtfsTripIds.forEach((tripId, index) => {
|
|
20722
|
+
tripsMapping.set(tripId, {
|
|
20723
|
+
routeId,
|
|
20724
|
+
tripRouteIndex: index,
|
|
20725
|
+
});
|
|
20726
|
+
});
|
|
20306
20727
|
}
|
|
20307
|
-
return { routes: routesAdjacency, serviceRoutesMap };
|
|
20728
|
+
return { routes: routesAdjacency, serviceRoutesMap, tripsMapping };
|
|
20308
20729
|
});
|
|
20309
20730
|
const parsePickupDropOffType = (gtfsType) => {
|
|
20310
20731
|
switch (gtfsType) {
|
|
@@ -20383,24 +20804,27 @@ class GtfsParser {
|
|
|
20383
20804
|
const tripsEnd = performance.now();
|
|
20384
20805
|
log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
|
|
20385
20806
|
let transfers = new Map();
|
|
20807
|
+
let tripContinuations = new Map();
|
|
20386
20808
|
if (entries[TRANSFERS_FILE]) {
|
|
20387
20809
|
log.info(`Parsing ${TRANSFERS_FILE}`);
|
|
20388
20810
|
const transfersStart = performance.now();
|
|
20389
20811
|
const transfersStream = yield zip.stream(TRANSFERS_FILE);
|
|
20390
|
-
transfers = yield parseTransfers(transfersStream, parsedStops);
|
|
20812
|
+
const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = yield parseTransfers(transfersStream, parsedStops);
|
|
20813
|
+
transfers = parsedTransfers;
|
|
20814
|
+
tripContinuations = parsedTripContinuations;
|
|
20391
20815
|
const transfersEnd = performance.now();
|
|
20392
|
-
log.info(`${transfers.size} valid transfers. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
|
|
20816
|
+
log.info(`${transfers.size} valid transfers and ${tripContinuations.size} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
|
|
20393
20817
|
}
|
|
20394
20818
|
log.info(`Parsing ${STOP_TIMES_FILE}`);
|
|
20395
20819
|
const stopTimesStart = performance.now();
|
|
20396
20820
|
const stopTimesStream = yield zip.stream(STOP_TIMES_FILE);
|
|
20397
|
-
const { routes, serviceRoutesMap } = yield parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
|
|
20821
|
+
const { routes, serviceRoutesMap, tripsMapping } = yield parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
|
|
20398
20822
|
const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
|
|
20399
20823
|
const stopTimesEnd = performance.now();
|
|
20400
20824
|
log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
|
|
20401
20825
|
log.info('Building stops adjacency structure');
|
|
20402
20826
|
const stopsAdjacencyStart = performance.now();
|
|
20403
|
-
const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
|
|
20827
|
+
const stopsAdjacency = buildStopsAdjacencyStructure(tripsMapping, serviceRoutes, routes, transfers, tripContinuations, parsedStops.size, activeStopIds);
|
|
20404
20828
|
const stopsAdjacencyEnd = performance.now();
|
|
20405
20829
|
log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
|
|
20406
20830
|
yield zip.close();
|
|
@@ -20481,53 +20905,242 @@ const chGtfsProfile = {
|
|
|
20481
20905
|
|
|
20482
20906
|
class Plotter {
|
|
20483
20907
|
constructor(result) {
|
|
20908
|
+
this.ROUND_COLORS = [
|
|
20909
|
+
'#60a5fa', // Round 1
|
|
20910
|
+
'#ff9800', // Round 2
|
|
20911
|
+
'#14b8a6', // Round 3
|
|
20912
|
+
'#fb7185', // Round 4
|
|
20913
|
+
'#ffdf00', // Round 5
|
|
20914
|
+
'#b600ff', // Round 6
|
|
20915
|
+
'#ee82ee', // Round 7+
|
|
20916
|
+
];
|
|
20484
20917
|
this.result = result;
|
|
20485
20918
|
}
|
|
20486
20919
|
/**
|
|
20487
|
-
*
|
|
20488
|
-
*
|
|
20489
|
-
* @returns A string representing the DOT graph of the path tree.
|
|
20920
|
+
* Gets the color for a round based on the specified palette.
|
|
20490
20921
|
*/
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20922
|
+
getRoundColor(round) {
|
|
20923
|
+
var _a;
|
|
20924
|
+
if (round === 0)
|
|
20925
|
+
return '#888888';
|
|
20926
|
+
const colorIndex = Math.min(round - 1, this.ROUND_COLORS.length - 1);
|
|
20927
|
+
return (_a = this.ROUND_COLORS[colorIndex]) !== null && _a !== void 0 ? _a : '#ee82ee';
|
|
20928
|
+
}
|
|
20929
|
+
/**
|
|
20930
|
+
* Escapes special characters in DOT strings to prevent syntax errors.
|
|
20931
|
+
*/
|
|
20932
|
+
escapeDotString(str) {
|
|
20933
|
+
return str
|
|
20934
|
+
.replace(/\\/g, '\\\\')
|
|
20935
|
+
.replace(/"/g, '\\"')
|
|
20936
|
+
.replace(/\n/g, '\\n')
|
|
20937
|
+
.replace(/\r/g, '\\r')
|
|
20938
|
+
.replace(/\t/g, '\\t');
|
|
20939
|
+
}
|
|
20940
|
+
/**
|
|
20941
|
+
* Determines station type (origin/destination) information.
|
|
20942
|
+
*/
|
|
20943
|
+
getStationInfo(stopId) {
|
|
20944
|
+
var _a, _b;
|
|
20945
|
+
const isOrigin = (_b = (_a = this.result.routingState.graph[0]) === null || _a === void 0 ? void 0 : _a.has(stopId)) !== null && _b !== void 0 ? _b : false;
|
|
20946
|
+
const isDestination = this.result.routingState.destinations.includes(stopId);
|
|
20947
|
+
return { isOrigin, isDestination };
|
|
20948
|
+
}
|
|
20949
|
+
/**
|
|
20950
|
+
* Formats a stop name for display, including platform information.
|
|
20951
|
+
*/
|
|
20952
|
+
formatStopName(stopId) {
|
|
20953
|
+
const stop = this.result.stopsIndex.findStopById(stopId);
|
|
20954
|
+
if (!stop)
|
|
20955
|
+
return `Unknown Stop (${stopId})`;
|
|
20956
|
+
const escapedName = this.escapeDotString(stop.name);
|
|
20957
|
+
const escapedPlatform = stop.platform
|
|
20958
|
+
? this.escapeDotString(stop.platform)
|
|
20959
|
+
: '';
|
|
20960
|
+
return escapedPlatform
|
|
20961
|
+
? `${escapedName}\\nPl. ${escapedPlatform}`
|
|
20962
|
+
: escapedName;
|
|
20963
|
+
}
|
|
20964
|
+
/**
|
|
20965
|
+
* Gets the appropriate fill color for a station based on its type.
|
|
20966
|
+
*/
|
|
20967
|
+
getStationFillColor(isOrigin, isDestination) {
|
|
20968
|
+
if (isOrigin)
|
|
20969
|
+
return '#60a5fa';
|
|
20970
|
+
if (isDestination)
|
|
20971
|
+
return '#ee82ee';
|
|
20972
|
+
return 'white';
|
|
20973
|
+
}
|
|
20974
|
+
/**
|
|
20975
|
+
* Creates a DOT node for a station.
|
|
20976
|
+
*/
|
|
20977
|
+
createStationNode(stopId) {
|
|
20978
|
+
const stop = this.result.stopsIndex.findStopById(stopId);
|
|
20979
|
+
if (!stop)
|
|
20980
|
+
return '';
|
|
20981
|
+
const displayName = this.formatStopName(stopId);
|
|
20982
|
+
const stopIdStr = this.escapeDotString(String(stopId));
|
|
20983
|
+
const nodeId = `s_${stopId}`;
|
|
20984
|
+
const stationInfo = this.getStationInfo(stopId);
|
|
20985
|
+
const fillColor = this.getStationFillColor(stationInfo.isOrigin, stationInfo.isDestination);
|
|
20986
|
+
return ` "${nodeId}" [label="${displayName}\\n${stopIdStr}" shape=box style=filled fillcolor="${fillColor}"];`;
|
|
20987
|
+
}
|
|
20988
|
+
/**
|
|
20989
|
+
* Creates a vehicle edge with route information oval in the middle.
|
|
20990
|
+
*/
|
|
20991
|
+
createVehicleEdge(edge, round) {
|
|
20992
|
+
var _a;
|
|
20993
|
+
const fromNodeId = `s_${edge.from}`;
|
|
20994
|
+
const toNodeId = `s_${edge.to}`;
|
|
20995
|
+
const roundColor = this.getRoundColor(round);
|
|
20996
|
+
const routeOvalId = `e_${edge.from}_${edge.to}_${edge.routeId}_${round}`;
|
|
20997
|
+
const route = this.result.timetable.getRoute(edge.routeId);
|
|
20998
|
+
const serviceRouteInfo = route
|
|
20999
|
+
? this.result.timetable.getServiceRouteInfo(route)
|
|
21000
|
+
: null;
|
|
21001
|
+
const routeName = (_a = serviceRouteInfo === null || serviceRouteInfo === void 0 ? void 0 : serviceRouteInfo.name) !== null && _a !== void 0 ? _a : `Route ${String(edge.routeId)}`;
|
|
21002
|
+
const routeType = (serviceRouteInfo === null || serviceRouteInfo === void 0 ? void 0 : serviceRouteInfo.type) || 'UNKNOWN';
|
|
21003
|
+
const departureTime = route
|
|
21004
|
+
? route.departureFrom(edge.from, edge.tripIndex).toString()
|
|
21005
|
+
: 'N/A';
|
|
21006
|
+
const arrivalTime = edge.arrival.toString();
|
|
21007
|
+
const escapedRouteName = this.escapeDotString(routeName);
|
|
21008
|
+
const escapedRouteType = this.escapeDotString(routeType);
|
|
21009
|
+
const routeInfo = `${edge.routeId}:${edge.tripIndex}`;
|
|
21010
|
+
const ovalLabel = `${escapedRouteType} ${escapedRouteName}\\n${routeInfo}\\n${departureTime} → ${arrivalTime}`;
|
|
21011
|
+
return [
|
|
21012
|
+
` "${routeOvalId}" [label="${ovalLabel}" shape=oval style=filled fillcolor="white" color="${roundColor}"];`,
|
|
21013
|
+
` "${fromNodeId}" -> "${routeOvalId}" [color="${roundColor}"];`,
|
|
21014
|
+
` "${routeOvalId}" -> "${toNodeId}" [color="${roundColor}"];`,
|
|
20497
21015
|
];
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
|
|
20501
|
-
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
|
|
20513
|
-
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20527
|
-
|
|
21016
|
+
}
|
|
21017
|
+
/**
|
|
21018
|
+
* Creates a transfer edge with transfer information oval in the middle.
|
|
21019
|
+
*/
|
|
21020
|
+
createTransferEdge(edge, round) {
|
|
21021
|
+
var _a;
|
|
21022
|
+
const fromNodeId = `s_${edge.from}`;
|
|
21023
|
+
const toNodeId = `s_${edge.to}`;
|
|
21024
|
+
const roundColor = this.getRoundColor(round);
|
|
21025
|
+
const transferOvalId = `e_${edge.from}_${edge.to}_${round}`;
|
|
21026
|
+
const transferTime = ((_a = edge.minTransferTime) === null || _a === void 0 ? void 0 : _a.toString()) || 'N/A';
|
|
21027
|
+
const escapedTransferTime = this.escapeDotString(transferTime);
|
|
21028
|
+
const ovalLabel = `Transfer\\n${escapedTransferTime}`;
|
|
21029
|
+
return [
|
|
21030
|
+
` "${transferOvalId}" [label="${ovalLabel}" shape=oval style="dashed,filled" fillcolor="white" color="${roundColor}"];`,
|
|
21031
|
+
` "${fromNodeId}" -> "${transferOvalId}" [color="${roundColor}" style="dashed"];`,
|
|
21032
|
+
` "${transferOvalId}" -> "${toNodeId}" [color="${roundColor}" style="dashed"];`,
|
|
21033
|
+
];
|
|
21034
|
+
}
|
|
21035
|
+
/**
|
|
21036
|
+
* Creates a continuation edge to visually link trip continuations.
|
|
21037
|
+
*/
|
|
21038
|
+
createContinuationEdge(fromEdge, toEdge, round) {
|
|
21039
|
+
var _a, _b;
|
|
21040
|
+
const fromStationId = `s_${fromEdge.to}`;
|
|
21041
|
+
const toStationId = `s_${toEdge.from}`;
|
|
21042
|
+
const roundColor = this.getRoundColor(round);
|
|
21043
|
+
const continuationOvalId = `continuation_${fromEdge.to}_${toEdge.from}_${round}`;
|
|
21044
|
+
const fromRoute = this.result.timetable.getRoute(fromEdge.routeId);
|
|
21045
|
+
const toRoute = this.result.timetable.getRoute(toEdge.routeId);
|
|
21046
|
+
const fromServiceRouteInfo = fromRoute
|
|
21047
|
+
? this.result.timetable.getServiceRouteInfo(fromRoute)
|
|
21048
|
+
: null;
|
|
21049
|
+
const toServiceRouteInfo = toRoute
|
|
21050
|
+
? this.result.timetable.getServiceRouteInfo(toRoute)
|
|
21051
|
+
: null;
|
|
21052
|
+
const fromRouteName = (_a = fromServiceRouteInfo === null || fromServiceRouteInfo === void 0 ? void 0 : fromServiceRouteInfo.name) !== null && _a !== void 0 ? _a : `Route ${String(fromEdge.routeId)}`;
|
|
21053
|
+
const toRouteName = (_b = toServiceRouteInfo === null || toServiceRouteInfo === void 0 ? void 0 : toServiceRouteInfo.name) !== null && _b !== void 0 ? _b : `Route ${String(toEdge.routeId)}`;
|
|
21054
|
+
const fromRouteType = (fromServiceRouteInfo === null || fromServiceRouteInfo === void 0 ? void 0 : fromServiceRouteInfo.type) || 'UNKNOWN';
|
|
21055
|
+
const toRouteType = (toServiceRouteInfo === null || toServiceRouteInfo === void 0 ? void 0 : toServiceRouteInfo.type) || 'UNKNOWN';
|
|
21056
|
+
const fromArrivalTime = fromEdge.arrival.toString();
|
|
21057
|
+
const toDepartureTime = toRoute
|
|
21058
|
+
? toRoute.departureFrom(toEdge.from, toEdge.tripIndex).toString()
|
|
21059
|
+
: 'N/A';
|
|
21060
|
+
const escapedFromRouteName = this.escapeDotString(fromRouteName);
|
|
21061
|
+
const escapedToRouteName = this.escapeDotString(toRouteName);
|
|
21062
|
+
const escapedFromRouteType = this.escapeDotString(fromRouteType);
|
|
21063
|
+
const escapedToRouteType = this.escapeDotString(toRouteType);
|
|
21064
|
+
const fromRouteInfo = `${fromEdge.routeId}:${fromEdge.tripIndex}`;
|
|
21065
|
+
const toRouteInfo = `${toEdge.routeId}:${toEdge.tripIndex}`;
|
|
21066
|
+
const ovalLabel = `${escapedFromRouteType} ${escapedFromRouteName} (${fromRouteInfo}) ${fromArrivalTime}\\n↓\\n${escapedToRouteType} ${escapedToRouteName} (${toRouteInfo}) ${toDepartureTime}`;
|
|
21067
|
+
return [
|
|
21068
|
+
` "${continuationOvalId}" [label="${ovalLabel}" shape=oval style="filled,bold" fillcolor="#ffffcc" color="${roundColor}" penwidth="2"];`,
|
|
21069
|
+
` "${fromStationId}" -> "${continuationOvalId}" [color="${roundColor}" style="bold" penwidth="3"];`,
|
|
21070
|
+
` "${continuationOvalId}" -> "${toStationId}" [color="${roundColor}" style="bold" penwidth="3"];`,
|
|
21071
|
+
];
|
|
21072
|
+
}
|
|
21073
|
+
/**
|
|
21074
|
+
* Collects all stations and edges for the graph.
|
|
21075
|
+
*/
|
|
21076
|
+
collectGraphData() {
|
|
21077
|
+
const stations = new Set();
|
|
21078
|
+
const edges = [];
|
|
21079
|
+
const continuationEdges = [];
|
|
21080
|
+
const graph = this.result.routingState.graph;
|
|
21081
|
+
// Collect all stops that appear in the graph
|
|
21082
|
+
graph.forEach((roundMap) => {
|
|
21083
|
+
roundMap.forEach((edge, stopId) => {
|
|
21084
|
+
stations.add(stopId);
|
|
21085
|
+
if ('from' in edge && 'to' in edge) {
|
|
21086
|
+
stations.add(edge.from);
|
|
21087
|
+
stations.add(edge.to);
|
|
21088
|
+
}
|
|
21089
|
+
});
|
|
21090
|
+
});
|
|
21091
|
+
// Create edges for each round
|
|
21092
|
+
graph.forEach((roundMap, round) => {
|
|
21093
|
+
if (round === 0) {
|
|
21094
|
+
// Skip round 0 as it contains only origin nodes
|
|
21095
|
+
return;
|
|
21096
|
+
}
|
|
21097
|
+
roundMap.forEach((edge) => {
|
|
21098
|
+
if ('from' in edge && 'to' in edge) {
|
|
21099
|
+
if ('routeId' in edge) {
|
|
21100
|
+
const vehicleEdgeParts = this.createVehicleEdge(edge, round);
|
|
21101
|
+
edges.push(...vehicleEdgeParts);
|
|
21102
|
+
if (edge.continuationOf) {
|
|
21103
|
+
let currentEdge = edge;
|
|
21104
|
+
let previousEdge = edge.continuationOf;
|
|
21105
|
+
while (previousEdge) {
|
|
21106
|
+
const continuationEdgeParts = this.createContinuationEdge(previousEdge, currentEdge, round);
|
|
21107
|
+
continuationEdges.push(...continuationEdgeParts);
|
|
21108
|
+
currentEdge = previousEdge;
|
|
21109
|
+
previousEdge = previousEdge.continuationOf;
|
|
21110
|
+
}
|
|
21111
|
+
}
|
|
21112
|
+
}
|
|
21113
|
+
else {
|
|
21114
|
+
const transferEdgeParts = this.createTransferEdge(edge, round);
|
|
21115
|
+
edges.push(...transferEdgeParts);
|
|
21116
|
+
}
|
|
20528
21117
|
}
|
|
20529
21118
|
});
|
|
20530
21119
|
});
|
|
21120
|
+
edges.push(...continuationEdges);
|
|
21121
|
+
return { stations, edges };
|
|
21122
|
+
}
|
|
21123
|
+
/**
|
|
21124
|
+
* Plots the routing graph as a DOT graph for visualization.
|
|
21125
|
+
*/
|
|
21126
|
+
plotDotGraph() {
|
|
21127
|
+
const { stations, edges } = this.collectGraphData();
|
|
21128
|
+
const dotParts = [
|
|
21129
|
+
'digraph RoutingGraph {',
|
|
21130
|
+
' graph [overlap=false, splines=true, rankdir=TB, bgcolor=white, nodesep=0.8, ranksep=1.2, concentrate=true];',
|
|
21131
|
+
' node [fontname="Arial" margin=0.1];',
|
|
21132
|
+
' edge [fontname="Arial" fontsize=10];',
|
|
21133
|
+
'',
|
|
21134
|
+
' // Stations',
|
|
21135
|
+
];
|
|
21136
|
+
stations.forEach((stopId) => {
|
|
21137
|
+
const stationNode = this.createStationNode(stopId);
|
|
21138
|
+
if (stationNode) {
|
|
21139
|
+
dotParts.push(stationNode);
|
|
21140
|
+
}
|
|
21141
|
+
});
|
|
21142
|
+
dotParts.push('', ' // Edges');
|
|
21143
|
+
dotParts.push(...edges);
|
|
20531
21144
|
dotParts.push('}');
|
|
20532
21145
|
return dotParts.join('\n');
|
|
20533
21146
|
}
|
|
@@ -20726,11 +21339,11 @@ class Route {
|
|
|
20726
21339
|
}
|
|
20727
21340
|
|
|
20728
21341
|
class Result {
|
|
20729
|
-
constructor(query,
|
|
21342
|
+
constructor(query, routingState, stopsIndex, timetable) {
|
|
20730
21343
|
this.query = query;
|
|
20731
|
-
this.
|
|
20732
|
-
this.earliestArrivalsPerRound = earliestArrivalsPerRound;
|
|
21344
|
+
this.routingState = routingState;
|
|
20733
21345
|
this.stopsIndex = stopsIndex;
|
|
21346
|
+
this.timetable = timetable;
|
|
20734
21347
|
}
|
|
20735
21348
|
/**
|
|
20736
21349
|
* Reconstructs the best route to a stop.
|
|
@@ -20740,17 +21353,18 @@ class Result {
|
|
|
20740
21353
|
* @returns a route to the destination stop if it exists.
|
|
20741
21354
|
*/
|
|
20742
21355
|
bestRoute(to) {
|
|
20743
|
-
var _a
|
|
21356
|
+
var _a;
|
|
20744
21357
|
const destinationList = to instanceof Set
|
|
20745
21358
|
? Array.from(to)
|
|
20746
21359
|
: to
|
|
20747
21360
|
? [to]
|
|
20748
21361
|
: Array.from(this.query.to);
|
|
20749
21362
|
const destinations = destinationList.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
21363
|
+
// find the first reached destination
|
|
20750
21364
|
let fastestDestination = undefined;
|
|
20751
21365
|
let fastestTime = undefined;
|
|
20752
21366
|
for (const destination of destinations) {
|
|
20753
|
-
const arrivalTime = this.earliestArrivals.get(destination.id);
|
|
21367
|
+
const arrivalTime = this.routingState.earliestArrivals.get(destination.id);
|
|
20754
21368
|
if (arrivalTime !== undefined) {
|
|
20755
21369
|
if (fastestTime === undefined ||
|
|
20756
21370
|
arrivalTime.arrival.isBefore(fastestTime.arrival)) {
|
|
@@ -20765,19 +21379,84 @@ class Result {
|
|
|
20765
21379
|
const route = [];
|
|
20766
21380
|
let currentStop = fastestDestination;
|
|
20767
21381
|
let round = fastestTime.legNumber;
|
|
20768
|
-
while (
|
|
20769
|
-
const
|
|
20770
|
-
if (!
|
|
20771
|
-
throw new Error(`No
|
|
21382
|
+
while (round > 0) {
|
|
21383
|
+
const edge = (_a = this.routingState.graph[round]) === null || _a === void 0 ? void 0 : _a.get(currentStop);
|
|
21384
|
+
if (!edge) {
|
|
21385
|
+
throw new Error(`No edge arriving at stop ${currentStop} at round ${round}`);
|
|
21386
|
+
}
|
|
21387
|
+
let leg;
|
|
21388
|
+
if ('routeId' in edge) {
|
|
21389
|
+
let vehicleEdge = edge;
|
|
21390
|
+
// Handle leg reconstruction for in-seat trip continuations
|
|
21391
|
+
const chainedEdges = [vehicleEdge];
|
|
21392
|
+
while ('routeId' in vehicleEdge && vehicleEdge.continuationOf) {
|
|
21393
|
+
chainedEdges.push(vehicleEdge.continuationOf);
|
|
21394
|
+
vehicleEdge = vehicleEdge.continuationOf;
|
|
21395
|
+
}
|
|
21396
|
+
leg = this.buildVehicleLeg(chainedEdges);
|
|
21397
|
+
}
|
|
21398
|
+
else if ('type' in edge) {
|
|
21399
|
+
leg = this.buildTransferLeg(edge);
|
|
20772
21400
|
}
|
|
20773
|
-
|
|
20774
|
-
|
|
20775
|
-
|
|
21401
|
+
else {
|
|
21402
|
+
break;
|
|
21403
|
+
}
|
|
21404
|
+
route.unshift(leg);
|
|
21405
|
+
currentStop = leg.from.id;
|
|
21406
|
+
if ('routeId' in edge) {
|
|
20776
21407
|
round -= 1;
|
|
20777
21408
|
}
|
|
20778
21409
|
}
|
|
20779
21410
|
return new Route(route);
|
|
20780
21411
|
}
|
|
21412
|
+
/**
|
|
21413
|
+
* Builds a vehicle leg from a chain of vehicle edges.
|
|
21414
|
+
*
|
|
21415
|
+
* @param edges Array of vehicle edges representing continuous trips on transit vehicles
|
|
21416
|
+
* @returns A vehicle leg with departure/arrival information and route details
|
|
21417
|
+
* @throws Error if the edges array is empty
|
|
21418
|
+
*/
|
|
21419
|
+
buildVehicleLeg(edges) {
|
|
21420
|
+
if (edges.length === 0) {
|
|
21421
|
+
throw new Error('Cannot build vehicle leg from empty edges');
|
|
21422
|
+
}
|
|
21423
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21424
|
+
const firstEdge = edges[edges.length - 1];
|
|
21425
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21426
|
+
const lastEdge = edges[0];
|
|
21427
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21428
|
+
const firstRoute = this.timetable.getRoute(firstEdge.routeId);
|
|
21429
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21430
|
+
const lastRoute = this.timetable.getRoute(lastEdge.routeId);
|
|
21431
|
+
return {
|
|
21432
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21433
|
+
from: this.stopsIndex.findStopById(firstEdge.from),
|
|
21434
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21435
|
+
to: this.stopsIndex.findStopById(lastEdge.to),
|
|
21436
|
+
// The route info comes from the first boarded route in case on continuous trips
|
|
21437
|
+
route: this.timetable.getServiceRouteInfo(firstRoute),
|
|
21438
|
+
departureTime: firstRoute.departureFrom(firstEdge.from, firstEdge.tripIndex),
|
|
21439
|
+
arrivalTime: lastEdge.arrival,
|
|
21440
|
+
pickUpType: firstRoute.pickUpTypeFrom(firstEdge.from, firstEdge.tripIndex),
|
|
21441
|
+
dropOffType: lastRoute.dropOffTypeAt(lastEdge.to, lastEdge.tripIndex),
|
|
21442
|
+
};
|
|
21443
|
+
}
|
|
21444
|
+
/**
|
|
21445
|
+
* Builds a transfer leg from a transfer edge.
|
|
21446
|
+
*
|
|
21447
|
+
* @param edge Transfer edge representing a walking connection between stops
|
|
21448
|
+
* @returns A transfer leg with from/to stops and transfer details
|
|
21449
|
+
*/
|
|
21450
|
+
buildTransferLeg(edge) {
|
|
21451
|
+
return {
|
|
21452
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21453
|
+
from: this.stopsIndex.findStopById(edge.from),
|
|
21454
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21455
|
+
to: this.stopsIndex.findStopById(edge.to),
|
|
21456
|
+
minTransferTime: edge.minTransferTime,
|
|
21457
|
+
type: edge.type,
|
|
21458
|
+
};
|
|
21459
|
+
}
|
|
20781
21460
|
/**
|
|
20782
21461
|
* Returns the arrival time at any stop reachable in less time / transfers than the destination(s) of the query)
|
|
20783
21462
|
*
|
|
@@ -20786,14 +21465,28 @@ class Result {
|
|
|
20786
21465
|
* @returns The arrival time if the target stop is reachable, otherwise undefined.
|
|
20787
21466
|
*/
|
|
20788
21467
|
arrivalAt(stop, maxTransfers) {
|
|
21468
|
+
var _a;
|
|
20789
21469
|
const equivalentStops = this.stopsIndex.equivalentStops(stop);
|
|
20790
21470
|
let earliestArrival = undefined;
|
|
20791
|
-
const relevantArrivals = maxTransfers !== undefined
|
|
20792
|
-
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20793
|
-
this.earliestArrivalsPerRound[maxTransfers + 1]
|
|
20794
|
-
: this.earliestArrivals;
|
|
20795
21471
|
for (const equivalentStop of equivalentStops) {
|
|
20796
|
-
|
|
21472
|
+
let arrivalTime;
|
|
21473
|
+
if (maxTransfers === undefined) {
|
|
21474
|
+
arrivalTime = this.routingState.earliestArrivals.get(equivalentStop.id);
|
|
21475
|
+
}
|
|
21476
|
+
else {
|
|
21477
|
+
// We have no guarantee that the stop was visited in the last round,
|
|
21478
|
+
// so we need to check all rounds if it's not found in the last one.
|
|
21479
|
+
for (let i = maxTransfers + 1; i >= 0; i--) {
|
|
21480
|
+
const arrivalEdge = (_a = this.routingState.graph[i]) === null || _a === void 0 ? void 0 : _a.get(equivalentStop.id);
|
|
21481
|
+
if (arrivalEdge !== undefined) {
|
|
21482
|
+
arrivalTime = {
|
|
21483
|
+
arrival: arrivalEdge.arrival,
|
|
21484
|
+
legNumber: i,
|
|
21485
|
+
};
|
|
21486
|
+
break;
|
|
21487
|
+
}
|
|
21488
|
+
}
|
|
21489
|
+
}
|
|
20797
21490
|
if (arrivalTime !== undefined) {
|
|
20798
21491
|
if (earliestArrival === undefined ||
|
|
20799
21492
|
arrivalTime.arrival.isBefore(earliestArrival.arrival)) {
|
|
@@ -20807,9 +21500,9 @@ class Result {
|
|
|
20807
21500
|
|
|
20808
21501
|
const UNREACHED = Time.infinity();
|
|
20809
21502
|
/**
|
|
20810
|
-
* A public transportation
|
|
20811
|
-
*
|
|
20812
|
-
*
|
|
21503
|
+
* A public transportation router implementing the RAPTOR algorithm.
|
|
21504
|
+
* For more information on the RAPTOR algorithm,
|
|
21505
|
+
* refer to its detailed explanation in the research paper:
|
|
20813
21506
|
* https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
|
|
20814
21507
|
*/
|
|
20815
21508
|
class Router {
|
|
@@ -20818,25 +21511,227 @@ class Router {
|
|
|
20818
21511
|
this.stopsIndex = stopsIndex;
|
|
20819
21512
|
}
|
|
20820
21513
|
/**
|
|
20821
|
-
*
|
|
20822
|
-
*
|
|
20823
|
-
*
|
|
21514
|
+
* The main Raptor algorithm implementation.
|
|
21515
|
+
*
|
|
21516
|
+
* @param query The query containing the main parameters for the routing.
|
|
21517
|
+
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
21518
|
+
*/
|
|
21519
|
+
route(query) {
|
|
21520
|
+
const routingState = this.initRoutingState(query);
|
|
21521
|
+
const markedStops = new Set();
|
|
21522
|
+
for (const originStop of routingState.graph[0].keys()) {
|
|
21523
|
+
markedStops.add(originStop);
|
|
21524
|
+
}
|
|
21525
|
+
// Initial transfer consideration for origins
|
|
21526
|
+
const newlyMarkedStops = this.considerTransfers(query, 0, markedStops, routingState);
|
|
21527
|
+
for (const newStop of newlyMarkedStops) {
|
|
21528
|
+
markedStops.add(newStop);
|
|
21529
|
+
}
|
|
21530
|
+
for (let round = 1; round <= query.options.maxTransfers + 1; round++) {
|
|
21531
|
+
const edgesAtCurrentRound = new Map();
|
|
21532
|
+
routingState.graph.push(edgesAtCurrentRound);
|
|
21533
|
+
const reachableRoutes = this.timetable.findReachableRoutes(markedStops, query.options.transportModes);
|
|
21534
|
+
markedStops.clear();
|
|
21535
|
+
// for each route that can be reached with at least round - 1 trips
|
|
21536
|
+
for (const [route, hopOnStop] of reachableRoutes) {
|
|
21537
|
+
const newlyMarkedStops = this.scanRoute(route, hopOnStop, round, routingState);
|
|
21538
|
+
for (const newStop of newlyMarkedStops) {
|
|
21539
|
+
markedStops.add(newStop);
|
|
21540
|
+
}
|
|
21541
|
+
}
|
|
21542
|
+
// process in-seat trip continuations
|
|
21543
|
+
let continuations = this.findTripContinuations(markedStops, edgesAtCurrentRound);
|
|
21544
|
+
while (continuations.length > 0) {
|
|
21545
|
+
const stopsFromContinuations = new Set();
|
|
21546
|
+
for (const continuation of continuations) {
|
|
21547
|
+
const route = this.timetable.getRoute(continuation.routeId);
|
|
21548
|
+
const routeScanResults = this.scanRoute(route, continuation.hopOnStop, round, routingState, continuation);
|
|
21549
|
+
for (const newStop of routeScanResults) {
|
|
21550
|
+
stopsFromContinuations.add(newStop);
|
|
21551
|
+
}
|
|
21552
|
+
}
|
|
21553
|
+
for (const newStop of stopsFromContinuations) {
|
|
21554
|
+
markedStops.add(newStop);
|
|
21555
|
+
}
|
|
21556
|
+
continuations = this.findTripContinuations(stopsFromContinuations, edgesAtCurrentRound);
|
|
21557
|
+
}
|
|
21558
|
+
const newlyMarkedStops = this.considerTransfers(query, round, markedStops, routingState);
|
|
21559
|
+
for (const newStop of newlyMarkedStops) {
|
|
21560
|
+
markedStops.add(newStop);
|
|
21561
|
+
}
|
|
21562
|
+
if (markedStops.size === 0)
|
|
21563
|
+
break;
|
|
21564
|
+
}
|
|
21565
|
+
return new Result(query, routingState, this.stopsIndex, this.timetable);
|
|
21566
|
+
}
|
|
21567
|
+
/**
|
|
21568
|
+
* Finds trip continuations for the given marked stops and edges at the current round.
|
|
21569
|
+
* @param markedStops The set of marked stops.
|
|
21570
|
+
* @param edgesAtCurrentRound The map of edges at the current round.
|
|
21571
|
+
* @returns An array of trip continuations.
|
|
21572
|
+
*/
|
|
21573
|
+
findTripContinuations(markedStops, edgesAtCurrentRound) {
|
|
21574
|
+
const continuations = [];
|
|
21575
|
+
for (const stopId of markedStops) {
|
|
21576
|
+
const arrival = edgesAtCurrentRound.get(stopId);
|
|
21577
|
+
if (!arrival || !('routeId' in arrival))
|
|
21578
|
+
continue;
|
|
21579
|
+
const continuousTrips = this.timetable.getContinuousTrips(stopId, arrival.routeId, arrival.tripIndex);
|
|
21580
|
+
for (let i = 0; i < continuousTrips.length; i++) {
|
|
21581
|
+
const trip = continuousTrips[i];
|
|
21582
|
+
continuations.push({
|
|
21583
|
+
routeId: trip.routeId,
|
|
21584
|
+
hopOnStop: trip.hopOnStop,
|
|
21585
|
+
tripIndex: trip.tripIndex,
|
|
21586
|
+
previousEdge: arrival,
|
|
21587
|
+
});
|
|
21588
|
+
}
|
|
21589
|
+
}
|
|
21590
|
+
return continuations;
|
|
21591
|
+
}
|
|
21592
|
+
/**
|
|
21593
|
+
* Initializes the routing state for the RAPTOR algorithm.
|
|
21594
|
+
*
|
|
21595
|
+
* This method sets up the initial data structures needed for route planning,
|
|
21596
|
+
* including origin and destination stops (considering equivalent stops),
|
|
21597
|
+
* earliest arrival times, and marked stops for processing.
|
|
21598
|
+
*
|
|
21599
|
+
* @param query The routing query containing origin, destination, and departure time
|
|
21600
|
+
* @returns The initialized routing state with all necessary data structures
|
|
21601
|
+
*/
|
|
21602
|
+
initRoutingState(query) {
|
|
21603
|
+
const { from, to, departureTime } = query;
|
|
21604
|
+
// Consider children or siblings of the "from" stop as potential origins
|
|
21605
|
+
const origins = this.stopsIndex
|
|
21606
|
+
.equivalentStops(from)
|
|
21607
|
+
.map((origin) => origin.id);
|
|
21608
|
+
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
21609
|
+
const destinations = Array.from(to)
|
|
21610
|
+
.flatMap((destination) => this.stopsIndex.equivalentStops(destination))
|
|
21611
|
+
.map((destination) => destination.id);
|
|
21612
|
+
const earliestArrivals = new Map();
|
|
21613
|
+
const earliestArrivalsWithoutAnyLeg = new Map();
|
|
21614
|
+
const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
|
|
21615
|
+
const initialState = {
|
|
21616
|
+
arrival: departureTime,
|
|
21617
|
+
legNumber: 0,
|
|
21618
|
+
};
|
|
21619
|
+
for (const originStop of origins) {
|
|
21620
|
+
earliestArrivals.set(originStop, initialState);
|
|
21621
|
+
earliestArrivalsWithoutAnyLeg.set(originStop, initialState);
|
|
21622
|
+
}
|
|
21623
|
+
return {
|
|
21624
|
+
destinations,
|
|
21625
|
+
earliestArrivals,
|
|
21626
|
+
graph: earliestArrivalsPerRound,
|
|
21627
|
+
};
|
|
21628
|
+
}
|
|
21629
|
+
/**
|
|
21630
|
+
* Scans a route to find the earliest possible trips (if not provided) and updates arrival times.
|
|
21631
|
+
*
|
|
21632
|
+
* This method implements the core route scanning logic of the RAPTOR algorithm.
|
|
21633
|
+
* It iterates through all stops on a given route starting from the hop-on stop,
|
|
21634
|
+
* maintaining the current best trip and updating arrival times when improvements
|
|
21635
|
+
* are found. The method also handles boarding new trips when earlier departures
|
|
21636
|
+
* are available if no given trip is provided as a parameter.
|
|
21637
|
+
*
|
|
21638
|
+
* @param route The route to scan for possible trips
|
|
21639
|
+
* @param hopOnStop The stop ID where passengers can board the route
|
|
21640
|
+
* @param round The current round number in the RAPTOR algorithm
|
|
21641
|
+
* @param routingState The current routing state containing arrival times and marked stops
|
|
21642
|
+
*/
|
|
21643
|
+
scanRoute(route, hopOnStop, round, routingState, tripContinuation) {
|
|
21644
|
+
var _a, _b, _c;
|
|
21645
|
+
const newlyMarkedStops = new Set();
|
|
21646
|
+
let activeTrip = tripContinuation
|
|
21647
|
+
? {
|
|
21648
|
+
routeId: route.id,
|
|
21649
|
+
hopOnStop,
|
|
21650
|
+
tripIndex: tripContinuation.tripIndex,
|
|
21651
|
+
}
|
|
21652
|
+
: undefined;
|
|
21653
|
+
const edgesAtCurrentRound = routingState.graph[round];
|
|
21654
|
+
const edgesAtPreviousRound = routingState.graph[round - 1];
|
|
21655
|
+
const startIndex = route.stopRouteIndex(hopOnStop);
|
|
21656
|
+
// Compute target pruning criteria only once per route
|
|
21657
|
+
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState.earliestArrivals, routingState.destinations);
|
|
21658
|
+
for (let j = startIndex; j < route.getNbStops(); j++) {
|
|
21659
|
+
const currentStop = route.stops[j];
|
|
21660
|
+
// If we're currently on a trip,
|
|
21661
|
+
// check if arrival at the stop improves the earliest arrival time
|
|
21662
|
+
if (activeTrip !== undefined) {
|
|
21663
|
+
const arrivalTime = route.arrivalAt(currentStop, activeTrip.tripIndex);
|
|
21664
|
+
const dropOffType = route.dropOffTypeAt(currentStop, activeTrip.tripIndex);
|
|
21665
|
+
const earliestArrivalAtCurrentStop = (_b = (_a = routingState.earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
21666
|
+
if (dropOffType !== 'NOT_AVAILABLE' &&
|
|
21667
|
+
arrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
|
|
21668
|
+
arrivalTime.isBefore(earliestArrivalAtAnyDestination)) {
|
|
21669
|
+
const edge = {
|
|
21670
|
+
arrival: arrivalTime,
|
|
21671
|
+
routeId: route.id,
|
|
21672
|
+
tripIndex: activeTrip.tripIndex,
|
|
21673
|
+
from: activeTrip.hopOnStop,
|
|
21674
|
+
to: currentStop,
|
|
21675
|
+
};
|
|
21676
|
+
if (tripContinuation) {
|
|
21677
|
+
// In case of continuous trip, we set a pointer to the previous edge
|
|
21678
|
+
edge.continuationOf = tripContinuation.previousEdge;
|
|
21679
|
+
}
|
|
21680
|
+
edgesAtCurrentRound.set(currentStop, edge);
|
|
21681
|
+
routingState.earliestArrivals.set(currentStop, {
|
|
21682
|
+
arrival: arrivalTime,
|
|
21683
|
+
legNumber: round,
|
|
21684
|
+
});
|
|
21685
|
+
newlyMarkedStops.add(currentStop);
|
|
21686
|
+
}
|
|
21687
|
+
}
|
|
21688
|
+
if (tripContinuation) {
|
|
21689
|
+
// If it's a trip continuation, no need to check for earlier trips
|
|
21690
|
+
continue;
|
|
21691
|
+
}
|
|
21692
|
+
// check if we can board an earlier trip at the current stop
|
|
21693
|
+
// if there was no current trip, find the first one reachable
|
|
21694
|
+
const earliestArrivalOnPreviousRound = (_c = edgesAtPreviousRound.get(currentStop)) === null || _c === void 0 ? void 0 : _c.arrival;
|
|
21695
|
+
// TODO if the last edge is not a transfer, and if there is no trip continuation of type 1 (guaranteed)
|
|
21696
|
+
// Add the minTransferTime to make sure there's at least 2 minutes to transfer.
|
|
21697
|
+
// If platforms are collapsed, make sure to apply the station level transfer time
|
|
21698
|
+
// (or later at route reconstruction time)
|
|
21699
|
+
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
21700
|
+
(activeTrip === undefined ||
|
|
21701
|
+
earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStop, activeTrip.tripIndex)) ||
|
|
21702
|
+
earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStop, activeTrip.tripIndex)))) {
|
|
21703
|
+
const earliestTrip = route.findEarliestTrip(currentStop, earliestArrivalOnPreviousRound, activeTrip === null || activeTrip === void 0 ? void 0 : activeTrip.tripIndex);
|
|
21704
|
+
if (earliestTrip !== undefined) {
|
|
21705
|
+
activeTrip = {
|
|
21706
|
+
routeId: route.id,
|
|
21707
|
+
tripIndex: earliestTrip,
|
|
21708
|
+
hopOnStop: currentStop,
|
|
21709
|
+
};
|
|
21710
|
+
}
|
|
21711
|
+
}
|
|
21712
|
+
}
|
|
21713
|
+
return newlyMarkedStops;
|
|
21714
|
+
}
|
|
21715
|
+
/**
|
|
21716
|
+
* Processes all currently marked stops to find available transfers
|
|
21717
|
+
* and determines if using these transfers would result in earlier arrival times
|
|
21718
|
+
* at destination stops. It handles different transfer types including in-seat
|
|
21719
|
+
* transfers and walking transfers with appropriate minimum transfer times.
|
|
21720
|
+
*
|
|
21721
|
+
* @param query The routing query containing transfer options and constraints
|
|
21722
|
+
* @param round The current round number in the RAPTOR algorithm
|
|
21723
|
+
* @param routingState The current routing state containing arrival times and marked stops
|
|
20824
21724
|
*/
|
|
20825
|
-
considerTransfers(query,
|
|
21725
|
+
considerTransfers(query, round, markedStops, routingState) {
|
|
20826
21726
|
var _a, _b;
|
|
20827
21727
|
const { options } = query;
|
|
21728
|
+
const arrivalsAtCurrentRound = routingState.graph[round];
|
|
20828
21729
|
const newlyMarkedStops = new Set();
|
|
20829
|
-
const
|
|
20830
|
-
for (let i = 0; i < markedStopsArray.length; i++) {
|
|
20831
|
-
const stop = markedStopsArray[i];
|
|
21730
|
+
for (const stop of markedStops) {
|
|
20832
21731
|
const currentArrival = arrivalsAtCurrentRound.get(stop);
|
|
20833
|
-
if (!currentArrival)
|
|
20834
|
-
continue;
|
|
20835
21732
|
// Skip transfers if the last leg was also a transfer
|
|
20836
|
-
|
|
20837
|
-
if (previousLeg && !('route' in previousLeg)) {
|
|
21733
|
+
if (!currentArrival || 'type' in currentArrival)
|
|
20838
21734
|
continue;
|
|
20839
|
-
}
|
|
20840
21735
|
const transfers = this.timetable.getTransfers(stop);
|
|
20841
21736
|
for (let j = 0; j < transfers.length; j++) {
|
|
20842
21737
|
const transfer = transfers[j];
|
|
@@ -20845,6 +21740,7 @@ class Router {
|
|
|
20845
21740
|
transferTime = transfer.minTransferTime;
|
|
20846
21741
|
}
|
|
20847
21742
|
else if (transfer.type === 'IN_SEAT') {
|
|
21743
|
+
// TODO not needed anymore now that trip continuations are handled separately
|
|
20848
21744
|
transferTime = Duration.zero();
|
|
20849
21745
|
}
|
|
20850
21746
|
else {
|
|
@@ -20853,32 +21749,22 @@ class Router {
|
|
|
20853
21749
|
const arrivalAfterTransfer = currentArrival.arrival.plus(transferTime);
|
|
20854
21750
|
const originalArrival = (_b = (_a = arrivalsAtCurrentRound.get(transfer.destination)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20855
21751
|
if (arrivalAfterTransfer.isBefore(originalArrival)) {
|
|
20856
|
-
const origin = currentArrival.origin;
|
|
20857
21752
|
arrivalsAtCurrentRound.set(transfer.destination, {
|
|
20858
21753
|
arrival: arrivalAfterTransfer,
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
|
|
20863
|
-
to: this.stopsIndex.findStopById(transfer.destination),
|
|
20864
|
-
minTransferTime: transfer.minTransferTime,
|
|
20865
|
-
type: transfer.type,
|
|
20866
|
-
},
|
|
21754
|
+
from: stop,
|
|
21755
|
+
to: transfer.destination,
|
|
21756
|
+
minTransferTime: transfer.minTransferTime,
|
|
21757
|
+
type: transfer.type,
|
|
20867
21758
|
});
|
|
20868
|
-
earliestArrivals.set(transfer.destination, {
|
|
21759
|
+
routingState.earliestArrivals.set(transfer.destination, {
|
|
20869
21760
|
arrival: arrivalAfterTransfer,
|
|
20870
21761
|
legNumber: round,
|
|
20871
|
-
origin: origin,
|
|
20872
21762
|
});
|
|
20873
21763
|
newlyMarkedStops.add(transfer.destination);
|
|
20874
21764
|
}
|
|
20875
21765
|
}
|
|
20876
21766
|
}
|
|
20877
|
-
|
|
20878
|
-
for (let i = 0; i < newlyMarkedStopsArray.length; i++) {
|
|
20879
|
-
const newStop = newlyMarkedStopsArray[i];
|
|
20880
|
-
markedStops.add(newStop);
|
|
20881
|
-
}
|
|
21767
|
+
return newlyMarkedStops;
|
|
20882
21768
|
}
|
|
20883
21769
|
/**
|
|
20884
21770
|
* Finds the earliest arrival time at any stop from a given set of destinations.
|
|
@@ -20892,116 +21778,11 @@ class Router {
|
|
|
20892
21778
|
let earliestArrivalAtAnyDestination = UNREACHED;
|
|
20893
21779
|
for (let i = 0; i < destinations.length; i++) {
|
|
20894
21780
|
const destination = destinations[i];
|
|
20895
|
-
const arrival = (_b = (_a = earliestArrivals.get(destination
|
|
21781
|
+
const arrival = (_b = (_a = earliestArrivals.get(destination)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20896
21782
|
earliestArrivalAtAnyDestination = Time.min(earliestArrivalAtAnyDestination, arrival);
|
|
20897
21783
|
}
|
|
20898
21784
|
return earliestArrivalAtAnyDestination;
|
|
20899
21785
|
}
|
|
20900
|
-
/**
|
|
20901
|
-
* The main Raptor algorithm implementation.
|
|
20902
|
-
*
|
|
20903
|
-
* @param query The query containing the main parameters for the routing.
|
|
20904
|
-
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
20905
|
-
*/
|
|
20906
|
-
route(query) {
|
|
20907
|
-
var _a, _b, _c, _d, _e;
|
|
20908
|
-
const { from, to, departureTime, options } = query;
|
|
20909
|
-
// Consider children or siblings of the "from" stop as potential origins
|
|
20910
|
-
const origins = this.stopsIndex.equivalentStops(from);
|
|
20911
|
-
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
20912
|
-
const destinations = Array.from(to).flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
20913
|
-
const earliestArrivals = new Map();
|
|
20914
|
-
const earliestArrivalsWithoutAnyLeg = new Map();
|
|
20915
|
-
const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
|
|
20916
|
-
// Stops that have been improved at round k-1
|
|
20917
|
-
const markedStops = new Set();
|
|
20918
|
-
for (let i = 0; i < origins.length; i++) {
|
|
20919
|
-
const originStop = origins[i];
|
|
20920
|
-
markedStops.add(originStop.id);
|
|
20921
|
-
earliestArrivals.set(originStop.id, {
|
|
20922
|
-
arrival: departureTime,
|
|
20923
|
-
legNumber: 0,
|
|
20924
|
-
origin: originStop.id,
|
|
20925
|
-
});
|
|
20926
|
-
earliestArrivalsWithoutAnyLeg.set(originStop.id, {
|
|
20927
|
-
arrival: departureTime,
|
|
20928
|
-
legNumber: 0,
|
|
20929
|
-
origin: originStop.id,
|
|
20930
|
-
});
|
|
20931
|
-
}
|
|
20932
|
-
// on the first round we need to first consider transfers to discover all possible route origins
|
|
20933
|
-
this.considerTransfers(query, markedStops, earliestArrivalsWithoutAnyLeg, earliestArrivals, 0);
|
|
20934
|
-
for (let round = 1; round <= options.maxTransfers + 1; round++) {
|
|
20935
|
-
const arrivalsAtCurrentRound = new Map();
|
|
20936
|
-
earliestArrivalsPerRound.push(arrivalsAtCurrentRound);
|
|
20937
|
-
const arrivalsAtPreviousRound = earliestArrivalsPerRound[round - 1];
|
|
20938
|
-
// Routes that contain at least one stop reached with at least round - 1 legs
|
|
20939
|
-
// together with corresponding hop on stop index (earliest marked stop)
|
|
20940
|
-
const reachableRoutes = this.timetable.findReachableRoutes(markedStops, options.transportModes);
|
|
20941
|
-
markedStops.clear();
|
|
20942
|
-
// for each route that can be reached with at least round - 1 trips
|
|
20943
|
-
const reachableRoutesArray = Array.from(reachableRoutes.entries());
|
|
20944
|
-
for (let i = 0; i < reachableRoutesArray.length; i++) {
|
|
20945
|
-
const [route, hopOnStop] = reachableRoutesArray[i];
|
|
20946
|
-
let currentTrip = undefined;
|
|
20947
|
-
const startIndex = route.stopIndex(hopOnStop);
|
|
20948
|
-
for (let j = startIndex; j < route.getNbStops(); j++) {
|
|
20949
|
-
const currentStop = route.stops[j];
|
|
20950
|
-
// If we're currently on a trip,
|
|
20951
|
-
// check if arrival at the stop improves the earliest arrival time
|
|
20952
|
-
if (currentTrip !== undefined) {
|
|
20953
|
-
const currentArrivalTime = route.arrivalAt(currentStop, currentTrip.tripIndex);
|
|
20954
|
-
const currentDropOffType = route.dropOffTypeAt(currentStop, currentTrip.tripIndex);
|
|
20955
|
-
const earliestArrivalAtCurrentStop = (_b = (_a = earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20956
|
-
if (currentDropOffType !== 'NOT_AVAILABLE' &&
|
|
20957
|
-
currentArrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
|
|
20958
|
-
currentArrivalTime.isBefore(this.earliestArrivalAtAnyStop(earliestArrivals, destinations))) {
|
|
20959
|
-
const bestHopOnDepartureTime = route.departureFrom(currentTrip.bestHopOnStop, currentTrip.tripIndex);
|
|
20960
|
-
arrivalsAtCurrentRound.set(currentStop, {
|
|
20961
|
-
arrival: currentArrivalTime,
|
|
20962
|
-
legNumber: round,
|
|
20963
|
-
origin: currentTrip.origin,
|
|
20964
|
-
leg: {
|
|
20965
|
-
from: this.stopsIndex.findStopById(currentTrip.bestHopOnStop),
|
|
20966
|
-
to: this.stopsIndex.findStopById(currentStop),
|
|
20967
|
-
departureTime: bestHopOnDepartureTime,
|
|
20968
|
-
arrivalTime: currentArrivalTime,
|
|
20969
|
-
route: this.timetable.getServiceRouteInfo(route),
|
|
20970
|
-
},
|
|
20971
|
-
});
|
|
20972
|
-
earliestArrivals.set(currentStop, {
|
|
20973
|
-
arrival: currentArrivalTime,
|
|
20974
|
-
legNumber: round,
|
|
20975
|
-
origin: currentTrip.origin,
|
|
20976
|
-
});
|
|
20977
|
-
markedStops.add(currentStop);
|
|
20978
|
-
}
|
|
20979
|
-
}
|
|
20980
|
-
// check if we can board an earlier trip at the current stop
|
|
20981
|
-
// if there was no current trip, find the first one reachable
|
|
20982
|
-
const earliestArrivalOnPreviousRound = (_c = arrivalsAtPreviousRound.get(currentStop)) === null || _c === void 0 ? void 0 : _c.arrival;
|
|
20983
|
-
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
20984
|
-
(currentTrip === undefined ||
|
|
20985
|
-
earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStop, currentTrip.tripIndex)) ||
|
|
20986
|
-
earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStop, currentTrip.tripIndex)))) {
|
|
20987
|
-
const earliestTrip = route.findEarliestTrip(currentStop, earliestArrivalOnPreviousRound, currentTrip === null || currentTrip === void 0 ? void 0 : currentTrip.tripIndex);
|
|
20988
|
-
if (earliestTrip !== undefined) {
|
|
20989
|
-
currentTrip = {
|
|
20990
|
-
tripIndex: earliestTrip,
|
|
20991
|
-
// we need to keep track of the best hop-on stop to reconstruct the route at the end
|
|
20992
|
-
bestHopOnStop: currentStop,
|
|
20993
|
-
origin: (_e = (_d = arrivalsAtPreviousRound.get(currentStop)) === null || _d === void 0 ? void 0 : _d.origin) !== null && _e !== void 0 ? _e : currentStop,
|
|
20994
|
-
};
|
|
20995
|
-
}
|
|
20996
|
-
}
|
|
20997
|
-
}
|
|
20998
|
-
}
|
|
20999
|
-
this.considerTransfers(query, markedStops, arrivalsAtCurrentRound, earliestArrivals, round);
|
|
21000
|
-
if (markedStops.size === 0)
|
|
21001
|
-
break;
|
|
21002
|
-
}
|
|
21003
|
-
return new Result(query, earliestArrivals, earliestArrivalsPerRound, this.stopsIndex);
|
|
21004
|
-
}
|
|
21005
21786
|
}
|
|
21006
21787
|
|
|
21007
21788
|
/**
|
|
@@ -21138,7 +21919,6 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21138
21919
|
replServer.defineCommand('route', {
|
|
21139
21920
|
help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
|
|
21140
21921
|
action(routeQuery) {
|
|
21141
|
-
var _a;
|
|
21142
21922
|
this.clearBufferedCommand();
|
|
21143
21923
|
const parts = routeQuery.split(' ').filter(Boolean);
|
|
21144
21924
|
const withTransfersIndex = parts.indexOf('with');
|
|
@@ -21194,12 +21974,13 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21194
21974
|
console.log(`Destination not reachable`);
|
|
21195
21975
|
}
|
|
21196
21976
|
else {
|
|
21197
|
-
console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${
|
|
21977
|
+
console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${fromStop.name}.`);
|
|
21198
21978
|
}
|
|
21199
21979
|
const bestRoute = result.bestRoute(toStop.sourceStopId);
|
|
21200
21980
|
if (bestRoute) {
|
|
21201
21981
|
console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
|
|
21202
21982
|
console.log(bestRoute.toString());
|
|
21983
|
+
console.log(bestRoute.asJson());
|
|
21203
21984
|
}
|
|
21204
21985
|
else {
|
|
21205
21986
|
console.log('No route found');
|
|
@@ -21275,6 +22056,203 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21275
22056
|
this.displayPrompt();
|
|
21276
22057
|
},
|
|
21277
22058
|
});
|
|
22059
|
+
const formatPickupDropoffType = (type) => {
|
|
22060
|
+
switch (type) {
|
|
22061
|
+
case 'REGULAR':
|
|
22062
|
+
return 'R';
|
|
22063
|
+
case 'NOT_AVAILABLE':
|
|
22064
|
+
return 'N';
|
|
22065
|
+
case 'MUST_PHONE_AGENCY':
|
|
22066
|
+
return 'A';
|
|
22067
|
+
case 'MUST_COORDINATE_WITH_DRIVER':
|
|
22068
|
+
return 'D';
|
|
22069
|
+
default:
|
|
22070
|
+
return '?';
|
|
22071
|
+
}
|
|
22072
|
+
};
|
|
22073
|
+
replServer.defineCommand('inspect', {
|
|
22074
|
+
help: 'Inspect a route or stop using .inspect route <routeId> or .inspect stop <stopId>',
|
|
22075
|
+
action(inspectQuery) {
|
|
22076
|
+
this.clearBufferedCommand();
|
|
22077
|
+
const parts = inspectQuery.trim().split(' ');
|
|
22078
|
+
if (parts.length !== 2) {
|
|
22079
|
+
console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
|
|
22080
|
+
this.displayPrompt();
|
|
22081
|
+
return;
|
|
22082
|
+
}
|
|
22083
|
+
const [type, idStr] = parts;
|
|
22084
|
+
if (type !== 'route' && type !== 'stop') {
|
|
22085
|
+
console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
|
|
22086
|
+
this.displayPrompt();
|
|
22087
|
+
return;
|
|
22088
|
+
}
|
|
22089
|
+
const inspectRoute = (routeIdStr) => {
|
|
22090
|
+
var _a, _b, _c;
|
|
22091
|
+
const routeId = parseInt(routeIdStr.trim());
|
|
22092
|
+
if (isNaN(routeId)) {
|
|
22093
|
+
console.log('Usage: .inspect route <routeId>');
|
|
22094
|
+
return;
|
|
22095
|
+
}
|
|
22096
|
+
const route = timetable.getRoute(routeId);
|
|
22097
|
+
if (!route) {
|
|
22098
|
+
console.log(`Route ${routeId} not found`);
|
|
22099
|
+
return;
|
|
22100
|
+
}
|
|
22101
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22102
|
+
const routeName = serviceRouteInfo.name;
|
|
22103
|
+
const routeType = serviceRouteInfo.type;
|
|
22104
|
+
console.log(`\n=== Route ${routeId} ===`);
|
|
22105
|
+
console.log(`Service Route: ${routeName}`);
|
|
22106
|
+
console.log(`Type: ${routeType}`);
|
|
22107
|
+
console.log(`Number of stops: ${route.getNbStops()}`);
|
|
22108
|
+
console.log(`Number of trips: ${route.getNbTrips()}`);
|
|
22109
|
+
console.log('\n--- Stops ---');
|
|
22110
|
+
for (let i = 0; i < route.stops.length; i++) {
|
|
22111
|
+
const stopId = route.stopId(i);
|
|
22112
|
+
const stop = stopsIndex.findStopById(stopId);
|
|
22113
|
+
const platform = (stop === null || stop === void 0 ? void 0 : stop.platform) ? ` (Pl. ${stop.platform})` : '';
|
|
22114
|
+
console.log(`${i + 1}. ${(_a = stop === null || stop === void 0 ? void 0 : stop.name) !== null && _a !== void 0 ? _a : 'Unknown'}${platform} (${stopId}, ${(_b = stop === null || stop === void 0 ? void 0 : stop.sourceStopId) !== null && _b !== void 0 ? _b : 'N/A'})`);
|
|
22115
|
+
}
|
|
22116
|
+
console.log('\n--- Trips ---');
|
|
22117
|
+
for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
|
|
22118
|
+
console.log(`\nTrip ${tripIndex}:`);
|
|
22119
|
+
for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
|
|
22120
|
+
const stopId = route.stopId(stopIndex);
|
|
22121
|
+
const stop = stopsIndex.findStopById(stopId);
|
|
22122
|
+
const departure = route.departureFrom(stopId, tripIndex);
|
|
22123
|
+
const arrival = route.arrivalAt(stopId, tripIndex);
|
|
22124
|
+
const pickupType = route.pickUpTypeFrom(stopId, tripIndex);
|
|
22125
|
+
const dropOffType = route.dropOffTypeAt(stopId, tripIndex);
|
|
22126
|
+
const pickupStr = formatPickupDropoffType(pickupType);
|
|
22127
|
+
const dropOffStr = formatPickupDropoffType(dropOffType);
|
|
22128
|
+
console.log(` ${stopIndex + 1}. ${(_c = stop === null || stop === void 0 ? void 0 : stop.name) !== null && _c !== void 0 ? _c : 'Unknown'}: arr ${arrival.toString()} (${pickupStr}) → dep ${departure.toString()} (${dropOffStr})`);
|
|
22129
|
+
}
|
|
22130
|
+
}
|
|
22131
|
+
console.log();
|
|
22132
|
+
};
|
|
22133
|
+
const inspectStop = (stopIdStr) => {
|
|
22134
|
+
var _a, _b, _c;
|
|
22135
|
+
let stop;
|
|
22136
|
+
const stopBySourceId = stopsIndex.findStopBySourceStopId(stopIdStr);
|
|
22137
|
+
if (stopBySourceId !== undefined) {
|
|
22138
|
+
stop = stopBySourceId;
|
|
22139
|
+
}
|
|
22140
|
+
else if (!isNaN(Number(stopIdStr))) {
|
|
22141
|
+
const stopById = stopsIndex.findStopById(Number(stopIdStr));
|
|
22142
|
+
if (stopById !== undefined) {
|
|
22143
|
+
stop = stopById;
|
|
22144
|
+
}
|
|
22145
|
+
}
|
|
22146
|
+
else {
|
|
22147
|
+
const stops = stopsIndex.findStopsByName(stopIdStr);
|
|
22148
|
+
if (stops.length > 0) {
|
|
22149
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22150
|
+
stop = stops[0];
|
|
22151
|
+
}
|
|
22152
|
+
}
|
|
22153
|
+
if (!stop) {
|
|
22154
|
+
console.log(`Stop not found: ${stopIdStr}`);
|
|
22155
|
+
return;
|
|
22156
|
+
}
|
|
22157
|
+
console.log(`\n=== Stop ${stop.id} ===`);
|
|
22158
|
+
console.log(`Name: ${stop.name}`);
|
|
22159
|
+
if (stop.platform) {
|
|
22160
|
+
console.log(`Platform: ${stop.platform}`);
|
|
22161
|
+
}
|
|
22162
|
+
console.log(`Source ID: ${stop.sourceStopId}`);
|
|
22163
|
+
const routes = timetable.routesPassingThrough(stop.id);
|
|
22164
|
+
console.log(`Number of routes: ${routes.length}`);
|
|
22165
|
+
const equivalentStops = stopsIndex
|
|
22166
|
+
.equivalentStops(stop.sourceStopId)
|
|
22167
|
+
.filter((equivStop) => equivStop.id !== stop.id);
|
|
22168
|
+
console.log(`Number of equivalent stops: ${equivalentStops.length}`);
|
|
22169
|
+
if (equivalentStops.length > 0) {
|
|
22170
|
+
console.log('\n--- Equivalent Stops ---');
|
|
22171
|
+
equivalentStops.forEach((equivStop, index) => {
|
|
22172
|
+
const platform = equivStop.platform
|
|
22173
|
+
? ` (Pl. ${equivStop.platform})`
|
|
22174
|
+
: '';
|
|
22175
|
+
console.log(`${index + 1}. ${equivStop.name}${platform} (${equivStop.id}, ${equivStop.sourceStopId})`);
|
|
22176
|
+
});
|
|
22177
|
+
}
|
|
22178
|
+
if (routes.length > 0) {
|
|
22179
|
+
console.log('\n--- Routes ---');
|
|
22180
|
+
routes.forEach((route, index) => {
|
|
22181
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22182
|
+
console.log(`${index + 1}. Route ${route.id}: ${serviceRouteInfo.name} (${serviceRouteInfo.type})`);
|
|
22183
|
+
});
|
|
22184
|
+
}
|
|
22185
|
+
const transfers = timetable.getTransfers(stop.id);
|
|
22186
|
+
console.log(`Number of transfers: ${transfers.length}`);
|
|
22187
|
+
if (transfers.length > 0) {
|
|
22188
|
+
console.log('\n--- Transfers ---');
|
|
22189
|
+
transfers.forEach((transfer, index) => {
|
|
22190
|
+
var _a, _b;
|
|
22191
|
+
const destStop = stopsIndex.findStopById(transfer.destination);
|
|
22192
|
+
const platform = (destStop === null || destStop === void 0 ? void 0 : destStop.platform)
|
|
22193
|
+
? ` (Pl. ${destStop.platform})`
|
|
22194
|
+
: '';
|
|
22195
|
+
const minTime = transfer.minTransferTime
|
|
22196
|
+
? ` (min: ${Math.floor(transfer.minTransferTime.toSeconds() / 60)}min)`
|
|
22197
|
+
: '';
|
|
22198
|
+
console.log(`${index + 1}. ${transfer.type} to ${(_a = destStop === null || destStop === void 0 ? void 0 : destStop.name) !== null && _a !== void 0 ? _a : 'Unknown'}${platform} (${transfer.destination}, ${(_b = destStop === null || destStop === void 0 ? void 0 : destStop.sourceStopId) !== null && _b !== void 0 ? _b : 'N/A'})${minTime}`);
|
|
22199
|
+
});
|
|
22200
|
+
}
|
|
22201
|
+
let totalContinuations = 0;
|
|
22202
|
+
const continuationsByTrip = new Map();
|
|
22203
|
+
routes.forEach((route) => {
|
|
22204
|
+
for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
|
|
22205
|
+
const continuations = timetable.getContinuousTrips(stop.id, route.id, tripIndex);
|
|
22206
|
+
if (continuations.length > 0) {
|
|
22207
|
+
totalContinuations += continuations.length;
|
|
22208
|
+
const tripKey = `${route.id}-${tripIndex}`;
|
|
22209
|
+
continuationsByTrip.set(tripKey, {
|
|
22210
|
+
route,
|
|
22211
|
+
tripIndex,
|
|
22212
|
+
continuations,
|
|
22213
|
+
});
|
|
22214
|
+
}
|
|
22215
|
+
}
|
|
22216
|
+
});
|
|
22217
|
+
console.log(`Number of trip continuations: ${totalContinuations}`);
|
|
22218
|
+
if (totalContinuations > 0) {
|
|
22219
|
+
console.log('\n--- Trip Continuations ---');
|
|
22220
|
+
let continuationIndex = 1;
|
|
22221
|
+
for (const [, value] of continuationsByTrip) {
|
|
22222
|
+
const { route, tripIndex, continuations } = value;
|
|
22223
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22224
|
+
for (const continuation of continuations) {
|
|
22225
|
+
const destStop = stopsIndex.findStopById(continuation.hopOnStop);
|
|
22226
|
+
const destPlatform = (destStop === null || destStop === void 0 ? void 0 : destStop.platform)
|
|
22227
|
+
? ` (Pl. ${destStop.platform})`
|
|
22228
|
+
: '';
|
|
22229
|
+
const destRoute = timetable.getRoute(continuation.routeId);
|
|
22230
|
+
const destServiceRouteInfo = destRoute
|
|
22231
|
+
? timetable.getServiceRouteInfo(destRoute)
|
|
22232
|
+
: null;
|
|
22233
|
+
const originTime = route.departureFrom(stop.id, tripIndex);
|
|
22234
|
+
const continuationTime = destRoute === null || destRoute === void 0 ? void 0 : destRoute.departureFrom(continuation.hopOnStop, continuation.tripIndex);
|
|
22235
|
+
const continuationTimeStr = continuationTime
|
|
22236
|
+
? ` at ${continuationTime.toString()}`
|
|
22237
|
+
: '';
|
|
22238
|
+
console.log(`${continuationIndex}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
|
|
22239
|
+
`Route ${continuation.routeId} (${(_a = destServiceRouteInfo === null || destServiceRouteInfo === void 0 ? void 0 : destServiceRouteInfo.name) !== null && _a !== void 0 ? _a : 'Unknown'}) Trip ${continuation.tripIndex}${continuationTimeStr} ` +
|
|
22240
|
+
`at ${(_b = destStop === null || destStop === void 0 ? void 0 : destStop.name) !== null && _b !== void 0 ? _b : 'Unknown'}${destPlatform} (${continuation.hopOnStop}, ${(_c = destStop === null || destStop === void 0 ? void 0 : destStop.sourceStopId) !== null && _c !== void 0 ? _c : 'N/A'})`);
|
|
22241
|
+
continuationIndex++;
|
|
22242
|
+
}
|
|
22243
|
+
}
|
|
22244
|
+
}
|
|
22245
|
+
console.log();
|
|
22246
|
+
};
|
|
22247
|
+
if (type === 'route') {
|
|
22248
|
+
inspectRoute(idStr !== null && idStr !== void 0 ? idStr : '');
|
|
22249
|
+
}
|
|
22250
|
+
else {
|
|
22251
|
+
inspectStop(idStr !== null && idStr !== void 0 ? idStr : '');
|
|
22252
|
+
}
|
|
22253
|
+
this.displayPrompt();
|
|
22254
|
+
},
|
|
22255
|
+
});
|
|
21278
22256
|
};
|
|
21279
22257
|
|
|
21280
22258
|
const program = new Command();
|