minotor 8.0.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.github/workflows/minotor.yml +1 -1
  2. package/CHANGELOG.md +3 -8
  3. package/README.md +1 -1
  4. package/dist/cli.mjs +352 -256
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +21 -6
  7. package/dist/gtfs/trips.d.ts +2 -2
  8. package/dist/parser.cjs.js +296 -188
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +296 -188
  11. package/dist/parser.esm.js.map +1 -1
  12. package/dist/router.cjs.js +1 -1
  13. package/dist/router.cjs.js.map +1 -1
  14. package/dist/router.esm.js +1 -1
  15. package/dist/router.esm.js.map +1 -1
  16. package/dist/router.umd.js +1 -1
  17. package/dist/router.umd.js.map +1 -1
  18. package/dist/routing/router.d.ts +4 -4
  19. package/dist/timetable/io.d.ts +3 -3
  20. package/dist/timetable/proto/timetable.d.ts +6 -4
  21. package/dist/timetable/route.d.ts +13 -21
  22. package/dist/timetable/timetable.d.ts +13 -11
  23. package/dist/timetable/tripBoardingId.d.ts +34 -0
  24. package/package.json +1 -1
  25. package/src/__e2e__/timetable/timetable.bin +2 -2
  26. package/src/cli/repl.ts +53 -67
  27. package/src/gtfs/__tests__/parser.test.ts +19 -4
  28. package/src/gtfs/__tests__/transfers.test.ts +598 -318
  29. package/src/gtfs/__tests__/trips.test.ts +3 -44
  30. package/src/gtfs/parser.ts +26 -8
  31. package/src/gtfs/transfers.ts +151 -20
  32. package/src/gtfs/trips.ts +1 -39
  33. package/src/routing/__tests__/result.test.ts +10 -10
  34. package/src/routing/__tests__/router.test.ts +11 -9
  35. package/src/routing/result.ts +2 -2
  36. package/src/routing/router.ts +34 -22
  37. package/src/timetable/__tests__/io.test.ts +8 -7
  38. package/src/timetable/__tests__/route.test.ts +66 -80
  39. package/src/timetable/__tests__/timetable.test.ts +32 -29
  40. package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
  41. package/src/timetable/io.ts +21 -20
  42. package/src/timetable/proto/timetable.proto +6 -4
  43. package/src/timetable/proto/timetable.ts +84 -48
  44. package/src/timetable/route.ts +39 -56
  45. package/src/timetable/timetable.ts +37 -26
  46. package/src/timetable/tripBoardingId.ts +94 -0
  47. package/dist/timetable/tripId.d.ts +0 -15
  48. package/src/timetable/__tests__/tripId.test.ts +0 -27
  49. package/src/timetable/tripId.ts +0 -29
  50. /package/dist/timetable/__tests__/{tripId.test.d.ts → tripBoardingId.test.d.ts} +0 -0
package/dist/cli.mjs CHANGED
@@ -16607,12 +16607,12 @@ const Transfer = {
16607
16607
  },
16608
16608
  };
16609
16609
  function createBaseTripBoarding() {
16610
- return { hopOnStop: 0, routeId: 0, tripIndex: 0 };
16610
+ return { hopOnStopIndex: 0, routeId: 0, tripIndex: 0 };
16611
16611
  }
16612
16612
  const TripBoarding = {
16613
16613
  encode(message, writer = new BinaryWriter()) {
16614
- if (message.hopOnStop !== 0) {
16615
- writer.uint32(8).uint32(message.hopOnStop);
16614
+ if (message.hopOnStopIndex !== 0) {
16615
+ writer.uint32(8).uint32(message.hopOnStopIndex);
16616
16616
  }
16617
16617
  if (message.routeId !== 0) {
16618
16618
  writer.uint32(16).uint32(message.routeId);
@@ -16633,7 +16633,7 @@ const TripBoarding = {
16633
16633
  if (tag !== 8) {
16634
16634
  break;
16635
16635
  }
16636
- message.hopOnStop = reader.uint32();
16636
+ message.hopOnStopIndex = reader.uint32();
16637
16637
  continue;
16638
16638
  }
16639
16639
  case 2: {
@@ -16660,15 +16660,15 @@ const TripBoarding = {
16660
16660
  },
16661
16661
  fromJSON(object) {
16662
16662
  return {
16663
- hopOnStop: isSet(object.hopOnStop) ? globalThis.Number(object.hopOnStop) : 0,
16663
+ hopOnStopIndex: isSet(object.hopOnStopIndex) ? globalThis.Number(object.hopOnStopIndex) : 0,
16664
16664
  routeId: isSet(object.routeId) ? globalThis.Number(object.routeId) : 0,
16665
16665
  tripIndex: isSet(object.tripIndex) ? globalThis.Number(object.tripIndex) : 0,
16666
16666
  };
16667
16667
  },
16668
16668
  toJSON(message) {
16669
16669
  const obj = {};
16670
- if (message.hopOnStop !== 0) {
16671
- obj.hopOnStop = Math.round(message.hopOnStop);
16670
+ if (message.hopOnStopIndex !== 0) {
16671
+ obj.hopOnStopIndex = Math.round(message.hopOnStopIndex);
16672
16672
  }
16673
16673
  if (message.routeId !== 0) {
16674
16674
  obj.routeId = Math.round(message.routeId);
@@ -16684,22 +16684,28 @@ const TripBoarding = {
16684
16684
  fromPartial(object) {
16685
16685
  var _a, _b, _c;
16686
16686
  const message = createBaseTripBoarding();
16687
- message.hopOnStop = (_a = object.hopOnStop) !== null && _a !== void 0 ? _a : 0;
16687
+ message.hopOnStopIndex = (_a = object.hopOnStopIndex) !== null && _a !== void 0 ? _a : 0;
16688
16688
  message.routeId = (_b = object.routeId) !== null && _b !== void 0 ? _b : 0;
16689
16689
  message.tripIndex = (_c = object.tripIndex) !== null && _c !== void 0 ? _c : 0;
16690
16690
  return message;
16691
16691
  },
16692
16692
  };
16693
16693
  function createBaseTripContinuationEntry() {
16694
- return { key: 0, value: [] };
16694
+ return { originStopIndex: 0, originRouteId: 0, originTripIndex: 0, continuations: [] };
16695
16695
  }
16696
16696
  const TripContinuationEntry = {
16697
16697
  encode(message, writer = new BinaryWriter()) {
16698
- if (message.key !== 0) {
16699
- writer.uint32(8).uint32(message.key);
16698
+ if (message.originStopIndex !== 0) {
16699
+ writer.uint32(8).uint32(message.originStopIndex);
16700
16700
  }
16701
- for (const v of message.value) {
16702
- TripBoarding.encode(v, writer.uint32(18).fork()).join();
16701
+ if (message.originRouteId !== 0) {
16702
+ writer.uint32(16).uint32(message.originRouteId);
16703
+ }
16704
+ if (message.originTripIndex !== 0) {
16705
+ writer.uint32(24).uint32(message.originTripIndex);
16706
+ }
16707
+ for (const v of message.continuations) {
16708
+ TripBoarding.encode(v, writer.uint32(34).fork()).join();
16703
16709
  }
16704
16710
  return writer;
16705
16711
  },
@@ -16714,14 +16720,28 @@ const TripContinuationEntry = {
16714
16720
  if (tag !== 8) {
16715
16721
  break;
16716
16722
  }
16717
- message.key = reader.uint32();
16723
+ message.originStopIndex = reader.uint32();
16718
16724
  continue;
16719
16725
  }
16720
16726
  case 2: {
16721
- if (tag !== 18) {
16727
+ if (tag !== 16) {
16728
+ break;
16729
+ }
16730
+ message.originRouteId = reader.uint32();
16731
+ continue;
16732
+ }
16733
+ case 3: {
16734
+ if (tag !== 24) {
16722
16735
  break;
16723
16736
  }
16724
- message.value.push(TripBoarding.decode(reader, reader.uint32()));
16737
+ message.originTripIndex = reader.uint32();
16738
+ continue;
16739
+ }
16740
+ case 4: {
16741
+ if (tag !== 34) {
16742
+ break;
16743
+ }
16744
+ message.continuations.push(TripBoarding.decode(reader, reader.uint32()));
16725
16745
  continue;
16726
16746
  }
16727
16747
  }
@@ -16734,18 +16754,28 @@ const TripContinuationEntry = {
16734
16754
  },
16735
16755
  fromJSON(object) {
16736
16756
  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)) : [],
16757
+ originStopIndex: isSet(object.originStopIndex) ? globalThis.Number(object.originStopIndex) : 0,
16758
+ originRouteId: isSet(object.originRouteId) ? globalThis.Number(object.originRouteId) : 0,
16759
+ originTripIndex: isSet(object.originTripIndex) ? globalThis.Number(object.originTripIndex) : 0,
16760
+ continuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.continuations)
16761
+ ? object.continuations.map((e) => TripBoarding.fromJSON(e))
16762
+ : [],
16739
16763
  };
16740
16764
  },
16741
16765
  toJSON(message) {
16742
16766
  var _a;
16743
16767
  const obj = {};
16744
- if (message.key !== 0) {
16745
- obj.key = Math.round(message.key);
16768
+ if (message.originStopIndex !== 0) {
16769
+ obj.originStopIndex = Math.round(message.originStopIndex);
16770
+ }
16771
+ if (message.originRouteId !== 0) {
16772
+ obj.originRouteId = Math.round(message.originRouteId);
16746
16773
  }
16747
- if ((_a = message.value) === null || _a === void 0 ? void 0 : _a.length) {
16748
- obj.value = message.value.map((e) => TripBoarding.toJSON(e));
16774
+ if (message.originTripIndex !== 0) {
16775
+ obj.originTripIndex = Math.round(message.originTripIndex);
16776
+ }
16777
+ if ((_a = message.continuations) === null || _a === void 0 ? void 0 : _a.length) {
16778
+ obj.continuations = message.continuations.map((e) => TripBoarding.toJSON(e));
16749
16779
  }
16750
16780
  return obj;
16751
16781
  },
@@ -16753,15 +16783,17 @@ const TripContinuationEntry = {
16753
16783
  return TripContinuationEntry.fromPartial(base !== null && base !== void 0 ? base : {});
16754
16784
  },
16755
16785
  fromPartial(object) {
16756
- var _a, _b;
16786
+ var _a, _b, _c, _d;
16757
16787
  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))) || [];
16788
+ message.originStopIndex = (_a = object.originStopIndex) !== null && _a !== void 0 ? _a : 0;
16789
+ message.originRouteId = (_b = object.originRouteId) !== null && _b !== void 0 ? _b : 0;
16790
+ message.originTripIndex = (_c = object.originTripIndex) !== null && _c !== void 0 ? _c : 0;
16791
+ message.continuations = ((_d = object.continuations) === null || _d === void 0 ? void 0 : _d.map((e) => TripBoarding.fromPartial(e))) || [];
16760
16792
  return message;
16761
16793
  },
16762
16794
  };
16763
16795
  function createBaseStopAdjacency() {
16764
- return { routes: [], transfers: [], tripContinuations: [] };
16796
+ return { routes: [], transfers: [] };
16765
16797
  }
16766
16798
  const StopAdjacency = {
16767
16799
  encode(message, writer = new BinaryWriter()) {
@@ -16773,9 +16805,6 @@ const StopAdjacency = {
16773
16805
  for (const v of message.transfers) {
16774
16806
  Transfer.encode(v, writer.uint32(18).fork()).join();
16775
16807
  }
16776
- for (const v of message.tripContinuations) {
16777
- TripContinuationEntry.encode(v, writer.uint32(26).fork()).join();
16778
- }
16779
16808
  return writer;
16780
16809
  },
16781
16810
  decode(input, length) {
@@ -16806,13 +16835,6 @@ const StopAdjacency = {
16806
16835
  message.transfers.push(Transfer.decode(reader, reader.uint32()));
16807
16836
  continue;
16808
16837
  }
16809
- case 3: {
16810
- if (tag !== 26) {
16811
- break;
16812
- }
16813
- message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
16814
- continue;
16815
- }
16816
16838
  }
16817
16839
  if ((tag & 7) === 4 || tag === 0) {
16818
16840
  break;
@@ -16827,13 +16849,10 @@ const StopAdjacency = {
16827
16849
  transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
16828
16850
  ? object.transfers.map((e) => Transfer.fromJSON(e))
16829
16851
  : [],
16830
- tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
16831
- ? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
16832
- : [],
16833
16852
  };
16834
16853
  },
16835
16854
  toJSON(message) {
16836
- var _a, _b, _c;
16855
+ var _a, _b;
16837
16856
  const obj = {};
16838
16857
  if ((_a = message.routes) === null || _a === void 0 ? void 0 : _a.length) {
16839
16858
  obj.routes = message.routes.map((e) => Math.round(e));
@@ -16841,20 +16860,16 @@ const StopAdjacency = {
16841
16860
  if ((_b = message.transfers) === null || _b === void 0 ? void 0 : _b.length) {
16842
16861
  obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
16843
16862
  }
16844
- if ((_c = message.tripContinuations) === null || _c === void 0 ? void 0 : _c.length) {
16845
- obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
16846
- }
16847
16863
  return obj;
16848
16864
  },
16849
16865
  create(base) {
16850
16866
  return StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
16851
16867
  },
16852
16868
  fromPartial(object) {
16853
- var _a, _b, _c;
16869
+ var _a, _b;
16854
16870
  const message = createBaseStopAdjacency();
16855
16871
  message.routes = ((_a = object.routes) === null || _a === void 0 ? void 0 : _a.map((e) => e)) || [];
16856
16872
  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))) || [];
16858
16873
  return message;
16859
16874
  },
16860
16875
  };
@@ -16953,7 +16968,7 @@ const ServiceRoute = {
16953
16968
  },
16954
16969
  };
16955
16970
  function createBaseTimetable() {
16956
- return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [] };
16971
+ return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [], tripContinuations: [] };
16957
16972
  }
16958
16973
  const Timetable$1 = {
16959
16974
  encode(message, writer = new BinaryWriter()) {
@@ -16969,6 +16984,9 @@ const Timetable$1 = {
16969
16984
  for (const v of message.serviceRoutes) {
16970
16985
  ServiceRoute.encode(v, writer.uint32(34).fork()).join();
16971
16986
  }
16987
+ for (const v of message.tripContinuations) {
16988
+ TripContinuationEntry.encode(v, writer.uint32(42).fork()).join();
16989
+ }
16972
16990
  return writer;
16973
16991
  },
16974
16992
  decode(input, length) {
@@ -17006,6 +17024,13 @@ const Timetable$1 = {
17006
17024
  message.serviceRoutes.push(ServiceRoute.decode(reader, reader.uint32()));
17007
17025
  continue;
17008
17026
  }
17027
+ case 5: {
17028
+ if (tag !== 42) {
17029
+ break;
17030
+ }
17031
+ message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
17032
+ continue;
17033
+ }
17009
17034
  }
17010
17035
  if ((tag & 7) === 4 || tag === 0) {
17011
17036
  break;
@@ -17026,10 +17051,13 @@ const Timetable$1 = {
17026
17051
  serviceRoutes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.serviceRoutes)
17027
17052
  ? object.serviceRoutes.map((e) => ServiceRoute.fromJSON(e))
17028
17053
  : [],
17054
+ tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
17055
+ ? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
17056
+ : [],
17029
17057
  };
17030
17058
  },
17031
17059
  toJSON(message) {
17032
- var _a, _b, _c;
17060
+ var _a, _b, _c, _d;
17033
17061
  const obj = {};
17034
17062
  if (message.version !== "") {
17035
17063
  obj.version = message.version;
@@ -17043,18 +17071,22 @@ const Timetable$1 = {
17043
17071
  if ((_c = message.serviceRoutes) === null || _c === void 0 ? void 0 : _c.length) {
17044
17072
  obj.serviceRoutes = message.serviceRoutes.map((e) => ServiceRoute.toJSON(e));
17045
17073
  }
17074
+ if ((_d = message.tripContinuations) === null || _d === void 0 ? void 0 : _d.length) {
17075
+ obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
17076
+ }
17046
17077
  return obj;
17047
17078
  },
17048
17079
  create(base) {
17049
17080
  return Timetable$1.fromPartial(base !== null && base !== void 0 ? base : {});
17050
17081
  },
17051
17082
  fromPartial(object) {
17052
- var _a, _b, _c, _d;
17083
+ var _a, _b, _c, _d, _e;
17053
17084
  const message = createBaseTimetable();
17054
17085
  message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
17055
17086
  message.stopsAdjacency = ((_b = object.stopsAdjacency) === null || _b === void 0 ? void 0 : _b.map((e) => StopAdjacency.fromPartial(e))) || [];
17056
17087
  message.routesAdjacency = ((_c = object.routesAdjacency) === null || _c === void 0 ? void 0 : _c.map((e) => Route$2.fromPartial(e))) || [];
17057
17088
  message.serviceRoutes = ((_d = object.serviceRoutes) === null || _d === void 0 ? void 0 : _d.map((e) => ServiceRoute.fromPartial(e))) || [];
17089
+ message.tripContinuations = ((_e = object.tripContinuations) === null || _e === void 0 ? void 0 : _e.map((e) => TripContinuationEntry.fromPartial(e))) || [];
17058
17090
  return message;
17059
17091
  },
17060
17092
  };
@@ -17354,7 +17386,14 @@ let Route$1 = class Route {
17354
17386
  this.stopIndices = new Map();
17355
17387
  for (let i = 0; i < stops.length; i++) {
17356
17388
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17357
- this.stopIndices.set(stops[i], i);
17389
+ const stopId = stops[i];
17390
+ const existingIndices = this.stopIndices.get(stopId);
17391
+ if (existingIndices) {
17392
+ existingIndices.push(i);
17393
+ }
17394
+ else {
17395
+ this.stopIndices.set(stopId, [i]);
17396
+ }
17358
17397
  }
17359
17398
  }
17360
17399
  /**
@@ -17442,24 +17481,6 @@ let Route$1 = class Route {
17442
17481
  serviceRouteId: this.serviceRouteId,
17443
17482
  };
17444
17483
  }
17445
- /**
17446
- * Checks if stop A is before stop B in the route.
17447
- *
17448
- * @param stopA - The StopId of the first stop.
17449
- * @param stopB - The StopId of the second stop.
17450
- * @returns True if stop A is before stop B, false otherwise.
17451
- */
17452
- isBefore(stopA, stopB) {
17453
- const stopAIndex = this.stopIndices.get(stopA);
17454
- if (stopAIndex === undefined) {
17455
- throw new Error(`Stop index not found for ${stopA} in route ${this.serviceRouteId}`);
17456
- }
17457
- const stopBIndex = this.stopIndices.get(stopB);
17458
- if (stopBIndex === undefined) {
17459
- throw new Error(`Stop index not found for ${stopB} in route ${this.serviceRouteId}`);
17460
- }
17461
- return stopAIndex < stopBIndex;
17462
- }
17463
17484
  /**
17464
17485
  * Retrieves the number of stops in the route.
17465
17486
  *
@@ -17488,47 +17509,47 @@ let Route$1 = class Route {
17488
17509
  /**
17489
17510
  * Retrieves the arrival time at a specific stop for a given trip.
17490
17511
  *
17491
- * @param stopId - The identifier of the stop.
17512
+ * @param stopIndex - The index of the stop in the route.
17492
17513
  * @param tripIndex - The index of the trip.
17493
17514
  * @returns The arrival time at the specified stop and trip as a Time object.
17494
17515
  */
17495
- arrivalAt(stopId, tripIndex) {
17496
- const arrivalIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2;
17516
+ arrivalAt(stopIndex, tripIndex) {
17517
+ const arrivalIndex = (tripIndex * this.stops.length + stopIndex) * 2;
17497
17518
  const arrival = this.stopTimes[arrivalIndex];
17498
17519
  if (arrival === undefined) {
17499
- throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17520
+ throw new Error(`Arrival time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17500
17521
  }
17501
17522
  return Time.fromMinutes(arrival);
17502
17523
  }
17503
17524
  /**
17504
17525
  * Retrieves the departure time at a specific stop for a given trip.
17505
17526
  *
17506
- * @param stopId - The identifier of the stop.
17527
+ * @param stopIndex - The index of the stop in the route.
17507
17528
  * @param tripIndex - The index of the trip.
17508
17529
  * @returns The departure time at the specified stop and trip as a Time object.
17509
17530
  */
17510
- departureFrom(stopId, tripIndex) {
17511
- const departureIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2 + 1;
17531
+ departureFrom(stopIndex, tripIndex) {
17532
+ const departureIndex = (tripIndex * this.stops.length + stopIndex) * 2 + 1;
17512
17533
  const departure = this.stopTimes[departureIndex];
17513
17534
  if (departure === undefined) {
17514
- throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17535
+ throw new Error(`Departure time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17515
17536
  }
17516
17537
  return Time.fromMinutes(departure);
17517
17538
  }
17518
17539
  /**
17519
17540
  * Retrieves the pick-up type for a specific stop and trip.
17520
17541
  *
17521
- * @param stopId - The identifier of the stop.
17542
+ * @param stopIndex - The index of the stop in the route.
17522
17543
  * @param tripIndex - The index of the trip.
17523
17544
  * @returns The pick-up type at the specified stop and trip.
17524
17545
  */
17525
- pickUpTypeFrom(stopId, tripIndex) {
17526
- const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
17546
+ pickUpTypeFrom(stopIndex, tripIndex) {
17547
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
17527
17548
  const byteIndex = Math.floor(globalIndex / 2);
17528
17549
  const isSecondPair = globalIndex % 2 === 1;
17529
17550
  const byte = this.pickUpDropOffTypes[byteIndex];
17530
17551
  if (byte === undefined) {
17531
- throw new Error(`Pick up type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17552
+ throw new Error(`Pick up type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17532
17553
  }
17533
17554
  const pickUpValue = isSecondPair
17534
17555
  ? (byte >> 6) & 0x03 // Upper 2 bits for second pair
@@ -17538,17 +17559,17 @@ let Route$1 = class Route {
17538
17559
  /**
17539
17560
  * Retrieves the drop-off type for a specific stop and trip.
17540
17561
  *
17541
- * @param stopId - The identifier of the stop.
17562
+ * @param stopIndex - The index of the stop in the route.
17542
17563
  * @param tripIndex - The index of the trip.
17543
17564
  * @returns The drop-off type at the specified stop and trip.
17544
17565
  */
17545
- dropOffTypeAt(stopId, tripIndex) {
17546
- const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
17566
+ dropOffTypeAt(stopIndex, tripIndex) {
17567
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
17547
17568
  const byteIndex = Math.floor(globalIndex / 2);
17548
17569
  const isSecondPair = globalIndex % 2 === 1;
17549
17570
  const byte = this.pickUpDropOffTypes[byteIndex];
17550
17571
  if (byte === undefined) {
17551
- throw new Error(`Drop off type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17572
+ throw new Error(`Drop off type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17552
17573
  }
17553
17574
  const dropOffValue = isSecondPair
17554
17575
  ? (byte >> 4) & 0x03 // Bits 4-5 for second pair
@@ -17560,14 +17581,14 @@ let Route$1 = class Route {
17560
17581
  * optionally constrained by a latest trip index and a time before which the trip
17561
17582
  * should not depart.
17562
17583
  * *
17563
- * @param stopId - The StopId of the stop where the trip should be found.
17584
+ * @param stopIndex - The route index of the stop where the trip should be found.
17564
17585
  * @param [after=Time.origin()] - The earliest time after which the trip should depart.
17565
17586
  * If not provided, searches all available trips.
17566
17587
  * @param [beforeTrip] - (Optional) The index of the trip before which the search should be constrained.
17567
17588
  * If not provided, searches all available trips.
17568
17589
  * @returns The index of the earliest trip meeting the criteria, or undefined if no such trip is found.
17569
17590
  */
17570
- findEarliestTrip(stopId, after = Time.origin(), beforeTrip) {
17591
+ findEarliestTrip(stopIndex, after = Time.origin(), beforeTrip) {
17571
17592
  if (this.nbTrips <= 0)
17572
17593
  return undefined;
17573
17594
  let hi = this.nbTrips - 1;
@@ -17579,7 +17600,7 @@ let Route$1 = class Route {
17579
17600
  let lb = -1;
17580
17601
  while (lo <= hi) {
17581
17602
  const mid = (lo + hi) >>> 1;
17582
- const depMid = this.departureFrom(stopId, mid);
17603
+ const depMid = this.departureFrom(stopIndex, mid);
17583
17604
  if (depMid.isBefore(after)) {
17584
17605
  lo = mid + 1;
17585
17606
  }
@@ -17591,7 +17612,7 @@ let Route$1 = class Route {
17591
17612
  if (lb === -1)
17592
17613
  return undefined;
17593
17614
  for (let t = lb; t < (beforeTrip !== null && beforeTrip !== void 0 ? beforeTrip : this.nbTrips); t++) {
17594
- const pickup = this.pickUpTypeFrom(stopId, t);
17615
+ const pickup = this.pickUpTypeFrom(stopIndex, t);
17595
17616
  if (pickup !== 'NOT_AVAILABLE') {
17596
17617
  return t;
17597
17618
  }
@@ -17599,14 +17620,14 @@ let Route$1 = class Route {
17599
17620
  return undefined;
17600
17621
  }
17601
17622
  /**
17602
- * Retrieves the index of a stop within the route.
17623
+ * Retrieves the indices of a stop within the route.
17603
17624
  * @param stopId The StopId of the stop to locate in the route.
17604
- * @returns The index of the stop in the route.
17625
+ * @returns An array of indices where the stop appears in the route, or an empty array if the stop is not found.
17605
17626
  */
17606
- stopRouteIndex(stopId) {
17627
+ stopRouteIndices(stopId) {
17607
17628
  const stopIndex = this.stopIndices.get(stopId);
17608
17629
  if (stopIndex === undefined) {
17609
- throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
17630
+ return [];
17610
17631
  }
17611
17632
  return stopIndex;
17612
17633
  }
@@ -17624,6 +17645,51 @@ let Route$1 = class Route {
17624
17645
  }
17625
17646
  };
17626
17647
 
17648
+ // Each value uses 20 bits, allowing values from 0 to 1,048,575 (2^20 - 1)
17649
+ const VALUE_MASK = (BigInt(1) << BigInt(20)) - BigInt(1); // 0xFFFFF
17650
+ const MAX_VALUE = 1048575; // 2^20 - 1
17651
+ // Bit positions for each value in the 60-bit bigint
17652
+ const TRIP_INDEX_SHIFT = BigInt(0);
17653
+ const ROUTE_ID_SHIFT = BigInt(20);
17654
+ const STOP_INDEX_SHIFT = BigInt(40);
17655
+ /**
17656
+ * Validates that a value fits within 20 bits (0 to 1,048,575)
17657
+ * @param value - The value to validate
17658
+ * @param name - The name of the value for error reporting
17659
+ * @throws Error if the value is out of range
17660
+ */
17661
+ const validateValue = (value, name) => {
17662
+ if (value < 0 || value > MAX_VALUE) {
17663
+ throw new Error(`${name} must be between 0 and ${MAX_VALUE}, got ${value}`);
17664
+ }
17665
+ };
17666
+ /**
17667
+ * Encodes a stop index, route ID, and trip index into a single trip boarding ID.
17668
+ * @param stopIndex - The index of the stop within the route (0 to 1,048,575)
17669
+ * @param routeId - The route identifier (0 to 1,048,575)
17670
+ * @param tripIndex - The index of the trip within the route (0 to 1,048,575)
17671
+ * @returns The encoded trip ID as a bigint
17672
+ */
17673
+ const encode = (stopIndex, routeId, tripIndex) => {
17674
+ validateValue(stopIndex, 'stopIndex');
17675
+ validateValue(routeId, 'routeId');
17676
+ validateValue(tripIndex, 'tripIndex');
17677
+ return ((BigInt(stopIndex) << STOP_INDEX_SHIFT) |
17678
+ (BigInt(routeId) << ROUTE_ID_SHIFT) |
17679
+ (BigInt(tripIndex) << TRIP_INDEX_SHIFT));
17680
+ };
17681
+ /**
17682
+ * Decodes a trip boarding ID back into its constituent stop index, route ID, and trip index.
17683
+ * @param tripBoardingId - The encoded trip ID
17684
+ * @returns A tuple containing [stopIndex, routeId, tripIndex]
17685
+ */
17686
+ const decode = (tripBoardingId) => {
17687
+ const stopIndex = Number((tripBoardingId >> STOP_INDEX_SHIFT) & VALUE_MASK);
17688
+ const routeId = Number((tripBoardingId >> ROUTE_ID_SHIFT) & VALUE_MASK);
17689
+ const tripIndex = Number((tripBoardingId >> TRIP_INDEX_SHIFT) & VALUE_MASK);
17690
+ return [stopIndex, routeId, tripIndex];
17691
+ };
17692
+
17627
17693
  const isLittleEndian = (() => {
17628
17694
  const buffer = new ArrayBuffer(4);
17629
17695
  const view = new DataView(buffer);
@@ -17698,9 +17764,6 @@ const serializeStopsAdjacency = (stopsAdjacency) => {
17698
17764
  }))))
17699
17765
  : [],
17700
17766
  routes: value.routes,
17701
- tripContinuations: value.tripContinuations
17702
- ? serializeTripContinuations(value.tripContinuations)
17703
- : [],
17704
17767
  };
17705
17768
  });
17706
17769
  };
@@ -17746,10 +17809,6 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
17746
17809
  if (transfers.length > 0) {
17747
17810
  stopAdjacency.transfers = transfers;
17748
17811
  }
17749
- const deserializedTripContinuations = deserializeTripContinuations(value.tripContinuations);
17750
- if (deserializedTripContinuations.size > 0) {
17751
- stopAdjacency.tripContinuations = deserializedTripContinuations;
17752
- }
17753
17812
  result.push(stopAdjacency);
17754
17813
  }
17755
17814
  return result;
@@ -17856,11 +17915,14 @@ const serializeRouteType = (type) => {
17856
17915
  };
17857
17916
  const serializeTripContinuations = (tripContinuations) => {
17858
17917
  const result = [];
17859
- for (const [key, value] of tripContinuations.entries()) {
17918
+ for (const [tripBoardingId, boardings] of tripContinuations.entries()) {
17919
+ const [originStopIndex, originRouteId, originTripIndex] = decode(tripBoardingId);
17860
17920
  result.push({
17861
- key: key,
17862
- value: value.map((tripBoarding) => ({
17863
- hopOnStop: tripBoarding.hopOnStop,
17921
+ originStopIndex,
17922
+ originRouteId,
17923
+ originTripIndex,
17924
+ continuations: boardings.map((tripBoarding) => ({
17925
+ hopOnStopIndex: tripBoarding.hopOnStopIndex,
17864
17926
  routeId: tripBoarding.routeId,
17865
17927
  tripIndex: tripBoarding.tripIndex,
17866
17928
  })),
@@ -17873,28 +17935,17 @@ const deserializeTripContinuations = (protoTripContinuations) => {
17873
17935
  for (let i = 0; i < protoTripContinuations.length; i++) {
17874
17936
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17875
17937
  const entry = protoTripContinuations[i];
17876
- const tripBoardings = entry.value.map((protoTripBoarding) => ({
17877
- hopOnStop: protoTripBoarding.hopOnStop,
17938
+ const tripBoardingId = encode(entry.originStopIndex, entry.originRouteId, entry.originTripIndex);
17939
+ const tripBoardings = entry.continuations.map((protoTripBoarding) => ({
17940
+ hopOnStopIndex: protoTripBoarding.hopOnStopIndex,
17878
17941
  routeId: protoTripBoarding.routeId,
17879
17942
  tripIndex: protoTripBoarding.tripIndex,
17880
17943
  }));
17881
- result.set(entry.key, tripBoardings);
17944
+ result.set(tripBoardingId, tripBoardings);
17882
17945
  }
17883
17946
  return result;
17884
17947
  };
17885
17948
 
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
- };
17897
-
17898
17949
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
17899
17950
  const ALL_TRANSPORT_MODES = new Set([
17900
17951
  'TRAM',
@@ -17909,21 +17960,21 @@ const ALL_TRANSPORT_MODES = new Set([
17909
17960
  'MONORAIL',
17910
17961
  ]);
17911
17962
  const EMPTY_TRIP_CONTINUATIONS = [];
17912
- const CURRENT_VERSION = '0.0.8';
17963
+ const CURRENT_VERSION = '0.0.9';
17913
17964
  /**
17914
17965
  * The internal transit timetable format.
17915
17966
  */
17916
17967
  class Timetable {
17917
- constructor(stopsAdjacency, routesAdjacency, routes) {
17968
+ constructor(stopsAdjacency, routesAdjacency, routes, tripContinuations) {
17918
17969
  this.stopsAdjacency = stopsAdjacency;
17919
17970
  this.routesAdjacency = routesAdjacency;
17920
17971
  this.serviceRoutes = routes;
17972
+ this.tripContinuations = tripContinuations;
17921
17973
  this.activeStops = new Set();
17922
17974
  for (let i = 0; i < stopsAdjacency.length; i++) {
17923
17975
  const stop = stopsAdjacency[i];
17924
17976
  if (stop.routes.length > 0 ||
17925
- (stop.transfers && stop.transfers.length > 0) ||
17926
- (stop.tripContinuations && stop.tripContinuations.size > 0)) {
17977
+ (stop.transfers && stop.transfers.length > 0)) {
17927
17978
  this.activeStops.add(i);
17928
17979
  }
17929
17980
  }
@@ -17939,6 +17990,7 @@ class Timetable {
17939
17990
  stopsAdjacency: serializeStopsAdjacency(this.stopsAdjacency),
17940
17991
  routesAdjacency: serializeRoutesAdjacency(this.routesAdjacency),
17941
17992
  serviceRoutes: serializeServiceRoutesMap(this.serviceRoutes),
17993
+ tripContinuations: serializeTripContinuations(this.tripContinuations || new Map()),
17942
17994
  };
17943
17995
  const writer = new BinaryWriter();
17944
17996
  Timetable$1.encode(protoTimetable, writer);
@@ -17956,7 +18008,7 @@ class Timetable {
17956
18008
  if (protoTimetable.version !== CURRENT_VERSION) {
17957
18009
  throw new Error(`Unsupported timetable version ${protoTimetable.version}`);
17958
18010
  }
17959
- return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes));
18011
+ return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes), deserializeTripContinuations(protoTimetable.tripContinuations));
17960
18012
  }
17961
18013
  /**
17962
18014
  * Checks if the given stop is active on the timetable.
@@ -17995,18 +18047,18 @@ class Timetable {
17995
18047
  /**
17996
18048
  * Retrieves all trip continuation options available at the specified stop for a given trip.
17997
18049
  *
17998
- * @param stopId - The ID of the stop to get trip continuations for.
18050
+ * @param stopIndex - The index in the route of the stop to get trip continuations for.
18051
+ * @param routeId - The ID of the route to get continuations for.
17999
18052
  * @param tripIndex - The index of the trip to get continuations for.
18000
18053
  * @returns An array of trip continuation options available at the stop for the specified trip.
18001
18054
  */
18002
- getContinuousTrips(stopId, routeId, tripIndex) {
18055
+ getContinuousTrips(stopIndex, routeId, tripIndex) {
18003
18056
  var _a;
18004
- const stopAdjacency = this.stopsAdjacency[stopId];
18005
- if (!stopAdjacency) {
18006
- throw new Error(`Stop ID ${stopId} not found`);
18057
+ const tripContinuations = (_a = this.tripContinuations) === null || _a === void 0 ? void 0 : _a.get(encode(stopIndex, routeId, tripIndex));
18058
+ if (!tripContinuations) {
18059
+ return EMPTY_TRIP_CONTINUATIONS;
18007
18060
  }
18008
- return (((_a = stopAdjacency.tripContinuations) === null || _a === void 0 ? void 0 : _a.get(encode(routeId, tripIndex))) ||
18009
- EMPTY_TRIP_CONTINUATIONS);
18061
+ return tripContinuations;
18010
18062
  }
18011
18063
  /**
18012
18064
  * Retrieves the service route associated with the given route.
@@ -18046,12 +18098,12 @@ class Timetable {
18046
18098
  }
18047
18099
  /**
18048
18100
  * Finds routes that are reachable from a set of stop IDs.
18049
- * Also identifies the first stop available to hop on each route among
18101
+ * Also identifies the first stop index available to hop on each route among
18050
18102
  * the input stops.
18051
18103
  *
18052
18104
  * @param fromStops - The set of stop IDs to find reachable routes from.
18053
18105
  * @param transportModes - The set of transport modes to consider for reachable routes.
18054
- * @returns A map of reachable routes to the first stop available to hop on each route.
18106
+ * @returns A map of reachable routes to the first stop index available to hop on each route.
18055
18107
  */
18056
18108
  findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
18057
18109
  const reachableRoutes = new Map();
@@ -18064,15 +18116,17 @@ class Timetable {
18064
18116
  });
18065
18117
  for (let j = 0; j < validRoutes.length; j++) {
18066
18118
  const route = validRoutes[j];
18067
- const hopOnStop = reachableRoutes.get(route);
18068
- if (hopOnStop) {
18069
- if (route.isBefore(originStop, hopOnStop)) {
18119
+ const originStopIndices = route.stopRouteIndices(originStop);
18120
+ const originStopIndex = originStopIndices[0];
18121
+ const existingHopOnStopIndex = reachableRoutes.get(route);
18122
+ if (existingHopOnStopIndex !== undefined) {
18123
+ if (originStopIndex < existingHopOnStopIndex) {
18070
18124
  // if the current stop is before the existing hop on stop, replace it
18071
- reachableRoutes.set(route, originStop);
18125
+ reachableRoutes.set(route, originStopIndex);
18072
18126
  }
18073
18127
  }
18074
18128
  else {
18075
- reachableRoutes.set(route, originStop);
18129
+ reachableRoutes.set(route, originStopIndex);
18076
18130
  }
18077
18131
  }
18078
18132
  }
@@ -20292,7 +20346,7 @@ const parseGtfsLocationType = (gtfsLocationType) => {
20292
20346
  const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
20293
20347
  var _a, e_1, _b, _c;
20294
20348
  const transfers = new Map();
20295
- const tripContinuations = new Map();
20349
+ const tripContinuations = [];
20296
20350
  try {
20297
20351
  for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
20298
20352
  'transfer_type',
@@ -20310,10 +20364,12 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
20310
20364
  console.warn(`Missing transfer origin or destination stop.`);
20311
20365
  continue;
20312
20366
  }
20313
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20314
20367
  const fromStop = stopsMap.get(transferEntry.from_stop_id);
20315
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20316
20368
  const toStop = stopsMap.get(transferEntry.to_stop_id);
20369
+ if (!fromStop || !toStop) {
20370
+ console.warn(`Transfer references non-existent stop(s): from_stop_id=${transferEntry.from_stop_id}, to_stop_id=${transferEntry.to_stop_id}`);
20371
+ continue;
20372
+ }
20317
20373
  if (transferEntry.transfer_type === 4) {
20318
20374
  if (transferEntry.from_trip_id === undefined ||
20319
20375
  transferEntry.from_trip_id === '' ||
@@ -20323,17 +20379,12 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
20323
20379
  continue;
20324
20380
  }
20325
20381
  const tripBoardingEntry = {
20382
+ fromStop: fromStop.id,
20326
20383
  fromTrip: transferEntry.from_trip_id,
20384
+ toStop: toStop.id,
20327
20385
  toTrip: transferEntry.to_trip_id,
20328
- hopOnStop: toStop.id,
20329
20386
  };
20330
- const existingBoardings = tripContinuations.get(fromStop.id);
20331
- if (existingBoardings) {
20332
- existingBoardings.push(tripBoardingEntry);
20333
- }
20334
- else {
20335
- tripContinuations.set(fromStop.id, [tripBoardingEntry]);
20336
- }
20387
+ tripContinuations.push(tripBoardingEntry);
20337
20388
  continue;
20338
20389
  }
20339
20390
  if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
@@ -20348,7 +20399,7 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
20348
20399
  transferEntry.min_transfer_time === undefined) {
20349
20400
  console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
20350
20401
  }
20351
- const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
20402
+ const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time !== undefined && {
20352
20403
  minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
20353
20404
  }));
20354
20405
  const fromStopTransfers = transfers.get(fromStop.id) || [];
@@ -20368,6 +20419,91 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
20368
20419
  tripContinuations,
20369
20420
  };
20370
20421
  });
20422
+ /**
20423
+ * Disambiguates stops involved in a transfer.
20424
+ *
20425
+ * The GTFS specification only refers to a stopId in the trip-to-trip transfers and not the
20426
+ * specific stop index in the route. For routes that have multiple stops with the same stopId,
20427
+ * we need to determine which are the from / to stop indices in respective routes.
20428
+ * We do so by picking the stop indices leading to the most coherent transfer.
20429
+ * (we pick the closest from stop index happening after the to stop index).
20430
+ */
20431
+ const disambiguateTransferStopsIndices = (fromStop, fromRoute, fromTripIndex, toStop, toRoute, toTripIndex) => {
20432
+ const fromStopIndices = fromRoute.stopRouteIndices(fromStop);
20433
+ const toStopIndices = toRoute.stopRouteIndices(toStop);
20434
+ let bestFromStopIndex;
20435
+ let bestToStopIndex;
20436
+ let bestTimeDifference = Infinity;
20437
+ for (const originStopIndex of fromStopIndices) {
20438
+ const fromArrivalTime = fromRoute.arrivalAt(originStopIndex, fromTripIndex);
20439
+ for (const toStopIndex of toStopIndices) {
20440
+ const toDepartureTime = toRoute.departureFrom(toStopIndex, toTripIndex);
20441
+ if (toDepartureTime.isAfter(fromArrivalTime)) {
20442
+ const timeDifference = toDepartureTime.toMinutes() - fromArrivalTime.toMinutes();
20443
+ if (timeDifference < bestTimeDifference) {
20444
+ bestTimeDifference = timeDifference;
20445
+ bestFromStopIndex = originStopIndex;
20446
+ bestToStopIndex = toStopIndex;
20447
+ }
20448
+ }
20449
+ }
20450
+ }
20451
+ if (bestFromStopIndex !== undefined && bestToStopIndex !== undefined) {
20452
+ return {
20453
+ fromStopIndex: bestFromStopIndex,
20454
+ toStopIndex: bestToStopIndex,
20455
+ };
20456
+ }
20457
+ return undefined;
20458
+ };
20459
+ /**
20460
+ * Builds trip continuations map from GTFS trip continuation data.
20461
+ *
20462
+ * This function processes GTFS in-seat transfer data and creates a mapping
20463
+ * from trip boarding IDs to continuation boarding information. It disambiguates
20464
+ * stop indices when routes have multiple stops with the same ID by finding
20465
+ * the most coherent transfer timing.
20466
+ *
20467
+ * @param tripsMapping Mapping from GTFS trip IDs to internal trip representations
20468
+ * @param tripContinuations Array of GTFS trip continuation data from transfers.txt
20469
+ * @param timetable The timetable containing route and timing information
20470
+ * @param activeStopIds Set of stop IDs that are active/enabled in the system
20471
+ * @returns A map from trip boarding IDs to arrays of continuation boarding options
20472
+ */
20473
+ const buildTripContinuations = (tripsMapping, tripContinuations, timetable, activeStopIds) => {
20474
+ const continuations = new Map();
20475
+ for (const gtfsContinuation of tripContinuations) {
20476
+ if (!activeStopIds.has(gtfsContinuation.fromStop) ||
20477
+ !activeStopIds.has(gtfsContinuation.toStop)) {
20478
+ continue;
20479
+ }
20480
+ const fromTripMapping = tripsMapping.get(gtfsContinuation.fromTrip);
20481
+ const toTripMapping = tripsMapping.get(gtfsContinuation.toTrip);
20482
+ if (!fromTripMapping || !toTripMapping) {
20483
+ continue;
20484
+ }
20485
+ const fromRoute = timetable.getRoute(fromTripMapping.routeId);
20486
+ const toRoute = timetable.getRoute(toTripMapping.routeId);
20487
+ if (!fromRoute || !toRoute) {
20488
+ continue;
20489
+ }
20490
+ const bestStopIndices = disambiguateTransferStopsIndices(gtfsContinuation.fromStop, fromRoute, fromTripMapping.tripRouteIndex, gtfsContinuation.toStop, toRoute, toTripMapping.tripRouteIndex);
20491
+ if (!bestStopIndices) {
20492
+ // No valid continuation found
20493
+ continue;
20494
+ }
20495
+ const tripBoardingId = encode(bestStopIndices.fromStopIndex, fromTripMapping.routeId, fromTripMapping.tripRouteIndex);
20496
+ const continuationBoarding = {
20497
+ hopOnStopIndex: bestStopIndices.toStopIndex,
20498
+ routeId: toTripMapping.routeId,
20499
+ tripIndex: toTripMapping.tripRouteIndex,
20500
+ };
20501
+ const existingContinuations = continuations.get(tripBoardingId) || [];
20502
+ existingContinuations.push(continuationBoarding);
20503
+ continuations.set(tripBoardingId, existingContinuations);
20504
+ }
20505
+ return continuations;
20506
+ };
20371
20507
  const parseGtfsTransferType = (gtfsTransferType) => {
20372
20508
  switch (gtfsTransferType) {
20373
20509
  case 0:
@@ -20504,7 +20640,7 @@ const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) => __awaiter(void
20504
20640
  }
20505
20641
  return trips;
20506
20642
  });
20507
- const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, transfersMap, tripContinuationsMap, nbStops, activeStops) => {
20643
+ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
20508
20644
  const stopsAdjacency = new Array(nbStops);
20509
20645
  for (let i = 0; i < nbStops; i++) {
20510
20646
  stopsAdjacency[i] = {
@@ -20544,40 +20680,6 @@ const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, trans
20544
20680
  }
20545
20681
  }
20546
20682
  }
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
- }
20581
20683
  return stopsAdjacency;
20582
20684
  };
20583
20685
  /**
@@ -20804,16 +20906,16 @@ class GtfsParser {
20804
20906
  const tripsEnd = performance.now();
20805
20907
  log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
20806
20908
  let transfers = new Map();
20807
- let tripContinuations = new Map();
20909
+ let tripContinuationsMap = [];
20808
20910
  if (entries[TRANSFERS_FILE]) {
20809
20911
  log.info(`Parsing ${TRANSFERS_FILE}`);
20810
20912
  const transfersStart = performance.now();
20811
20913
  const transfersStream = yield zip.stream(TRANSFERS_FILE);
20812
20914
  const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = yield parseTransfers(transfersStream, parsedStops);
20813
20915
  transfers = parsedTransfers;
20814
- tripContinuations = parsedTripContinuations;
20916
+ tripContinuationsMap = parsedTripContinuations;
20815
20917
  const transfersEnd = performance.now();
20816
- log.info(`${transfers.size} valid transfers and ${tripContinuations.size} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
20918
+ log.info(`${transfers.size} valid transfers and ${tripContinuationsMap.length} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
20817
20919
  }
20818
20920
  log.info(`Parsing ${STOP_TIMES_FILE}`);
20819
20921
  const stopTimesStart = performance.now();
@@ -20824,13 +20926,19 @@ class GtfsParser {
20824
20926
  log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
20825
20927
  log.info('Building stops adjacency structure');
20826
20928
  const stopsAdjacencyStart = performance.now();
20827
- const stopsAdjacency = buildStopsAdjacencyStructure(tripsMapping, serviceRoutes, routes, transfers, tripContinuations, parsedStops.size, activeStopIds);
20929
+ const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
20828
20930
  const stopsAdjacencyEnd = performance.now();
20829
20931
  log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
20830
20932
  yield zip.close();
20933
+ // temporary timetable for building continuations
20831
20934
  const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
20935
+ log.info('Building in-seat trip continuations');
20936
+ const tripContinuationsStart = performance.now();
20937
+ const tripContinuations = buildTripContinuations(tripsMapping, tripContinuationsMap, timetable, activeStopIds);
20938
+ const tripContinuationsEnd = performance.now();
20939
+ log.info(`${tripContinuations.size} in-seat trip continuations origins created. (${(tripContinuationsEnd - tripContinuationsStart).toFixed(2)}ms)`);
20832
20940
  log.info('Parsing complete.');
20833
- return timetable;
20941
+ return new Timetable(stopsAdjacency, routes, serviceRoutes, tripContinuations);
20834
20942
  });
20835
20943
  }
20836
20944
  /**
@@ -21430,9 +21538,9 @@ class Result {
21430
21538
  const lastRoute = this.timetable.getRoute(lastEdge.routeId);
21431
21539
  return {
21432
21540
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21433
- from: this.stopsIndex.findStopById(firstEdge.from),
21541
+ from: this.stopsIndex.findStopById(firstRoute.stopId(firstEdge.from)),
21434
21542
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21435
- to: this.stopsIndex.findStopById(lastEdge.to),
21543
+ to: this.stopsIndex.findStopById(lastRoute.stopId(lastEdge.to)),
21436
21544
  // The route info comes from the first boarded route in case on continuous trips
21437
21545
  route: this.timetable.getServiceRouteInfo(firstRoute),
21438
21546
  departureTime: firstRoute.departureFrom(firstEdge.from, firstEdge.tripIndex),
@@ -21533,8 +21641,8 @@ class Router {
21533
21641
  const reachableRoutes = this.timetable.findReachableRoutes(markedStops, query.options.transportModes);
21534
21642
  markedStops.clear();
21535
21643
  // 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);
21644
+ for (const [route, hopOnStopIndex] of reachableRoutes) {
21645
+ const newlyMarkedStops = this.scanRoute(route, hopOnStopIndex, round, routingState);
21538
21646
  for (const newStop of newlyMarkedStops) {
21539
21647
  markedStops.add(newStop);
21540
21648
  }
@@ -21545,7 +21653,7 @@ class Router {
21545
21653
  const stopsFromContinuations = new Set();
21546
21654
  for (const continuation of continuations) {
21547
21655
  const route = this.timetable.getRoute(continuation.routeId);
21548
- const routeScanResults = this.scanRoute(route, continuation.hopOnStop, round, routingState, continuation);
21656
+ const routeScanResults = this.scanRoute(route, continuation.hopOnStopIndex, round, routingState, continuation);
21549
21657
  for (const newStop of routeScanResults) {
21550
21658
  stopsFromContinuations.add(newStop);
21551
21659
  }
@@ -21576,12 +21684,12 @@ class Router {
21576
21684
  const arrival = edgesAtCurrentRound.get(stopId);
21577
21685
  if (!arrival || !('routeId' in arrival))
21578
21686
  continue;
21579
- const continuousTrips = this.timetable.getContinuousTrips(stopId, arrival.routeId, arrival.tripIndex);
21687
+ const continuousTrips = this.timetable.getContinuousTrips(arrival.to, arrival.routeId, arrival.tripIndex);
21580
21688
  for (let i = 0; i < continuousTrips.length; i++) {
21581
21689
  const trip = continuousTrips[i];
21582
21690
  continuations.push({
21583
21691
  routeId: trip.routeId,
21584
- hopOnStop: trip.hopOnStop,
21692
+ hopOnStopIndex: trip.hopOnStopIndex,
21585
21693
  tripIndex: trip.tripIndex,
21586
21694
  previousEdge: arrival,
21587
21695
  });
@@ -21636,32 +21744,31 @@ class Router {
21636
21744
  * are available if no given trip is provided as a parameter.
21637
21745
  *
21638
21746
  * @param route The route to scan for possible trips
21639
- * @param hopOnStop The stop ID where passengers can board the route
21747
+ * @param hopOnStopIndex The stop index where passengers can board the route
21640
21748
  * @param round The current round number in the RAPTOR algorithm
21641
21749
  * @param routingState The current routing state containing arrival times and marked stops
21642
21750
  */
21643
- scanRoute(route, hopOnStop, round, routingState, tripContinuation) {
21751
+ scanRoute(route, hopOnStopIndex, round, routingState, tripContinuation) {
21644
21752
  var _a, _b, _c;
21645
21753
  const newlyMarkedStops = new Set();
21646
21754
  let activeTrip = tripContinuation
21647
21755
  ? {
21648
21756
  routeId: route.id,
21649
- hopOnStop,
21757
+ hopOnStopIndex,
21650
21758
  tripIndex: tripContinuation.tripIndex,
21651
21759
  }
21652
21760
  : undefined;
21653
21761
  const edgesAtCurrentRound = routingState.graph[round];
21654
21762
  const edgesAtPreviousRound = routingState.graph[round - 1];
21655
- const startIndex = route.stopRouteIndex(hopOnStop);
21656
21763
  // Compute target pruning criteria only once per route
21657
21764
  const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState.earliestArrivals, routingState.destinations);
21658
- for (let j = startIndex; j < route.getNbStops(); j++) {
21659
- const currentStop = route.stops[j];
21765
+ for (let currentStopIndex = hopOnStopIndex; currentStopIndex < route.getNbStops(); currentStopIndex++) {
21766
+ const currentStop = route.stops[currentStopIndex];
21660
21767
  // If we're currently on a trip,
21661
21768
  // check if arrival at the stop improves the earliest arrival time
21662
21769
  if (activeTrip !== undefined) {
21663
- const arrivalTime = route.arrivalAt(currentStop, activeTrip.tripIndex);
21664
- const dropOffType = route.dropOffTypeAt(currentStop, activeTrip.tripIndex);
21770
+ const arrivalTime = route.arrivalAt(currentStopIndex, activeTrip.tripIndex);
21771
+ const dropOffType = route.dropOffTypeAt(currentStopIndex, activeTrip.tripIndex);
21665
21772
  const earliestArrivalAtCurrentStop = (_b = (_a = routingState.earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21666
21773
  if (dropOffType !== 'NOT_AVAILABLE' &&
21667
21774
  arrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
@@ -21670,8 +21777,8 @@ class Router {
21670
21777
  arrival: arrivalTime,
21671
21778
  routeId: route.id,
21672
21779
  tripIndex: activeTrip.tripIndex,
21673
- from: activeTrip.hopOnStop,
21674
- to: currentStop,
21780
+ from: activeTrip.hopOnStopIndex,
21781
+ to: currentStopIndex,
21675
21782
  };
21676
21783
  if (tripContinuation) {
21677
21784
  // In case of continuous trip, we set a pointer to the previous edge
@@ -21698,14 +21805,14 @@ class Router {
21698
21805
  // (or later at route reconstruction time)
21699
21806
  if (earliestArrivalOnPreviousRound !== undefined &&
21700
21807
  (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);
21808
+ earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStopIndex, activeTrip.tripIndex)) ||
21809
+ earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStopIndex, activeTrip.tripIndex)))) {
21810
+ const earliestTrip = route.findEarliestTrip(currentStopIndex, earliestArrivalOnPreviousRound, activeTrip === null || activeTrip === void 0 ? void 0 : activeTrip.tripIndex);
21704
21811
  if (earliestTrip !== undefined) {
21705
21812
  activeTrip = {
21706
21813
  routeId: route.id,
21707
21814
  tripIndex: earliestTrip,
21708
- hopOnStop: currentStop,
21815
+ hopOnStopIndex: currentStopIndex,
21709
21816
  };
21710
21817
  }
21711
21818
  }
@@ -21980,7 +22087,6 @@ const startRepl = (stopsPath, timetablePath) => {
21980
22087
  if (bestRoute) {
21981
22088
  console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
21982
22089
  console.log(bestRoute.toString());
21983
- console.log(bestRoute.asJson());
21984
22090
  }
21985
22091
  else {
21986
22092
  console.log('No route found');
@@ -22087,7 +22193,6 @@ const startRepl = (stopsPath, timetablePath) => {
22087
22193
  return;
22088
22194
  }
22089
22195
  const inspectRoute = (routeIdStr) => {
22090
- var _a, _b, _c;
22091
22196
  const routeId = parseInt(routeIdStr.trim());
22092
22197
  if (isNaN(routeId)) {
22093
22198
  console.log('Usage: .inspect route <routeId>');
@@ -22109,29 +22214,30 @@ const startRepl = (stopsPath, timetablePath) => {
22109
22214
  console.log('\n--- Stops ---');
22110
22215
  for (let i = 0; i < route.stops.length; i++) {
22111
22216
  const stopId = route.stopId(i);
22217
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22112
22218
  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'})`);
22219
+ const platform = stop.platform ? ` (Pl. ${stop.platform})` : '';
22220
+ console.log(`${i + 1}. ${stop.name}${platform} (${stopId}, ${stop.sourceStopId})`);
22115
22221
  }
22116
22222
  console.log('\n--- Trips ---');
22117
22223
  for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
22118
22224
  console.log(`\nTrip ${tripIndex}:`);
22119
22225
  for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
22120
22226
  const stopId = route.stopId(stopIndex);
22227
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22121
22228
  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);
22229
+ const departure = route.departureFrom(stopIndex, tripIndex);
22230
+ const arrival = route.arrivalAt(stopIndex, tripIndex);
22231
+ const pickupType = route.pickUpTypeFrom(stopIndex, tripIndex);
22232
+ const dropOffType = route.dropOffTypeAt(stopIndex, tripIndex);
22126
22233
  const pickupStr = formatPickupDropoffType(pickupType);
22127
22234
  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})`);
22235
+ console.log(` ${stopIndex + 1}. ${stop.name}: arr ${arrival.toString()} (${pickupStr}) → dep ${departure.toString()} (${dropOffStr})`);
22129
22236
  }
22130
22237
  }
22131
22238
  console.log();
22132
22239
  };
22133
22240
  const inspectStop = (stopIdStr) => {
22134
- var _a, _b, _c;
22135
22241
  let stop;
22136
22242
  const stopBySourceId = stopsIndex.findStopBySourceStopId(stopIdStr);
22137
22243
  if (stopBySourceId !== undefined) {
@@ -22199,48 +22305,38 @@ const startRepl = (stopsPath, timetablePath) => {
22199
22305
  });
22200
22306
  }
22201
22307
  let totalContinuations = 0;
22202
- const continuationsByTrip = new Map();
22308
+ console.log('\n--- Trip Continuations ---');
22203
22309
  routes.forEach((route) => {
22310
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
22311
+ const stopIndices = route.stopRouteIndices(stop.id);
22204
22312
  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
- });
22313
+ for (const stopIndex of stopIndices) {
22314
+ const continuations = timetable.getContinuousTrips(stopIndex, route.id, tripIndex);
22315
+ for (const continuation of continuations) {
22316
+ totalContinuations++;
22317
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22318
+ const destRoute = timetable.getRoute(continuation.routeId);
22319
+ const destStopId = destRoute.stopId(continuation.hopOnStopIndex);
22320
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22321
+ const destStop = stopsIndex.findStopById(destStopId);
22322
+ const destPlatform = destStop.platform
22323
+ ? ` (Pl. ${destStop.platform})`
22324
+ : '';
22325
+ const destServiceRouteInfo = timetable.getServiceRouteInfo(destRoute);
22326
+ const originTime = route.departureFrom(stopIndex, tripIndex);
22327
+ const continuationTime = destRoute.departureFrom(continuation.hopOnStopIndex, continuation.tripIndex);
22328
+ console.log(`${totalContinuations}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
22329
+ `Route ${continuation.routeId} (${destServiceRouteInfo.name}) Trip ${continuation.tripIndex} at ${continuationTime.toString()} ` +
22330
+ `at ${destStop.name}${destPlatform} (${destStopId}, ${destStop.sourceStopId})`);
22331
+ }
22214
22332
  }
22215
22333
  }
22216
22334
  });
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
- }
22335
+ if (totalContinuations === 0) {
22336
+ console.log('No trip continuations found.');
22337
+ }
22338
+ else {
22339
+ console.log(`\nTotal trip continuations: ${totalContinuations}`);
22244
22340
  }
22245
22341
  console.log();
22246
22342
  };