minotor 7.0.2 → 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 +1243 -267
- 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 +494 -71
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +494 -71
- 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 -211
- 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/timetable.ts +45 -6
- package/src/timetable/tripId.ts +29 -0
package/dist/cli.mjs
CHANGED
|
@@ -16606,41 +16606,191 @@ const Transfer = {
|
|
|
16606
16606
|
return message;
|
|
16607
16607
|
},
|
|
16608
16608
|
};
|
|
16609
|
-
function
|
|
16610
|
-
return {
|
|
16609
|
+
function createBaseTripBoarding() {
|
|
16610
|
+
return { hopOnStop: 0, routeId: 0, tripIndex: 0 };
|
|
16611
16611
|
}
|
|
16612
|
-
const
|
|
16612
|
+
const TripBoarding = {
|
|
16613
16613
|
encode(message, writer = new BinaryWriter()) {
|
|
16614
|
-
|
|
16615
|
-
|
|
16614
|
+
if (message.hopOnStop !== 0) {
|
|
16615
|
+
writer.uint32(8).uint32(message.hopOnStop);
|
|
16616
16616
|
}
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
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);
|
|
16620
16622
|
}
|
|
16621
|
-
writer.join();
|
|
16622
16623
|
return writer;
|
|
16623
16624
|
},
|
|
16624
16625
|
decode(input, length) {
|
|
16625
16626
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16626
16627
|
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16627
|
-
const message =
|
|
16628
|
+
const message = createBaseTripBoarding();
|
|
16628
16629
|
while (reader.pos < end) {
|
|
16629
16630
|
const tag = reader.uint32();
|
|
16630
16631
|
switch (tag >>> 3) {
|
|
16631
16632
|
case 1: {
|
|
16632
|
-
if (tag !==
|
|
16633
|
+
if (tag !== 8) {
|
|
16633
16634
|
break;
|
|
16634
16635
|
}
|
|
16635
|
-
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();
|
|
16636
16718
|
continue;
|
|
16637
16719
|
}
|
|
16638
16720
|
case 2: {
|
|
16639
|
-
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) {
|
|
16640
16790
|
message.routes.push(reader.uint32());
|
|
16641
16791
|
continue;
|
|
16642
16792
|
}
|
|
16643
|
-
if (tag ===
|
|
16793
|
+
if (tag === 10) {
|
|
16644
16794
|
const end2 = reader.uint32() + reader.pos;
|
|
16645
16795
|
while (reader.pos < end2) {
|
|
16646
16796
|
message.routes.push(reader.uint32());
|
|
@@ -16649,6 +16799,20 @@ const StopAdjacency = {
|
|
|
16649
16799
|
}
|
|
16650
16800
|
break;
|
|
16651
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
|
+
}
|
|
16652
16816
|
}
|
|
16653
16817
|
if ((tag & 7) === 4 || tag === 0) {
|
|
16654
16818
|
break;
|
|
@@ -16659,20 +16823,26 @@ const StopAdjacency = {
|
|
|
16659
16823
|
},
|
|
16660
16824
|
fromJSON(object) {
|
|
16661
16825
|
return {
|
|
16826
|
+
routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
|
|
16662
16827
|
transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
|
|
16663
16828
|
? object.transfers.map((e) => Transfer.fromJSON(e))
|
|
16664
16829
|
: [],
|
|
16665
|
-
|
|
16830
|
+
tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
|
|
16831
|
+
? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
|
|
16832
|
+
: [],
|
|
16666
16833
|
};
|
|
16667
16834
|
},
|
|
16668
16835
|
toJSON(message) {
|
|
16669
|
-
var _a, _b;
|
|
16836
|
+
var _a, _b, _c;
|
|
16670
16837
|
const obj = {};
|
|
16671
|
-
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) {
|
|
16672
16842
|
obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
|
|
16673
16843
|
}
|
|
16674
|
-
if ((
|
|
16675
|
-
obj.
|
|
16844
|
+
if ((_c = message.tripContinuations) === null || _c === void 0 ? void 0 : _c.length) {
|
|
16845
|
+
obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
|
|
16676
16846
|
}
|
|
16677
16847
|
return obj;
|
|
16678
16848
|
},
|
|
@@ -16680,10 +16850,11 @@ const StopAdjacency = {
|
|
|
16680
16850
|
return StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
|
|
16681
16851
|
},
|
|
16682
16852
|
fromPartial(object) {
|
|
16683
|
-
var _a, _b;
|
|
16853
|
+
var _a, _b, _c;
|
|
16684
16854
|
const message = createBaseStopAdjacency();
|
|
16685
|
-
message.
|
|
16686
|
-
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))) || [];
|
|
16687
16858
|
return message;
|
|
16688
16859
|
},
|
|
16689
16860
|
};
|
|
@@ -17172,7 +17343,8 @@ const toPickupDropOffType = (numericalType) => {
|
|
|
17172
17343
|
* A route identifies all trips of a given service route sharing the same list of stops.
|
|
17173
17344
|
*/
|
|
17174
17345
|
let Route$1 = class Route {
|
|
17175
|
-
constructor(stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
|
|
17346
|
+
constructor(id, stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
|
|
17347
|
+
this.id = id;
|
|
17176
17348
|
this.stopTimes = stopTimes;
|
|
17177
17349
|
this.pickUpDropOffTypes = pickUpDropOffTypes;
|
|
17178
17350
|
this.stops = stops;
|
|
@@ -17185,6 +17357,78 @@ let Route$1 = class Route {
|
|
|
17185
17357
|
this.stopIndices.set(stops[i], i);
|
|
17186
17358
|
}
|
|
17187
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
|
+
}
|
|
17188
17432
|
/**
|
|
17189
17433
|
* Serializes the Route into binary arrays.
|
|
17190
17434
|
*
|
|
@@ -17208,11 +17452,11 @@ let Route$1 = class Route {
|
|
|
17208
17452
|
isBefore(stopA, stopB) {
|
|
17209
17453
|
const stopAIndex = this.stopIndices.get(stopA);
|
|
17210
17454
|
if (stopAIndex === undefined) {
|
|
17211
|
-
throw new Error(`Stop index ${
|
|
17455
|
+
throw new Error(`Stop index not found for ${stopA} in route ${this.serviceRouteId}`);
|
|
17212
17456
|
}
|
|
17213
17457
|
const stopBIndex = this.stopIndices.get(stopB);
|
|
17214
17458
|
if (stopBIndex === undefined) {
|
|
17215
|
-
throw new Error(`Stop index ${
|
|
17459
|
+
throw new Error(`Stop index not found for ${stopB} in route ${this.serviceRouteId}`);
|
|
17216
17460
|
}
|
|
17217
17461
|
return stopAIndex < stopBIndex;
|
|
17218
17462
|
}
|
|
@@ -17224,6 +17468,14 @@ let Route$1 = class Route {
|
|
|
17224
17468
|
getNbStops() {
|
|
17225
17469
|
return this.nbStops;
|
|
17226
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
|
+
}
|
|
17227
17479
|
/**
|
|
17228
17480
|
* Finds the ServiceRouteId of the route. It corresponds the identifier
|
|
17229
17481
|
* of the service shown to the end user as a route.
|
|
@@ -17241,7 +17493,7 @@ let Route$1 = class Route {
|
|
|
17241
17493
|
* @returns The arrival time at the specified stop and trip as a Time object.
|
|
17242
17494
|
*/
|
|
17243
17495
|
arrivalAt(stopId, tripIndex) {
|
|
17244
|
-
const arrivalIndex = (tripIndex * this.stops.length + this.
|
|
17496
|
+
const arrivalIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2;
|
|
17245
17497
|
const arrival = this.stopTimes[arrivalIndex];
|
|
17246
17498
|
if (arrival === undefined) {
|
|
17247
17499
|
throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
@@ -17256,7 +17508,7 @@ let Route$1 = class Route {
|
|
|
17256
17508
|
* @returns The departure time at the specified stop and trip as a Time object.
|
|
17257
17509
|
*/
|
|
17258
17510
|
departureFrom(stopId, tripIndex) {
|
|
17259
|
-
const departureIndex = (tripIndex * this.stops.length + this.
|
|
17511
|
+
const departureIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2 + 1;
|
|
17260
17512
|
const departure = this.stopTimes[departureIndex];
|
|
17261
17513
|
if (departure === undefined) {
|
|
17262
17514
|
throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
@@ -17271,7 +17523,7 @@ let Route$1 = class Route {
|
|
|
17271
17523
|
* @returns The pick-up type at the specified stop and trip.
|
|
17272
17524
|
*/
|
|
17273
17525
|
pickUpTypeFrom(stopId, tripIndex) {
|
|
17274
|
-
const globalIndex = tripIndex * this.stops.length + this.
|
|
17526
|
+
const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
|
|
17275
17527
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17276
17528
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17277
17529
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
@@ -17291,7 +17543,7 @@ let Route$1 = class Route {
|
|
|
17291
17543
|
* @returns The drop-off type at the specified stop and trip.
|
|
17292
17544
|
*/
|
|
17293
17545
|
dropOffTypeAt(stopId, tripIndex) {
|
|
17294
|
-
const globalIndex = tripIndex * this.stops.length + this.
|
|
17546
|
+
const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
|
|
17295
17547
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17296
17548
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17297
17549
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
@@ -17351,13 +17603,25 @@ let Route$1 = class Route {
|
|
|
17351
17603
|
* @param stopId The StopId of the stop to locate in the route.
|
|
17352
17604
|
* @returns The index of the stop in the route.
|
|
17353
17605
|
*/
|
|
17354
|
-
|
|
17606
|
+
stopRouteIndex(stopId) {
|
|
17355
17607
|
const stopIndex = this.stopIndices.get(stopId);
|
|
17356
17608
|
if (stopIndex === undefined) {
|
|
17357
17609
|
throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
|
|
17358
17610
|
}
|
|
17359
17611
|
return stopIndex;
|
|
17360
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
|
+
}
|
|
17361
17625
|
};
|
|
17362
17626
|
|
|
17363
17627
|
const isLittleEndian = (() => {
|
|
@@ -17428,10 +17692,15 @@ const bytesToUint16Array = (bytes) => {
|
|
|
17428
17692
|
const serializeStopsAdjacency = (stopsAdjacency) => {
|
|
17429
17693
|
return stopsAdjacency.map((value) => {
|
|
17430
17694
|
return {
|
|
17431
|
-
transfers: value.transfers
|
|
17432
|
-
|
|
17433
|
-
|
|
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
|
+
: [],
|
|
17434
17700
|
routes: value.routes,
|
|
17701
|
+
tripContinuations: value.tripContinuations
|
|
17702
|
+
? serializeTripContinuations(value.tripContinuations)
|
|
17703
|
+
: [],
|
|
17435
17704
|
};
|
|
17436
17705
|
});
|
|
17437
17706
|
};
|
|
@@ -17471,10 +17740,17 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
|
|
|
17471
17740
|
}));
|
|
17472
17741
|
transfers.push(newTransfer);
|
|
17473
17742
|
}
|
|
17474
|
-
|
|
17475
|
-
transfers: transfers,
|
|
17743
|
+
const stopAdjacency = {
|
|
17476
17744
|
routes: value.routes,
|
|
17477
|
-
}
|
|
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);
|
|
17478
17754
|
}
|
|
17479
17755
|
return result;
|
|
17480
17756
|
};
|
|
@@ -17484,7 +17760,7 @@ const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
|
|
|
17484
17760
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17485
17761
|
const value = protoRoutesAdjacency[i];
|
|
17486
17762
|
const stops = bytesToUint32Array(value.stops);
|
|
17487
|
-
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));
|
|
17488
17764
|
}
|
|
17489
17765
|
return routesAdjacency;
|
|
17490
17766
|
};
|
|
@@ -17578,6 +17854,46 @@ const serializeRouteType = (type) => {
|
|
|
17578
17854
|
return RouteType.MONORAIL;
|
|
17579
17855
|
}
|
|
17580
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
|
+
};
|
|
17581
17897
|
|
|
17582
17898
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
17583
17899
|
const ALL_TRANSPORT_MODES = new Set([
|
|
@@ -17592,7 +17908,8 @@ const ALL_TRANSPORT_MODES = new Set([
|
|
|
17592
17908
|
'TROLLEYBUS',
|
|
17593
17909
|
'MONORAIL',
|
|
17594
17910
|
]);
|
|
17595
|
-
const
|
|
17911
|
+
const EMPTY_TRIP_CONTINUATIONS = [];
|
|
17912
|
+
const CURRENT_VERSION = '0.0.8';
|
|
17596
17913
|
/**
|
|
17597
17914
|
* The internal transit timetable format.
|
|
17598
17915
|
*/
|
|
@@ -17603,9 +17920,10 @@ class Timetable {
|
|
|
17603
17920
|
this.serviceRoutes = routes;
|
|
17604
17921
|
this.activeStops = new Set();
|
|
17605
17922
|
for (let i = 0; i < stopsAdjacency.length; i++) {
|
|
17606
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17607
17923
|
const stop = stopsAdjacency[i];
|
|
17608
|
-
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)) {
|
|
17609
17927
|
this.activeStops.add(i);
|
|
17610
17928
|
}
|
|
17611
17929
|
}
|
|
@@ -17668,8 +17986,27 @@ class Timetable {
|
|
|
17668
17986
|
* @returns An array of transfer options available at the stop.
|
|
17669
17987
|
*/
|
|
17670
17988
|
getTransfers(stopId) {
|
|
17671
|
-
|
|
17672
|
-
|
|
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);
|
|
17673
18010
|
}
|
|
17674
18011
|
/**
|
|
17675
18012
|
* Retrieves the service route associated with the given route.
|
|
@@ -19955,6 +20292,7 @@ const parseGtfsLocationType = (gtfsLocationType) => {
|
|
|
19955
20292
|
const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
|
|
19956
20293
|
var _a, e_1, _b, _c;
|
|
19957
20294
|
const transfers = new Map();
|
|
20295
|
+
const tripContinuations = new Map();
|
|
19958
20296
|
try {
|
|
19959
20297
|
for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
|
|
19960
20298
|
'transfer_type',
|
|
@@ -19968,25 +20306,48 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
|
|
|
19968
20306
|
transferEntry.transfer_type === 5) {
|
|
19969
20307
|
continue;
|
|
19970
20308
|
}
|
|
19971
|
-
if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
|
|
19972
|
-
console.warn(`Unsupported transfer between trips ${transferEntry.from_trip_id} and ${transferEntry.to_trip_id}.`);
|
|
19973
|
-
continue;
|
|
19974
|
-
}
|
|
19975
|
-
if (transferEntry.from_route_id && transferEntry.to_route_id) {
|
|
19976
|
-
console.warn(`Unsupported transfer between routes ${transferEntry.from_route_id} and ${transferEntry.to_route_id}.`);
|
|
19977
|
-
continue;
|
|
19978
|
-
}
|
|
19979
20309
|
if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
|
|
19980
20310
|
console.warn(`Missing transfer origin or destination stop.`);
|
|
19981
20311
|
continue;
|
|
19982
20312
|
}
|
|
19983
|
-
if (transferEntry.transfer_type === 2 && !transferEntry.min_transfer_time) {
|
|
19984
|
-
console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
|
|
19985
|
-
}
|
|
19986
20313
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19987
20314
|
const fromStop = stopsMap.get(transferEntry.from_stop_id);
|
|
19988
20315
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19989
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
|
+
}
|
|
19990
20351
|
const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
|
|
19991
20352
|
minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
|
|
19992
20353
|
}));
|
|
@@ -20002,7 +20363,10 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
|
|
|
20002
20363
|
}
|
|
20003
20364
|
finally { if (e_1) throw e_1.error; }
|
|
20004
20365
|
}
|
|
20005
|
-
return
|
|
20366
|
+
return {
|
|
20367
|
+
transfers,
|
|
20368
|
+
tripContinuations,
|
|
20369
|
+
};
|
|
20006
20370
|
});
|
|
20007
20371
|
const parseGtfsTransferType = (gtfsTransferType) => {
|
|
20008
20372
|
switch (gtfsTransferType) {
|
|
@@ -20065,11 +20429,13 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20065
20429
|
const stopTimesArray = new Uint16Array(stopsCount * tripsCount * 2);
|
|
20066
20430
|
const allPickUpTypes = [];
|
|
20067
20431
|
const allDropOffTypes = [];
|
|
20432
|
+
const gtfsTripIds = [];
|
|
20068
20433
|
for (let tripIndex = 0; tripIndex < tripsCount; tripIndex++) {
|
|
20069
20434
|
const trip = builder.trips[tripIndex];
|
|
20070
20435
|
if (!trip) {
|
|
20071
20436
|
throw new Error(`Missing trip data at index ${tripIndex}`);
|
|
20072
20437
|
}
|
|
20438
|
+
gtfsTripIds.push(trip.gtfsTripId);
|
|
20073
20439
|
const baseIndex = tripIndex * stopsCount * 2;
|
|
20074
20440
|
for (let stopIndex = 0; stopIndex < stopsCount; stopIndex++) {
|
|
20075
20441
|
const timeIndex = baseIndex + stopIndex * 2;
|
|
@@ -20091,12 +20457,15 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20091
20457
|
}
|
|
20092
20458
|
// Use 2-bit encoding for pickup/drop-off types
|
|
20093
20459
|
const pickUpDropOffTypesArray = encodePickUpDropOffTypes(allPickUpTypes, allDropOffTypes);
|
|
20094
|
-
return
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20460
|
+
return [
|
|
20461
|
+
{
|
|
20462
|
+
serviceRouteId: builder.serviceRouteId,
|
|
20463
|
+
stops: stopsArray,
|
|
20464
|
+
stopTimes: stopTimesArray,
|
|
20465
|
+
pickUpDropOffTypes: pickUpDropOffTypesArray,
|
|
20466
|
+
},
|
|
20467
|
+
gtfsTripIds,
|
|
20468
|
+
];
|
|
20100
20469
|
};
|
|
20101
20470
|
/**
|
|
20102
20471
|
* Parses the trips.txt file from a GTFS feed
|
|
@@ -20135,11 +20504,12 @@ const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) => __awaiter(void
|
|
|
20135
20504
|
}
|
|
20136
20505
|
return trips;
|
|
20137
20506
|
});
|
|
20138
|
-
const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
|
|
20139
|
-
// TODO somehow works when it's a map
|
|
20507
|
+
const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, transfersMap, tripContinuationsMap, nbStops, activeStops) => {
|
|
20140
20508
|
const stopsAdjacency = new Array(nbStops);
|
|
20141
20509
|
for (let i = 0; i < nbStops; i++) {
|
|
20142
|
-
stopsAdjacency[i] = {
|
|
20510
|
+
stopsAdjacency[i] = {
|
|
20511
|
+
routes: [],
|
|
20512
|
+
};
|
|
20143
20513
|
}
|
|
20144
20514
|
for (let index = 0; index < routes.length; index++) {
|
|
20145
20515
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -20164,12 +20534,50 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
|
|
|
20164
20534
|
const transfer = transfers[i];
|
|
20165
20535
|
if (activeStops.has(stop) || activeStops.has(transfer.destination)) {
|
|
20166
20536
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20167
|
-
stopsAdjacency[stop]
|
|
20537
|
+
const stopAdj = stopsAdjacency[stop];
|
|
20538
|
+
if (!stopAdj.transfers) {
|
|
20539
|
+
stopAdj.transfers = [];
|
|
20540
|
+
}
|
|
20541
|
+
stopAdj.transfers.push(transfer);
|
|
20168
20542
|
activeStops.add(transfer.destination);
|
|
20169
20543
|
activeStops.add(stop);
|
|
20170
20544
|
}
|
|
20171
20545
|
}
|
|
20172
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
|
+
}
|
|
20173
20581
|
return stopsAdjacency;
|
|
20174
20582
|
};
|
|
20175
20583
|
/**
|
|
@@ -20228,6 +20636,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20228
20636
|
}
|
|
20229
20637
|
routeBuilder.trips.push({
|
|
20230
20638
|
firstDeparture,
|
|
20639
|
+
gtfsTripId: currentTripId,
|
|
20231
20640
|
arrivalTimes: arrivalTimes,
|
|
20232
20641
|
departureTimes: departureTimes,
|
|
20233
20642
|
pickUpTypes: pickUpTypes,
|
|
@@ -20265,6 +20674,9 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20265
20674
|
continue;
|
|
20266
20675
|
}
|
|
20267
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.
|
|
20268
20680
|
continue;
|
|
20269
20681
|
}
|
|
20270
20682
|
if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
|
|
@@ -20301,11 +20713,19 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20301
20713
|
addTrip(currentTripId);
|
|
20302
20714
|
}
|
|
20303
20715
|
const routesAdjacency = [];
|
|
20716
|
+
const tripsMapping = new Map();
|
|
20304
20717
|
for (const [, routeBuilder] of routeBuilders) {
|
|
20305
|
-
const routeData = finalizeRouteFromBuilder(routeBuilder);
|
|
20306
|
-
|
|
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
|
+
});
|
|
20307
20727
|
}
|
|
20308
|
-
return { routes: routesAdjacency, serviceRoutesMap };
|
|
20728
|
+
return { routes: routesAdjacency, serviceRoutesMap, tripsMapping };
|
|
20309
20729
|
});
|
|
20310
20730
|
const parsePickupDropOffType = (gtfsType) => {
|
|
20311
20731
|
switch (gtfsType) {
|
|
@@ -20384,24 +20804,27 @@ class GtfsParser {
|
|
|
20384
20804
|
const tripsEnd = performance.now();
|
|
20385
20805
|
log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
|
|
20386
20806
|
let transfers = new Map();
|
|
20807
|
+
let tripContinuations = new Map();
|
|
20387
20808
|
if (entries[TRANSFERS_FILE]) {
|
|
20388
20809
|
log.info(`Parsing ${TRANSFERS_FILE}`);
|
|
20389
20810
|
const transfersStart = performance.now();
|
|
20390
20811
|
const transfersStream = yield zip.stream(TRANSFERS_FILE);
|
|
20391
|
-
transfers = yield parseTransfers(transfersStream, parsedStops);
|
|
20812
|
+
const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = yield parseTransfers(transfersStream, parsedStops);
|
|
20813
|
+
transfers = parsedTransfers;
|
|
20814
|
+
tripContinuations = parsedTripContinuations;
|
|
20392
20815
|
const transfersEnd = performance.now();
|
|
20393
|
-
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)`);
|
|
20394
20817
|
}
|
|
20395
20818
|
log.info(`Parsing ${STOP_TIMES_FILE}`);
|
|
20396
20819
|
const stopTimesStart = performance.now();
|
|
20397
20820
|
const stopTimesStream = yield zip.stream(STOP_TIMES_FILE);
|
|
20398
|
-
const { routes, serviceRoutesMap } = yield parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
|
|
20821
|
+
const { routes, serviceRoutesMap, tripsMapping } = yield parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
|
|
20399
20822
|
const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
|
|
20400
20823
|
const stopTimesEnd = performance.now();
|
|
20401
20824
|
log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
|
|
20402
20825
|
log.info('Building stops adjacency structure');
|
|
20403
20826
|
const stopsAdjacencyStart = performance.now();
|
|
20404
|
-
const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
|
|
20827
|
+
const stopsAdjacency = buildStopsAdjacencyStructure(tripsMapping, serviceRoutes, routes, transfers, tripContinuations, parsedStops.size, activeStopIds);
|
|
20405
20828
|
const stopsAdjacencyEnd = performance.now();
|
|
20406
20829
|
log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
|
|
20407
20830
|
yield zip.close();
|
|
@@ -20482,53 +20905,242 @@ const chGtfsProfile = {
|
|
|
20482
20905
|
|
|
20483
20906
|
class Plotter {
|
|
20484
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
|
+
];
|
|
20485
20917
|
this.result = result;
|
|
20486
20918
|
}
|
|
20487
20919
|
/**
|
|
20488
|
-
*
|
|
20489
|
-
*
|
|
20490
|
-
* @returns A string representing the DOT graph of the path tree.
|
|
20920
|
+
* Gets the color for a round based on the specified palette.
|
|
20491
20921
|
*/
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
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}"];`,
|
|
20498
21015
|
];
|
|
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
|
-
|
|
20528
|
-
|
|
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);
|
|
20529
21088
|
}
|
|
20530
21089
|
});
|
|
20531
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
|
+
}
|
|
21117
|
+
}
|
|
21118
|
+
});
|
|
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);
|
|
20532
21144
|
dotParts.push('}');
|
|
20533
21145
|
return dotParts.join('\n');
|
|
20534
21146
|
}
|
|
@@ -20727,11 +21339,11 @@ class Route {
|
|
|
20727
21339
|
}
|
|
20728
21340
|
|
|
20729
21341
|
class Result {
|
|
20730
|
-
constructor(query,
|
|
21342
|
+
constructor(query, routingState, stopsIndex, timetable) {
|
|
20731
21343
|
this.query = query;
|
|
20732
|
-
this.
|
|
20733
|
-
this.earliestArrivalsPerRound = earliestArrivalsPerRound;
|
|
21344
|
+
this.routingState = routingState;
|
|
20734
21345
|
this.stopsIndex = stopsIndex;
|
|
21346
|
+
this.timetable = timetable;
|
|
20735
21347
|
}
|
|
20736
21348
|
/**
|
|
20737
21349
|
* Reconstructs the best route to a stop.
|
|
@@ -20741,17 +21353,18 @@ class Result {
|
|
|
20741
21353
|
* @returns a route to the destination stop if it exists.
|
|
20742
21354
|
*/
|
|
20743
21355
|
bestRoute(to) {
|
|
20744
|
-
var _a
|
|
21356
|
+
var _a;
|
|
20745
21357
|
const destinationList = to instanceof Set
|
|
20746
21358
|
? Array.from(to)
|
|
20747
21359
|
: to
|
|
20748
21360
|
? [to]
|
|
20749
21361
|
: Array.from(this.query.to);
|
|
20750
21362
|
const destinations = destinationList.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
21363
|
+
// find the first reached destination
|
|
20751
21364
|
let fastestDestination = undefined;
|
|
20752
21365
|
let fastestTime = undefined;
|
|
20753
21366
|
for (const destination of destinations) {
|
|
20754
|
-
const arrivalTime = this.earliestArrivals.get(destination.id);
|
|
21367
|
+
const arrivalTime = this.routingState.earliestArrivals.get(destination.id);
|
|
20755
21368
|
if (arrivalTime !== undefined) {
|
|
20756
21369
|
if (fastestTime === undefined ||
|
|
20757
21370
|
arrivalTime.arrival.isBefore(fastestTime.arrival)) {
|
|
@@ -20766,19 +21379,84 @@ class Result {
|
|
|
20766
21379
|
const route = [];
|
|
20767
21380
|
let currentStop = fastestDestination;
|
|
20768
21381
|
let round = fastestTime.legNumber;
|
|
20769
|
-
while (
|
|
20770
|
-
const
|
|
20771
|
-
if (!
|
|
20772
|
-
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);
|
|
21400
|
+
}
|
|
21401
|
+
else {
|
|
21402
|
+
break;
|
|
20773
21403
|
}
|
|
20774
|
-
route.unshift(
|
|
20775
|
-
currentStop =
|
|
20776
|
-
if ('
|
|
21404
|
+
route.unshift(leg);
|
|
21405
|
+
currentStop = leg.from.id;
|
|
21406
|
+
if ('routeId' in edge) {
|
|
20777
21407
|
round -= 1;
|
|
20778
21408
|
}
|
|
20779
21409
|
}
|
|
20780
21410
|
return new Route(route);
|
|
20781
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
|
+
}
|
|
20782
21460
|
/**
|
|
20783
21461
|
* Returns the arrival time at any stop reachable in less time / transfers than the destination(s) of the query)
|
|
20784
21462
|
*
|
|
@@ -20787,14 +21465,28 @@ class Result {
|
|
|
20787
21465
|
* @returns The arrival time if the target stop is reachable, otherwise undefined.
|
|
20788
21466
|
*/
|
|
20789
21467
|
arrivalAt(stop, maxTransfers) {
|
|
21468
|
+
var _a;
|
|
20790
21469
|
const equivalentStops = this.stopsIndex.equivalentStops(stop);
|
|
20791
21470
|
let earliestArrival = undefined;
|
|
20792
|
-
const relevantArrivals = maxTransfers !== undefined
|
|
20793
|
-
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20794
|
-
this.earliestArrivalsPerRound[maxTransfers + 1]
|
|
20795
|
-
: this.earliestArrivals;
|
|
20796
21471
|
for (const equivalentStop of equivalentStops) {
|
|
20797
|
-
|
|
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
|
+
}
|
|
20798
21490
|
if (arrivalTime !== undefined) {
|
|
20799
21491
|
if (earliestArrival === undefined ||
|
|
20800
21492
|
arrivalTime.arrival.isBefore(earliestArrival.arrival)) {
|
|
@@ -20808,9 +21500,9 @@ class Result {
|
|
|
20808
21500
|
|
|
20809
21501
|
const UNREACHED = Time.infinity();
|
|
20810
21502
|
/**
|
|
20811
|
-
* A public transportation
|
|
20812
|
-
*
|
|
20813
|
-
*
|
|
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:
|
|
20814
21506
|
* https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
|
|
20815
21507
|
*/
|
|
20816
21508
|
class Router {
|
|
@@ -20819,25 +21511,227 @@ class Router {
|
|
|
20819
21511
|
this.stopsIndex = stopsIndex;
|
|
20820
21512
|
}
|
|
20821
21513
|
/**
|
|
20822
|
-
*
|
|
20823
|
-
*
|
|
20824
|
-
*
|
|
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.
|
|
20825
21572
|
*/
|
|
20826
|
-
|
|
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
|
|
21724
|
+
*/
|
|
21725
|
+
considerTransfers(query, round, markedStops, routingState) {
|
|
20827
21726
|
var _a, _b;
|
|
20828
21727
|
const { options } = query;
|
|
21728
|
+
const arrivalsAtCurrentRound = routingState.graph[round];
|
|
20829
21729
|
const newlyMarkedStops = new Set();
|
|
20830
|
-
const
|
|
20831
|
-
for (let i = 0; i < markedStopsArray.length; i++) {
|
|
20832
|
-
const stop = markedStopsArray[i];
|
|
21730
|
+
for (const stop of markedStops) {
|
|
20833
21731
|
const currentArrival = arrivalsAtCurrentRound.get(stop);
|
|
20834
|
-
if (!currentArrival)
|
|
20835
|
-
continue;
|
|
20836
21732
|
// Skip transfers if the last leg was also a transfer
|
|
20837
|
-
|
|
20838
|
-
if (previousLeg && !('route' in previousLeg)) {
|
|
21733
|
+
if (!currentArrival || 'type' in currentArrival)
|
|
20839
21734
|
continue;
|
|
20840
|
-
}
|
|
20841
21735
|
const transfers = this.timetable.getTransfers(stop);
|
|
20842
21736
|
for (let j = 0; j < transfers.length; j++) {
|
|
20843
21737
|
const transfer = transfers[j];
|
|
@@ -20846,6 +21740,7 @@ class Router {
|
|
|
20846
21740
|
transferTime = transfer.minTransferTime;
|
|
20847
21741
|
}
|
|
20848
21742
|
else if (transfer.type === 'IN_SEAT') {
|
|
21743
|
+
// TODO not needed anymore now that trip continuations are handled separately
|
|
20849
21744
|
transferTime = Duration.zero();
|
|
20850
21745
|
}
|
|
20851
21746
|
else {
|
|
@@ -20854,32 +21749,22 @@ class Router {
|
|
|
20854
21749
|
const arrivalAfterTransfer = currentArrival.arrival.plus(transferTime);
|
|
20855
21750
|
const originalArrival = (_b = (_a = arrivalsAtCurrentRound.get(transfer.destination)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20856
21751
|
if (arrivalAfterTransfer.isBefore(originalArrival)) {
|
|
20857
|
-
const origin = currentArrival.origin;
|
|
20858
21752
|
arrivalsAtCurrentRound.set(transfer.destination, {
|
|
20859
21753
|
arrival: arrivalAfterTransfer,
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
|
|
20863
|
-
|
|
20864
|
-
to: this.stopsIndex.findStopById(transfer.destination),
|
|
20865
|
-
minTransferTime: transfer.minTransferTime,
|
|
20866
|
-
type: transfer.type,
|
|
20867
|
-
},
|
|
21754
|
+
from: stop,
|
|
21755
|
+
to: transfer.destination,
|
|
21756
|
+
minTransferTime: transfer.minTransferTime,
|
|
21757
|
+
type: transfer.type,
|
|
20868
21758
|
});
|
|
20869
|
-
earliestArrivals.set(transfer.destination, {
|
|
21759
|
+
routingState.earliestArrivals.set(transfer.destination, {
|
|
20870
21760
|
arrival: arrivalAfterTransfer,
|
|
20871
21761
|
legNumber: round,
|
|
20872
|
-
origin: origin,
|
|
20873
21762
|
});
|
|
20874
21763
|
newlyMarkedStops.add(transfer.destination);
|
|
20875
21764
|
}
|
|
20876
21765
|
}
|
|
20877
21766
|
}
|
|
20878
|
-
|
|
20879
|
-
for (let i = 0; i < newlyMarkedStopsArray.length; i++) {
|
|
20880
|
-
const newStop = newlyMarkedStopsArray[i];
|
|
20881
|
-
markedStops.add(newStop);
|
|
20882
|
-
}
|
|
21767
|
+
return newlyMarkedStops;
|
|
20883
21768
|
}
|
|
20884
21769
|
/**
|
|
20885
21770
|
* Finds the earliest arrival time at any stop from a given set of destinations.
|
|
@@ -20893,117 +21778,11 @@ class Router {
|
|
|
20893
21778
|
let earliestArrivalAtAnyDestination = UNREACHED;
|
|
20894
21779
|
for (let i = 0; i < destinations.length; i++) {
|
|
20895
21780
|
const destination = destinations[i];
|
|
20896
|
-
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;
|
|
20897
21782
|
earliestArrivalAtAnyDestination = Time.min(earliestArrivalAtAnyDestination, arrival);
|
|
20898
21783
|
}
|
|
20899
21784
|
return earliestArrivalAtAnyDestination;
|
|
20900
21785
|
}
|
|
20901
|
-
/**
|
|
20902
|
-
* The main Raptor algorithm implementation.
|
|
20903
|
-
*
|
|
20904
|
-
* @param query The query containing the main parameters for the routing.
|
|
20905
|
-
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
20906
|
-
*/
|
|
20907
|
-
route(query) {
|
|
20908
|
-
var _a, _b, _c, _d, _e;
|
|
20909
|
-
const { from, to, departureTime, options } = query;
|
|
20910
|
-
// Consider children or siblings of the "from" stop as potential origins
|
|
20911
|
-
const origins = this.stopsIndex.equivalentStops(from);
|
|
20912
|
-
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
20913
|
-
const destinations = Array.from(to).flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
20914
|
-
const earliestArrivals = new Map();
|
|
20915
|
-
const earliestArrivalsWithoutAnyLeg = new Map();
|
|
20916
|
-
const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
|
|
20917
|
-
// Stops that have been improved at round k-1
|
|
20918
|
-
const markedStops = new Set();
|
|
20919
|
-
for (let i = 0; i < origins.length; i++) {
|
|
20920
|
-
const originStop = origins[i];
|
|
20921
|
-
markedStops.add(originStop.id);
|
|
20922
|
-
earliestArrivals.set(originStop.id, {
|
|
20923
|
-
arrival: departureTime,
|
|
20924
|
-
legNumber: 0,
|
|
20925
|
-
origin: originStop.id,
|
|
20926
|
-
});
|
|
20927
|
-
earliestArrivalsWithoutAnyLeg.set(originStop.id, {
|
|
20928
|
-
arrival: departureTime,
|
|
20929
|
-
legNumber: 0,
|
|
20930
|
-
origin: originStop.id,
|
|
20931
|
-
});
|
|
20932
|
-
}
|
|
20933
|
-
// on the first round we need to first consider transfers to discover all possible route origins
|
|
20934
|
-
this.considerTransfers(query, markedStops, earliestArrivalsWithoutAnyLeg, earliestArrivals, 0);
|
|
20935
|
-
for (let round = 1; round <= options.maxTransfers + 1; round++) {
|
|
20936
|
-
const arrivalsAtCurrentRound = new Map();
|
|
20937
|
-
earliestArrivalsPerRound.push(arrivalsAtCurrentRound);
|
|
20938
|
-
const arrivalsAtPreviousRound = earliestArrivalsPerRound[round - 1];
|
|
20939
|
-
// Routes that contain at least one stop reached with at least round - 1 legs
|
|
20940
|
-
// together with corresponding hop on stop index (earliest marked stop)
|
|
20941
|
-
const reachableRoutes = this.timetable.findReachableRoutes(markedStops, options.transportModes);
|
|
20942
|
-
markedStops.clear();
|
|
20943
|
-
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(earliestArrivals, destinations);
|
|
20944
|
-
// for each route that can be reached with at least round - 1 trips
|
|
20945
|
-
const reachableRoutesArray = Array.from(reachableRoutes.entries());
|
|
20946
|
-
for (let i = 0; i < reachableRoutesArray.length; i++) {
|
|
20947
|
-
const [route, hopOnStop] = reachableRoutesArray[i];
|
|
20948
|
-
let currentTrip = undefined;
|
|
20949
|
-
const startIndex = route.stopIndex(hopOnStop);
|
|
20950
|
-
for (let j = startIndex; j < route.getNbStops(); j++) {
|
|
20951
|
-
const currentStop = route.stops[j];
|
|
20952
|
-
// If we're currently on a trip,
|
|
20953
|
-
// check if arrival at the stop improves the earliest arrival time
|
|
20954
|
-
if (currentTrip !== undefined) {
|
|
20955
|
-
const currentArrivalTime = route.arrivalAt(currentStop, currentTrip.tripIndex);
|
|
20956
|
-
const currentDropOffType = route.dropOffTypeAt(currentStop, currentTrip.tripIndex);
|
|
20957
|
-
const earliestArrivalAtCurrentStop = (_b = (_a = earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20958
|
-
if (currentDropOffType !== 'NOT_AVAILABLE' &&
|
|
20959
|
-
currentArrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
|
|
20960
|
-
currentArrivalTime.isBefore(earliestArrivalAtAnyDestination)) {
|
|
20961
|
-
const bestHopOnDepartureTime = route.departureFrom(currentTrip.bestHopOnStop, currentTrip.tripIndex);
|
|
20962
|
-
arrivalsAtCurrentRound.set(currentStop, {
|
|
20963
|
-
arrival: currentArrivalTime,
|
|
20964
|
-
legNumber: round,
|
|
20965
|
-
origin: currentTrip.origin,
|
|
20966
|
-
leg: {
|
|
20967
|
-
from: this.stopsIndex.findStopById(currentTrip.bestHopOnStop),
|
|
20968
|
-
to: this.stopsIndex.findStopById(currentStop),
|
|
20969
|
-
departureTime: bestHopOnDepartureTime,
|
|
20970
|
-
arrivalTime: currentArrivalTime,
|
|
20971
|
-
route: this.timetable.getServiceRouteInfo(route),
|
|
20972
|
-
},
|
|
20973
|
-
});
|
|
20974
|
-
earliestArrivals.set(currentStop, {
|
|
20975
|
-
arrival: currentArrivalTime,
|
|
20976
|
-
legNumber: round,
|
|
20977
|
-
origin: currentTrip.origin,
|
|
20978
|
-
});
|
|
20979
|
-
markedStops.add(currentStop);
|
|
20980
|
-
}
|
|
20981
|
-
}
|
|
20982
|
-
// check if we can board an earlier trip at the current stop
|
|
20983
|
-
// if there was no current trip, find the first one reachable
|
|
20984
|
-
const earliestArrivalOnPreviousRound = (_c = arrivalsAtPreviousRound.get(currentStop)) === null || _c === void 0 ? void 0 : _c.arrival;
|
|
20985
|
-
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
20986
|
-
(currentTrip === undefined ||
|
|
20987
|
-
earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStop, currentTrip.tripIndex)) ||
|
|
20988
|
-
earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStop, currentTrip.tripIndex)))) {
|
|
20989
|
-
const earliestTrip = route.findEarliestTrip(currentStop, earliestArrivalOnPreviousRound, currentTrip === null || currentTrip === void 0 ? void 0 : currentTrip.tripIndex);
|
|
20990
|
-
if (earliestTrip !== undefined) {
|
|
20991
|
-
currentTrip = {
|
|
20992
|
-
tripIndex: earliestTrip,
|
|
20993
|
-
// we need to keep track of the best hop-on stop to reconstruct the route at the end
|
|
20994
|
-
bestHopOnStop: currentStop,
|
|
20995
|
-
origin: (_e = (_d = arrivalsAtPreviousRound.get(currentStop)) === null || _d === void 0 ? void 0 : _d.origin) !== null && _e !== void 0 ? _e : currentStop,
|
|
20996
|
-
};
|
|
20997
|
-
}
|
|
20998
|
-
}
|
|
20999
|
-
}
|
|
21000
|
-
}
|
|
21001
|
-
this.considerTransfers(query, markedStops, arrivalsAtCurrentRound, earliestArrivals, round);
|
|
21002
|
-
if (markedStops.size === 0)
|
|
21003
|
-
break;
|
|
21004
|
-
}
|
|
21005
|
-
return new Result(query, earliestArrivals, earliestArrivalsPerRound, this.stopsIndex);
|
|
21006
|
-
}
|
|
21007
21786
|
}
|
|
21008
21787
|
|
|
21009
21788
|
/**
|
|
@@ -21140,7 +21919,6 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21140
21919
|
replServer.defineCommand('route', {
|
|
21141
21920
|
help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
|
|
21142
21921
|
action(routeQuery) {
|
|
21143
|
-
var _a;
|
|
21144
21922
|
this.clearBufferedCommand();
|
|
21145
21923
|
const parts = routeQuery.split(' ').filter(Boolean);
|
|
21146
21924
|
const withTransfersIndex = parts.indexOf('with');
|
|
@@ -21196,12 +21974,13 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21196
21974
|
console.log(`Destination not reachable`);
|
|
21197
21975
|
}
|
|
21198
21976
|
else {
|
|
21199
|
-
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}.`);
|
|
21200
21978
|
}
|
|
21201
21979
|
const bestRoute = result.bestRoute(toStop.sourceStopId);
|
|
21202
21980
|
if (bestRoute) {
|
|
21203
21981
|
console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
|
|
21204
21982
|
console.log(bestRoute.toString());
|
|
21983
|
+
console.log(bestRoute.asJson());
|
|
21205
21984
|
}
|
|
21206
21985
|
else {
|
|
21207
21986
|
console.log('No route found');
|
|
@@ -21277,6 +22056,203 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21277
22056
|
this.displayPrompt();
|
|
21278
22057
|
},
|
|
21279
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
|
+
});
|
|
21280
22256
|
};
|
|
21281
22257
|
|
|
21282
22258
|
const program = new Command();
|