minotor 7.0.2 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.cspell.json +11 -1
  2. package/CHANGELOG.md +8 -3
  3. package/README.md +26 -24
  4. package/dist/cli.mjs +1786 -791
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +29 -5
  7. package/dist/gtfs/trips.d.ts +10 -5
  8. package/dist/parser.cjs.js +972 -525
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +972 -525
  11. package/dist/parser.esm.js.map +1 -1
  12. package/dist/router.cjs.js +1 -1
  13. package/dist/router.cjs.js.map +1 -1
  14. package/dist/router.d.ts +2 -2
  15. package/dist/router.esm.js +1 -1
  16. package/dist/router.esm.js.map +1 -1
  17. package/dist/router.umd.js +1 -1
  18. package/dist/router.umd.js.map +1 -1
  19. package/dist/routing/__tests__/plotter.test.d.ts +1 -0
  20. package/dist/routing/plotter.d.ts +42 -3
  21. package/dist/routing/result.d.ts +23 -7
  22. package/dist/routing/route.d.ts +2 -0
  23. package/dist/routing/router.d.ts +78 -19
  24. package/dist/timetable/__tests__/tripBoardingId.test.d.ts +1 -0
  25. package/dist/timetable/io.d.ts +4 -2
  26. package/dist/timetable/proto/timetable.d.ts +15 -1
  27. package/dist/timetable/route.d.ts +48 -23
  28. package/dist/timetable/timetable.d.ts +24 -7
  29. package/dist/timetable/tripBoardingId.d.ts +34 -0
  30. package/package.json +1 -1
  31. package/src/__e2e__/router.test.ts +114 -105
  32. package/src/__e2e__/timetable/stops.bin +2 -2
  33. package/src/__e2e__/timetable/timetable.bin +2 -2
  34. package/src/cli/repl.ts +245 -1
  35. package/src/gtfs/__tests__/parser.test.ts +19 -4
  36. package/src/gtfs/__tests__/transfers.test.ts +773 -37
  37. package/src/gtfs/__tests__/trips.test.ts +308 -27
  38. package/src/gtfs/parser.ts +36 -6
  39. package/src/gtfs/transfers.ts +193 -19
  40. package/src/gtfs/trips.ts +58 -21
  41. package/src/router.ts +2 -2
  42. package/src/routing/__tests__/plotter.test.ts +230 -0
  43. package/src/routing/__tests__/result.test.ts +486 -125
  44. package/src/routing/__tests__/route.test.ts +7 -3
  45. package/src/routing/__tests__/router.test.ts +380 -172
  46. package/src/routing/plotter.ts +279 -48
  47. package/src/routing/result.ts +114 -34
  48. package/src/routing/route.ts +0 -3
  49. package/src/routing/router.ts +344 -211
  50. package/src/timetable/__tests__/io.test.ts +34 -1
  51. package/src/timetable/__tests__/route.test.ts +74 -81
  52. package/src/timetable/__tests__/timetable.test.ts +232 -61
  53. package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
  54. package/src/timetable/io.ts +72 -10
  55. package/src/timetable/proto/timetable.proto +16 -2
  56. package/src/timetable/proto/timetable.ts +256 -22
  57. package/src/timetable/route.ts +174 -58
  58. package/src/timetable/timetable.ts +66 -16
  59. package/src/timetable/tripBoardingId.ts +94 -0
  60. package/tsconfig.json +2 -2
package/dist/cli.mjs CHANGED
@@ -13,58 +13,6 @@ import require$$5, { Transform } from 'stream';
13
13
  import { performance as performance$1 } from 'perf_hooks';
14
14
  import repl from 'node:repl';
15
15
 
16
- /******************************************************************************
17
- Copyright (c) Microsoft Corporation.
18
-
19
- Permission to use, copy, modify, and/or distribute this software for any
20
- purpose with or without fee is hereby granted.
21
-
22
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
23
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
24
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
25
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
26
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
27
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
28
- PERFORMANCE OF THIS SOFTWARE.
29
- ***************************************************************************** */
30
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
31
-
32
-
33
- function __awaiter(thisArg, _arguments, P, generator) {
34
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
35
- return new (P || (P = Promise))(function (resolve, reject) {
36
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
37
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
38
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
39
- step((generator = generator.apply(thisArg, _arguments || [])).next());
40
- });
41
- }
42
-
43
- function __values(o) {
44
- var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
45
- if (m) return m.call(o);
46
- if (o && typeof o.length === "number") return {
47
- next: function () {
48
- if (o && i >= o.length) o = void 0;
49
- return { value: o && o[i++], done: !o };
50
- }
51
- };
52
- throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
53
- }
54
-
55
- function __asyncValues(o) {
56
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
57
- var m = o[Symbol.asyncIterator], i;
58
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
59
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
60
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
61
- }
62
-
63
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
64
- var e = new Error(message);
65
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
66
- };
67
-
68
16
  function getDefaultExportFromCjs (x) {
69
17
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
70
18
  }
@@ -15864,14 +15812,13 @@ const Stop = {
15864
15812
  sourceStopId: isSet$1(object.sourceStopId) ? globalThis.String(object.sourceStopId) : "",
15865
15813
  lat: isSet$1(object.lat) ? globalThis.Number(object.lat) : undefined,
15866
15814
  lon: isSet$1(object.lon) ? globalThis.Number(object.lon) : undefined,
15867
- children: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.children) ? object.children.map((e) => globalThis.Number(e)) : [],
15815
+ children: globalThis.Array.isArray(object?.children) ? object.children.map((e) => globalThis.Number(e)) : [],
15868
15816
  parent: isSet$1(object.parent) ? globalThis.Number(object.parent) : undefined,
15869
15817
  locationType: isSet$1(object.locationType) ? locationTypeFromJSON(object.locationType) : 0,
15870
15818
  platform: isSet$1(object.platform) ? globalThis.String(object.platform) : undefined,
15871
15819
  };
15872
15820
  },
15873
15821
  toJSON(message) {
15874
- var _a;
15875
15822
  const obj = {};
15876
15823
  if (message.name !== "") {
15877
15824
  obj.name = message.name;
@@ -15885,7 +15832,7 @@ const Stop = {
15885
15832
  if (message.lon !== undefined) {
15886
15833
  obj.lon = message.lon;
15887
15834
  }
15888
- if ((_a = message.children) === null || _a === void 0 ? void 0 : _a.length) {
15835
+ if (message.children?.length) {
15889
15836
  obj.children = message.children.map((e) => Math.round(e));
15890
15837
  }
15891
15838
  if (message.parent !== undefined) {
@@ -15900,19 +15847,18 @@ const Stop = {
15900
15847
  return obj;
15901
15848
  },
15902
15849
  create(base) {
15903
- return Stop.fromPartial(base !== null && base !== void 0 ? base : {});
15850
+ return Stop.fromPartial(base ?? {});
15904
15851
  },
15905
15852
  fromPartial(object) {
15906
- var _a, _b, _c, _d, _e, _f, _g, _h;
15907
15853
  const message = createBaseStop();
15908
- message.name = (_a = object.name) !== null && _a !== void 0 ? _a : "";
15909
- message.sourceStopId = (_b = object.sourceStopId) !== null && _b !== void 0 ? _b : "";
15910
- message.lat = (_c = object.lat) !== null && _c !== void 0 ? _c : undefined;
15911
- message.lon = (_d = object.lon) !== null && _d !== void 0 ? _d : undefined;
15912
- message.children = ((_e = object.children) === null || _e === void 0 ? void 0 : _e.map((e) => e)) || [];
15913
- message.parent = (_f = object.parent) !== null && _f !== void 0 ? _f : undefined;
15914
- message.locationType = (_g = object.locationType) !== null && _g !== void 0 ? _g : 0;
15915
- message.platform = (_h = object.platform) !== null && _h !== void 0 ? _h : undefined;
15854
+ message.name = object.name ?? "";
15855
+ message.sourceStopId = object.sourceStopId ?? "";
15856
+ message.lat = object.lat ?? undefined;
15857
+ message.lon = object.lon ?? undefined;
15858
+ message.children = object.children?.map((e) => e) || [];
15859
+ message.parent = object.parent ?? undefined;
15860
+ message.locationType = object.locationType ?? 0;
15861
+ message.platform = object.platform ?? undefined;
15916
15862
  return message;
15917
15863
  },
15918
15864
  };
@@ -15961,28 +15907,26 @@ const StopsMap = {
15961
15907
  fromJSON(object) {
15962
15908
  return {
15963
15909
  version: isSet$1(object.version) ? globalThis.String(object.version) : "",
15964
- stops: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.stops) ? object.stops.map((e) => Stop.fromJSON(e)) : [],
15910
+ stops: globalThis.Array.isArray(object?.stops) ? object.stops.map((e) => Stop.fromJSON(e)) : [],
15965
15911
  };
15966
15912
  },
15967
15913
  toJSON(message) {
15968
- var _a;
15969
15914
  const obj = {};
15970
15915
  if (message.version !== "") {
15971
15916
  obj.version = message.version;
15972
15917
  }
15973
- if ((_a = message.stops) === null || _a === void 0 ? void 0 : _a.length) {
15918
+ if (message.stops?.length) {
15974
15919
  obj.stops = message.stops.map((e) => Stop.toJSON(e));
15975
15920
  }
15976
15921
  return obj;
15977
15922
  },
15978
15923
  create(base) {
15979
- return StopsMap.fromPartial(base !== null && base !== void 0 ? base : {});
15924
+ return StopsMap.fromPartial(base ?? {});
15980
15925
  },
15981
15926
  fromPartial(object) {
15982
- var _a, _b;
15983
15927
  const message = createBaseStopsMap();
15984
- message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
15985
- message.stops = ((_b = object.stops) === null || _b === void 0 ? void 0 : _b.map((e) => Stop.fromPartial(e))) || [];
15928
+ message.version = object.version ?? "";
15929
+ message.stops = object.stops?.map((e) => Stop.fromPartial(e)) || [];
15986
15930
  return message;
15987
15931
  },
15988
15932
  };
@@ -16066,8 +16010,12 @@ const serializeLocationType = (locationType) => {
16066
16010
  * to efficiently find stops based on user queries.
16067
16011
  */
16068
16012
  class StopsIndex {
16013
+ stops;
16014
+ sourceStopsMap;
16015
+ textIndex;
16016
+ geoIndex;
16017
+ stopPoints;
16069
16018
  constructor(stops) {
16070
- var _a;
16071
16019
  this.stops = stops;
16072
16020
  this.sourceStopsMap = new Map();
16073
16021
  const stopsSet = new Map();
@@ -16076,7 +16024,7 @@ class StopsIndex {
16076
16024
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16077
16025
  const stop = stops[id];
16078
16026
  this.sourceStopsMap.set(stop.sourceStopId, id);
16079
- const effectiveStopId = (_a = stop.parent) !== null && _a !== void 0 ? _a : id;
16027
+ const effectiveStopId = stop.parent ?? id;
16080
16028
  if (!stopsSet.has(effectiveStopId)) {
16081
16029
  stopsSet.set(effectiveStopId, {
16082
16030
  id: effectiveStopId,
@@ -16191,7 +16139,6 @@ class StopsIndex {
16191
16139
  * Find ids of all sibling stops.
16192
16140
  */
16193
16141
  equivalentStops(sourceId) {
16194
- var _a, _b;
16195
16142
  const id = this.sourceStopsMap.get(sourceId);
16196
16143
  if (id === undefined) {
16197
16144
  return [];
@@ -16201,13 +16148,14 @@ class StopsIndex {
16201
16148
  return [];
16202
16149
  }
16203
16150
  const equivalentStops = stop.parent
16204
- ? ((_b = (_a = this.stops[stop.parent]) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : [])
16151
+ ? (this.stops[stop.parent]?.children ?? [])
16205
16152
  : stop.children;
16206
16153
  return Array.from(new Set([id, ...equivalentStops])).map((stopId) => this.stops[stopId]);
16207
16154
  }
16208
16155
  }
16209
16156
 
16210
16157
  class Duration {
16158
+ totalSeconds;
16211
16159
  constructor(totalSeconds) {
16212
16160
  this.totalSeconds = totalSeconds;
16213
16161
  }
@@ -16510,15 +16458,14 @@ const Route$2 = {
16510
16458
  return obj;
16511
16459
  },
16512
16460
  create(base) {
16513
- return Route$2.fromPartial(base !== null && base !== void 0 ? base : {});
16461
+ return Route$2.fromPartial(base ?? {});
16514
16462
  },
16515
16463
  fromPartial(object) {
16516
- var _a, _b, _c, _d;
16517
16464
  const message = createBaseRoute();
16518
- message.stopTimes = (_a = object.stopTimes) !== null && _a !== void 0 ? _a : new Uint8Array(0);
16519
- message.pickUpDropOffTypes = (_b = object.pickUpDropOffTypes) !== null && _b !== void 0 ? _b : new Uint8Array(0);
16520
- message.stops = (_c = object.stops) !== null && _c !== void 0 ? _c : new Uint8Array(0);
16521
- message.serviceRouteId = (_d = object.serviceRouteId) !== null && _d !== void 0 ? _d : 0;
16465
+ message.stopTimes = object.stopTimes ?? new Uint8Array(0);
16466
+ message.pickUpDropOffTypes = object.pickUpDropOffTypes ?? new Uint8Array(0);
16467
+ message.stops = object.stops ?? new Uint8Array(0);
16468
+ message.serviceRouteId = object.serviceRouteId ?? 0;
16522
16469
  return message;
16523
16470
  },
16524
16471
  };
@@ -16595,52 +16542,227 @@ const Transfer = {
16595
16542
  return obj;
16596
16543
  },
16597
16544
  create(base) {
16598
- return Transfer.fromPartial(base !== null && base !== void 0 ? base : {});
16545
+ return Transfer.fromPartial(base ?? {});
16599
16546
  },
16600
16547
  fromPartial(object) {
16601
- var _a, _b, _c;
16602
16548
  const message = createBaseTransfer();
16603
- message.destination = (_a = object.destination) !== null && _a !== void 0 ? _a : 0;
16604
- message.type = (_b = object.type) !== null && _b !== void 0 ? _b : 0;
16605
- message.minTransferTime = (_c = object.minTransferTime) !== null && _c !== void 0 ? _c : undefined;
16549
+ message.destination = object.destination ?? 0;
16550
+ message.type = object.type ?? 0;
16551
+ message.minTransferTime = object.minTransferTime ?? undefined;
16606
16552
  return message;
16607
16553
  },
16608
16554
  };
16609
- function createBaseStopAdjacency() {
16610
- return { transfers: [], routes: [] };
16555
+ function createBaseTripBoarding() {
16556
+ return { hopOnStopIndex: 0, routeId: 0, tripIndex: 0 };
16611
16557
  }
16612
- const StopAdjacency = {
16558
+ const TripBoarding = {
16613
16559
  encode(message, writer = new BinaryWriter()) {
16614
- for (const v of message.transfers) {
16615
- Transfer.encode(v, writer.uint32(10).fork()).join();
16560
+ if (message.hopOnStopIndex !== 0) {
16561
+ writer.uint32(8).uint32(message.hopOnStopIndex);
16616
16562
  }
16617
- writer.uint32(18).fork();
16618
- for (const v of message.routes) {
16619
- writer.uint32(v);
16563
+ if (message.routeId !== 0) {
16564
+ writer.uint32(16).uint32(message.routeId);
16565
+ }
16566
+ if (message.tripIndex !== 0) {
16567
+ writer.uint32(24).uint32(message.tripIndex);
16620
16568
  }
16621
- writer.join();
16622
16569
  return writer;
16623
16570
  },
16624
16571
  decode(input, length) {
16625
16572
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16626
16573
  const end = length === undefined ? reader.len : reader.pos + length;
16627
- const message = createBaseStopAdjacency();
16574
+ const message = createBaseTripBoarding();
16628
16575
  while (reader.pos < end) {
16629
16576
  const tag = reader.uint32();
16630
16577
  switch (tag >>> 3) {
16631
16578
  case 1: {
16632
- if (tag !== 10) {
16579
+ if (tag !== 8) {
16633
16580
  break;
16634
16581
  }
16635
- message.transfers.push(Transfer.decode(reader, reader.uint32()));
16582
+ message.hopOnStopIndex = reader.uint32();
16583
+ continue;
16584
+ }
16585
+ case 2: {
16586
+ if (tag !== 16) {
16587
+ break;
16588
+ }
16589
+ message.routeId = reader.uint32();
16590
+ continue;
16591
+ }
16592
+ case 3: {
16593
+ if (tag !== 24) {
16594
+ break;
16595
+ }
16596
+ message.tripIndex = reader.uint32();
16597
+ continue;
16598
+ }
16599
+ }
16600
+ if ((tag & 7) === 4 || tag === 0) {
16601
+ break;
16602
+ }
16603
+ reader.skip(tag & 7);
16604
+ }
16605
+ return message;
16606
+ },
16607
+ fromJSON(object) {
16608
+ return {
16609
+ hopOnStopIndex: isSet(object.hopOnStopIndex) ? globalThis.Number(object.hopOnStopIndex) : 0,
16610
+ routeId: isSet(object.routeId) ? globalThis.Number(object.routeId) : 0,
16611
+ tripIndex: isSet(object.tripIndex) ? globalThis.Number(object.tripIndex) : 0,
16612
+ };
16613
+ },
16614
+ toJSON(message) {
16615
+ const obj = {};
16616
+ if (message.hopOnStopIndex !== 0) {
16617
+ obj.hopOnStopIndex = Math.round(message.hopOnStopIndex);
16618
+ }
16619
+ if (message.routeId !== 0) {
16620
+ obj.routeId = Math.round(message.routeId);
16621
+ }
16622
+ if (message.tripIndex !== 0) {
16623
+ obj.tripIndex = Math.round(message.tripIndex);
16624
+ }
16625
+ return obj;
16626
+ },
16627
+ create(base) {
16628
+ return TripBoarding.fromPartial(base ?? {});
16629
+ },
16630
+ fromPartial(object) {
16631
+ const message = createBaseTripBoarding();
16632
+ message.hopOnStopIndex = object.hopOnStopIndex ?? 0;
16633
+ message.routeId = object.routeId ?? 0;
16634
+ message.tripIndex = object.tripIndex ?? 0;
16635
+ return message;
16636
+ },
16637
+ };
16638
+ function createBaseTripContinuationEntry() {
16639
+ return { originStopIndex: 0, originRouteId: 0, originTripIndex: 0, continuations: [] };
16640
+ }
16641
+ const TripContinuationEntry = {
16642
+ encode(message, writer = new BinaryWriter()) {
16643
+ if (message.originStopIndex !== 0) {
16644
+ writer.uint32(8).uint32(message.originStopIndex);
16645
+ }
16646
+ if (message.originRouteId !== 0) {
16647
+ writer.uint32(16).uint32(message.originRouteId);
16648
+ }
16649
+ if (message.originTripIndex !== 0) {
16650
+ writer.uint32(24).uint32(message.originTripIndex);
16651
+ }
16652
+ for (const v of message.continuations) {
16653
+ TripBoarding.encode(v, writer.uint32(34).fork()).join();
16654
+ }
16655
+ return writer;
16656
+ },
16657
+ decode(input, length) {
16658
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16659
+ const end = length === undefined ? reader.len : reader.pos + length;
16660
+ const message = createBaseTripContinuationEntry();
16661
+ while (reader.pos < end) {
16662
+ const tag = reader.uint32();
16663
+ switch (tag >>> 3) {
16664
+ case 1: {
16665
+ if (tag !== 8) {
16666
+ break;
16667
+ }
16668
+ message.originStopIndex = reader.uint32();
16636
16669
  continue;
16637
16670
  }
16638
16671
  case 2: {
16639
- if (tag === 16) {
16672
+ if (tag !== 16) {
16673
+ break;
16674
+ }
16675
+ message.originRouteId = reader.uint32();
16676
+ continue;
16677
+ }
16678
+ case 3: {
16679
+ if (tag !== 24) {
16680
+ break;
16681
+ }
16682
+ message.originTripIndex = reader.uint32();
16683
+ continue;
16684
+ }
16685
+ case 4: {
16686
+ if (tag !== 34) {
16687
+ break;
16688
+ }
16689
+ message.continuations.push(TripBoarding.decode(reader, reader.uint32()));
16690
+ continue;
16691
+ }
16692
+ }
16693
+ if ((tag & 7) === 4 || tag === 0) {
16694
+ break;
16695
+ }
16696
+ reader.skip(tag & 7);
16697
+ }
16698
+ return message;
16699
+ },
16700
+ fromJSON(object) {
16701
+ return {
16702
+ originStopIndex: isSet(object.originStopIndex) ? globalThis.Number(object.originStopIndex) : 0,
16703
+ originRouteId: isSet(object.originRouteId) ? globalThis.Number(object.originRouteId) : 0,
16704
+ originTripIndex: isSet(object.originTripIndex) ? globalThis.Number(object.originTripIndex) : 0,
16705
+ continuations: globalThis.Array.isArray(object?.continuations)
16706
+ ? object.continuations.map((e) => TripBoarding.fromJSON(e))
16707
+ : [],
16708
+ };
16709
+ },
16710
+ toJSON(message) {
16711
+ const obj = {};
16712
+ if (message.originStopIndex !== 0) {
16713
+ obj.originStopIndex = Math.round(message.originStopIndex);
16714
+ }
16715
+ if (message.originRouteId !== 0) {
16716
+ obj.originRouteId = Math.round(message.originRouteId);
16717
+ }
16718
+ if (message.originTripIndex !== 0) {
16719
+ obj.originTripIndex = Math.round(message.originTripIndex);
16720
+ }
16721
+ if (message.continuations?.length) {
16722
+ obj.continuations = message.continuations.map((e) => TripBoarding.toJSON(e));
16723
+ }
16724
+ return obj;
16725
+ },
16726
+ create(base) {
16727
+ return TripContinuationEntry.fromPartial(base ?? {});
16728
+ },
16729
+ fromPartial(object) {
16730
+ const message = createBaseTripContinuationEntry();
16731
+ message.originStopIndex = object.originStopIndex ?? 0;
16732
+ message.originRouteId = object.originRouteId ?? 0;
16733
+ message.originTripIndex = object.originTripIndex ?? 0;
16734
+ message.continuations = object.continuations?.map((e) => TripBoarding.fromPartial(e)) || [];
16735
+ return message;
16736
+ },
16737
+ };
16738
+ function createBaseStopAdjacency() {
16739
+ return { routes: [], transfers: [] };
16740
+ }
16741
+ const StopAdjacency = {
16742
+ encode(message, writer = new BinaryWriter()) {
16743
+ writer.uint32(10).fork();
16744
+ for (const v of message.routes) {
16745
+ writer.uint32(v);
16746
+ }
16747
+ writer.join();
16748
+ for (const v of message.transfers) {
16749
+ Transfer.encode(v, writer.uint32(18).fork()).join();
16750
+ }
16751
+ return writer;
16752
+ },
16753
+ decode(input, length) {
16754
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16755
+ const end = length === undefined ? reader.len : reader.pos + length;
16756
+ const message = createBaseStopAdjacency();
16757
+ while (reader.pos < end) {
16758
+ const tag = reader.uint32();
16759
+ switch (tag >>> 3) {
16760
+ case 1: {
16761
+ if (tag === 8) {
16640
16762
  message.routes.push(reader.uint32());
16641
16763
  continue;
16642
16764
  }
16643
- if (tag === 18) {
16765
+ if (tag === 10) {
16644
16766
  const end2 = reader.uint32() + reader.pos;
16645
16767
  while (reader.pos < end2) {
16646
16768
  message.routes.push(reader.uint32());
@@ -16649,6 +16771,13 @@ const StopAdjacency = {
16649
16771
  }
16650
16772
  break;
16651
16773
  }
16774
+ case 2: {
16775
+ if (tag !== 18) {
16776
+ break;
16777
+ }
16778
+ message.transfers.push(Transfer.decode(reader, reader.uint32()));
16779
+ continue;
16780
+ }
16652
16781
  }
16653
16782
  if ((tag & 7) === 4 || tag === 0) {
16654
16783
  break;
@@ -16659,31 +16788,29 @@ const StopAdjacency = {
16659
16788
  },
16660
16789
  fromJSON(object) {
16661
16790
  return {
16662
- transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
16791
+ routes: globalThis.Array.isArray(object?.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
16792
+ transfers: globalThis.Array.isArray(object?.transfers)
16663
16793
  ? object.transfers.map((e) => Transfer.fromJSON(e))
16664
16794
  : [],
16665
- routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
16666
16795
  };
16667
16796
  },
16668
16797
  toJSON(message) {
16669
- var _a, _b;
16670
16798
  const obj = {};
16671
- if ((_a = message.transfers) === null || _a === void 0 ? void 0 : _a.length) {
16672
- obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
16673
- }
16674
- if ((_b = message.routes) === null || _b === void 0 ? void 0 : _b.length) {
16799
+ if (message.routes?.length) {
16675
16800
  obj.routes = message.routes.map((e) => Math.round(e));
16676
16801
  }
16802
+ if (message.transfers?.length) {
16803
+ obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
16804
+ }
16677
16805
  return obj;
16678
16806
  },
16679
16807
  create(base) {
16680
- return StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
16808
+ return StopAdjacency.fromPartial(base ?? {});
16681
16809
  },
16682
16810
  fromPartial(object) {
16683
- var _a, _b;
16684
16811
  const message = createBaseStopAdjacency();
16685
- message.transfers = ((_a = object.transfers) === null || _a === void 0 ? void 0 : _a.map((e) => Transfer.fromPartial(e))) || [];
16686
- message.routes = ((_b = object.routes) === null || _b === void 0 ? void 0 : _b.map((e) => e)) || [];
16812
+ message.routes = object.routes?.map((e) => e) || [];
16813
+ message.transfers = object.transfers?.map((e) => Transfer.fromPartial(e)) || [];
16687
16814
  return message;
16688
16815
  },
16689
16816
  };
@@ -16752,11 +16879,10 @@ const ServiceRoute = {
16752
16879
  return {
16753
16880
  type: isSet(object.type) ? routeTypeFromJSON(object.type) : 0,
16754
16881
  name: isSet(object.name) ? globalThis.String(object.name) : "",
16755
- routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
16882
+ routes: globalThis.Array.isArray(object?.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
16756
16883
  };
16757
16884
  },
16758
16885
  toJSON(message) {
16759
- var _a;
16760
16886
  const obj = {};
16761
16887
  if (message.type !== 0) {
16762
16888
  obj.type = routeTypeToJSON(message.type);
@@ -16764,25 +16890,24 @@ const ServiceRoute = {
16764
16890
  if (message.name !== "") {
16765
16891
  obj.name = message.name;
16766
16892
  }
16767
- if ((_a = message.routes) === null || _a === void 0 ? void 0 : _a.length) {
16893
+ if (message.routes?.length) {
16768
16894
  obj.routes = message.routes.map((e) => Math.round(e));
16769
16895
  }
16770
16896
  return obj;
16771
16897
  },
16772
16898
  create(base) {
16773
- return ServiceRoute.fromPartial(base !== null && base !== void 0 ? base : {});
16899
+ return ServiceRoute.fromPartial(base ?? {});
16774
16900
  },
16775
16901
  fromPartial(object) {
16776
- var _a, _b, _c;
16777
16902
  const message = createBaseServiceRoute();
16778
- message.type = (_a = object.type) !== null && _a !== void 0 ? _a : 0;
16779
- message.name = (_b = object.name) !== null && _b !== void 0 ? _b : "";
16780
- message.routes = ((_c = object.routes) === null || _c === void 0 ? void 0 : _c.map((e) => e)) || [];
16903
+ message.type = object.type ?? 0;
16904
+ message.name = object.name ?? "";
16905
+ message.routes = object.routes?.map((e) => e) || [];
16781
16906
  return message;
16782
16907
  },
16783
16908
  };
16784
16909
  function createBaseTimetable() {
16785
- return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [] };
16910
+ return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [], tripContinuations: [] };
16786
16911
  }
16787
16912
  const Timetable$1 = {
16788
16913
  encode(message, writer = new BinaryWriter()) {
@@ -16798,6 +16923,9 @@ const Timetable$1 = {
16798
16923
  for (const v of message.serviceRoutes) {
16799
16924
  ServiceRoute.encode(v, writer.uint32(34).fork()).join();
16800
16925
  }
16926
+ for (const v of message.tripContinuations) {
16927
+ TripContinuationEntry.encode(v, writer.uint32(42).fork()).join();
16928
+ }
16801
16929
  return writer;
16802
16930
  },
16803
16931
  decode(input, length) {
@@ -16835,6 +16963,13 @@ const Timetable$1 = {
16835
16963
  message.serviceRoutes.push(ServiceRoute.decode(reader, reader.uint32()));
16836
16964
  continue;
16837
16965
  }
16966
+ case 5: {
16967
+ if (tag !== 42) {
16968
+ break;
16969
+ }
16970
+ message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
16971
+ continue;
16972
+ }
16838
16973
  }
16839
16974
  if ((tag & 7) === 4 || tag === 0) {
16840
16975
  break;
@@ -16846,44 +16981,49 @@ const Timetable$1 = {
16846
16981
  fromJSON(object) {
16847
16982
  return {
16848
16983
  version: isSet(object.version) ? globalThis.String(object.version) : "",
16849
- stopsAdjacency: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.stopsAdjacency)
16984
+ stopsAdjacency: globalThis.Array.isArray(object?.stopsAdjacency)
16850
16985
  ? object.stopsAdjacency.map((e) => StopAdjacency.fromJSON(e))
16851
16986
  : [],
16852
- routesAdjacency: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routesAdjacency)
16987
+ routesAdjacency: globalThis.Array.isArray(object?.routesAdjacency)
16853
16988
  ? object.routesAdjacency.map((e) => Route$2.fromJSON(e))
16854
16989
  : [],
16855
- serviceRoutes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.serviceRoutes)
16990
+ serviceRoutes: globalThis.Array.isArray(object?.serviceRoutes)
16856
16991
  ? object.serviceRoutes.map((e) => ServiceRoute.fromJSON(e))
16857
16992
  : [],
16993
+ tripContinuations: globalThis.Array.isArray(object?.tripContinuations)
16994
+ ? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
16995
+ : [],
16858
16996
  };
16859
16997
  },
16860
16998
  toJSON(message) {
16861
- var _a, _b, _c;
16862
16999
  const obj = {};
16863
17000
  if (message.version !== "") {
16864
17001
  obj.version = message.version;
16865
17002
  }
16866
- if ((_a = message.stopsAdjacency) === null || _a === void 0 ? void 0 : _a.length) {
17003
+ if (message.stopsAdjacency?.length) {
16867
17004
  obj.stopsAdjacency = message.stopsAdjacency.map((e) => StopAdjacency.toJSON(e));
16868
17005
  }
16869
- if ((_b = message.routesAdjacency) === null || _b === void 0 ? void 0 : _b.length) {
17006
+ if (message.routesAdjacency?.length) {
16870
17007
  obj.routesAdjacency = message.routesAdjacency.map((e) => Route$2.toJSON(e));
16871
17008
  }
16872
- if ((_c = message.serviceRoutes) === null || _c === void 0 ? void 0 : _c.length) {
17009
+ if (message.serviceRoutes?.length) {
16873
17010
  obj.serviceRoutes = message.serviceRoutes.map((e) => ServiceRoute.toJSON(e));
16874
17011
  }
17012
+ if (message.tripContinuations?.length) {
17013
+ obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
17014
+ }
16875
17015
  return obj;
16876
17016
  },
16877
17017
  create(base) {
16878
- return Timetable$1.fromPartial(base !== null && base !== void 0 ? base : {});
17018
+ return Timetable$1.fromPartial(base ?? {});
16879
17019
  },
16880
17020
  fromPartial(object) {
16881
- var _a, _b, _c, _d;
16882
17021
  const message = createBaseTimetable();
16883
- message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
16884
- message.stopsAdjacency = ((_b = object.stopsAdjacency) === null || _b === void 0 ? void 0 : _b.map((e) => StopAdjacency.fromPartial(e))) || [];
16885
- message.routesAdjacency = ((_c = object.routesAdjacency) === null || _c === void 0 ? void 0 : _c.map((e) => Route$2.fromPartial(e))) || [];
16886
- message.serviceRoutes = ((_d = object.serviceRoutes) === null || _d === void 0 ? void 0 : _d.map((e) => ServiceRoute.fromPartial(e))) || [];
17022
+ message.version = object.version ?? "";
17023
+ message.stopsAdjacency = object.stopsAdjacency?.map((e) => StopAdjacency.fromPartial(e)) || [];
17024
+ message.routesAdjacency = object.routesAdjacency?.map((e) => Route$2.fromPartial(e)) || [];
17025
+ message.serviceRoutes = object.serviceRoutes?.map((e) => ServiceRoute.fromPartial(e)) || [];
17026
+ message.tripContinuations = object.tripContinuations?.map((e) => TripContinuationEntry.fromPartial(e)) || [];
16887
17027
  return message;
16888
17028
  },
16889
17029
  };
@@ -16920,6 +17060,11 @@ function isSet(value) {
16920
17060
  * A class representing a time as minutes since midnight.
16921
17061
  */
16922
17062
  class Time {
17063
+ /*
17064
+ * Number of minutes since midnight.
17065
+ Note that this value can go beyond 3600 to model services overlapping with the next day.
17066
+ */
17067
+ minutesSinceMidnight;
16923
17068
  /**
16924
17069
  * Gets the infinity time as a Time instance.
16925
17070
  * This represents a time that is conceptually beyond any real possible time.
@@ -17172,7 +17317,53 @@ const toPickupDropOffType = (numericalType) => {
17172
17317
  * A route identifies all trips of a given service route sharing the same list of stops.
17173
17318
  */
17174
17319
  let Route$1 = class Route {
17175
- constructor(stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
17320
+ id;
17321
+ /**
17322
+ * Arrivals and departures encoded as minutes from midnight.
17323
+ * Format: [arrival1, departure1, arrival2, departure2, etc.]
17324
+ */
17325
+ stopTimes;
17326
+ /**
17327
+ * PickUp and DropOff types represented as a 2-bit encoded Uint8Array.
17328
+ * Values (2 bits each):
17329
+ * 0: REGULAR
17330
+ * 1: NOT_AVAILABLE
17331
+ * 2: MUST_PHONE_AGENCY
17332
+ * 3: MUST_COORDINATE_WITH_DRIVER
17333
+ *
17334
+ * Encoding format: Each byte contains 2 pickup/drop-off pairs (4 bits each)
17335
+ * Bit layout per byte: [pickup_1 (2 bits)][drop_off_1 (2 bits)][pickup_0 (2 bits)][drop_off_0 (2 bits)]
17336
+ * Example: For stops 0 and 1 in a trip, one byte encodes all 4 values
17337
+ */
17338
+ pickUpDropOffTypes;
17339
+ /**
17340
+ * A binary array of stopIds in the route.
17341
+ * [stop1, stop2, stop3,...]
17342
+ */
17343
+ stops;
17344
+ /**
17345
+ * A reverse mapping of each stop with their index in the route:
17346
+ * {
17347
+ * 4: 0,
17348
+ * 5: 1,
17349
+ * ...
17350
+ * }
17351
+ */
17352
+ stopIndices;
17353
+ /**
17354
+ * The identifier of the route as a service shown to users.
17355
+ */
17356
+ serviceRouteId;
17357
+ /**
17358
+ * The total number of stops in the route.
17359
+ */
17360
+ nbStops;
17361
+ /**
17362
+ * The total number of trips in the route.
17363
+ */
17364
+ nbTrips;
17365
+ constructor(id, stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
17366
+ this.id = id;
17176
17367
  this.stopTimes = stopTimes;
17177
17368
  this.pickUpDropOffTypes = pickUpDropOffTypes;
17178
17369
  this.stops = stops;
@@ -17182,8 +17373,86 @@ let Route$1 = class Route {
17182
17373
  this.stopIndices = new Map();
17183
17374
  for (let i = 0; i < stops.length; i++) {
17184
17375
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17185
- this.stopIndices.set(stops[i], i);
17376
+ const stopId = stops[i];
17377
+ const existingIndices = this.stopIndices.get(stopId);
17378
+ if (existingIndices) {
17379
+ existingIndices.push(i);
17380
+ }
17381
+ else {
17382
+ this.stopIndices.set(stopId, [i]);
17383
+ }
17384
+ }
17385
+ }
17386
+ /**
17387
+ * Creates a new route from multiple trips with their stops.
17388
+ *
17389
+ * @param params The route parameters including ID, service route ID, and trips.
17390
+ * @returns The new route.
17391
+ */
17392
+ static of(params) {
17393
+ const { id, serviceRouteId, trips } = params;
17394
+ if (trips.length === 0) {
17395
+ throw new Error('At least one trip must be provided');
17396
+ }
17397
+ // All trips must have the same stops in the same order
17398
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17399
+ const firstTrip = trips[0];
17400
+ const stopIds = new Uint32Array(firstTrip.stops.map((stop) => stop.id));
17401
+ const numStops = stopIds.length;
17402
+ // Validate all trips have the same stops
17403
+ for (let tripIndex = 1; tripIndex < trips.length; tripIndex++) {
17404
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17405
+ const trip = trips[tripIndex];
17406
+ if (trip.stops.length !== numStops) {
17407
+ throw new Error(`Trip ${tripIndex} has ${trip.stops.length} stops, expected ${numStops}`);
17408
+ }
17409
+ for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
17410
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17411
+ if (trip.stops[stopIndex].id !== stopIds[stopIndex]) {
17412
+ throw new Error(`Trip ${tripIndex} has different stop at index ${stopIndex}`);
17413
+ }
17414
+ }
17415
+ }
17416
+ // Create stopTimes array with arrivals and departures for all trips
17417
+ const stopTimes = new Uint16Array(trips.length * numStops * 2);
17418
+ for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
17419
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17420
+ const trip = trips[tripIndex];
17421
+ for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
17422
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17423
+ const stop = trip.stops[stopIndex];
17424
+ const baseIndex = (tripIndex * numStops + stopIndex) * 2;
17425
+ stopTimes[baseIndex] = stop.arrivalTime.toMinutes();
17426
+ stopTimes[baseIndex + 1] = stop.departureTime.toMinutes();
17427
+ }
17428
+ }
17429
+ // Create pickUpDropOffTypes array (2-bit encoded) for all trips
17430
+ const totalStopEntries = trips.length * numStops;
17431
+ const pickUpDropOffTypes = new Uint8Array(Math.ceil(totalStopEntries / 2));
17432
+ for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
17433
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17434
+ const trip = trips[tripIndex];
17435
+ for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
17436
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17437
+ const stop = trip.stops[stopIndex];
17438
+ const globalIndex = tripIndex * numStops + stopIndex;
17439
+ const pickUp = stop.pickUpType ?? REGULAR;
17440
+ const dropOff = stop.dropOffType ?? REGULAR;
17441
+ const byteIndex = Math.floor(globalIndex / 2);
17442
+ const isSecondPair = globalIndex % 2 === 1;
17443
+ if (isSecondPair) {
17444
+ // Second pair: pickup in upper 2 bits, dropOff in bits 4-5
17445
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17446
+ pickUpDropOffTypes[byteIndex] |= (pickUp << 6) | (dropOff << 4);
17447
+ }
17448
+ else {
17449
+ // First pair: pickup in bits 2-3, dropOff in lower 2 bits
17450
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17451
+ pickUpDropOffTypes[byteIndex] |= (pickUp << 2) | dropOff;
17452
+ }
17453
+ }
17186
17454
  }
17455
+ return new Route(id, stopTimes, pickUpDropOffTypes, stopIds, serviceRouteId);
17187
17456
  }
17188
17457
  /**
17189
17458
  * Serializes the Route into binary arrays.
@@ -17198,24 +17467,6 @@ let Route$1 = class Route {
17198
17467
  serviceRouteId: this.serviceRouteId,
17199
17468
  };
17200
17469
  }
17201
- /**
17202
- * Checks if stop A is before stop B in the route.
17203
- *
17204
- * @param stopA - The StopId of the first stop.
17205
- * @param stopB - The StopId of the second stop.
17206
- * @returns True if stop A is before stop B, false otherwise.
17207
- */
17208
- isBefore(stopA, stopB) {
17209
- const stopAIndex = this.stopIndices.get(stopA);
17210
- if (stopAIndex === undefined) {
17211
- throw new Error(`Stop index ${stopAIndex} not found in route ${this.serviceRouteId}`);
17212
- }
17213
- const stopBIndex = this.stopIndices.get(stopB);
17214
- if (stopBIndex === undefined) {
17215
- throw new Error(`Stop index ${stopBIndex} not found in route ${this.serviceRouteId}`);
17216
- }
17217
- return stopAIndex < stopBIndex;
17218
- }
17219
17470
  /**
17220
17471
  * Retrieves the number of stops in the route.
17221
17472
  *
@@ -17224,6 +17475,14 @@ let Route$1 = class Route {
17224
17475
  getNbStops() {
17225
17476
  return this.nbStops;
17226
17477
  }
17478
+ /**
17479
+ * Retrieves the number of trips in the route.
17480
+ *
17481
+ * @returns The total number of trips in the route.
17482
+ */
17483
+ getNbTrips() {
17484
+ return this.nbTrips;
17485
+ }
17227
17486
  /**
17228
17487
  * Finds the ServiceRouteId of the route. It corresponds the identifier
17229
17488
  * of the service shown to the end user as a route.
@@ -17236,47 +17495,47 @@ let Route$1 = class Route {
17236
17495
  /**
17237
17496
  * Retrieves the arrival time at a specific stop for a given trip.
17238
17497
  *
17239
- * @param stopId - The identifier of the stop.
17498
+ * @param stopIndex - The index of the stop in the route.
17240
17499
  * @param tripIndex - The index of the trip.
17241
17500
  * @returns The arrival time at the specified stop and trip as a Time object.
17242
17501
  */
17243
- arrivalAt(stopId, tripIndex) {
17244
- const arrivalIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2;
17502
+ arrivalAt(stopIndex, tripIndex) {
17503
+ const arrivalIndex = (tripIndex * this.stops.length + stopIndex) * 2;
17245
17504
  const arrival = this.stopTimes[arrivalIndex];
17246
17505
  if (arrival === undefined) {
17247
- throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17506
+ throw new Error(`Arrival time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17248
17507
  }
17249
17508
  return Time.fromMinutes(arrival);
17250
17509
  }
17251
17510
  /**
17252
17511
  * Retrieves the departure time at a specific stop for a given trip.
17253
17512
  *
17254
- * @param stopId - The identifier of the stop.
17513
+ * @param stopIndex - The index of the stop in the route.
17255
17514
  * @param tripIndex - The index of the trip.
17256
17515
  * @returns The departure time at the specified stop and trip as a Time object.
17257
17516
  */
17258
- departureFrom(stopId, tripIndex) {
17259
- const departureIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2 + 1;
17517
+ departureFrom(stopIndex, tripIndex) {
17518
+ const departureIndex = (tripIndex * this.stops.length + stopIndex) * 2 + 1;
17260
17519
  const departure = this.stopTimes[departureIndex];
17261
17520
  if (departure === undefined) {
17262
- throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17521
+ throw new Error(`Departure time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17263
17522
  }
17264
17523
  return Time.fromMinutes(departure);
17265
17524
  }
17266
17525
  /**
17267
17526
  * Retrieves the pick-up type for a specific stop and trip.
17268
17527
  *
17269
- * @param stopId - The identifier of the stop.
17528
+ * @param stopIndex - The index of the stop in the route.
17270
17529
  * @param tripIndex - The index of the trip.
17271
17530
  * @returns The pick-up type at the specified stop and trip.
17272
17531
  */
17273
- pickUpTypeFrom(stopId, tripIndex) {
17274
- const globalIndex = tripIndex * this.stops.length + this.stopIndex(stopId);
17532
+ pickUpTypeFrom(stopIndex, tripIndex) {
17533
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
17275
17534
  const byteIndex = Math.floor(globalIndex / 2);
17276
17535
  const isSecondPair = globalIndex % 2 === 1;
17277
17536
  const byte = this.pickUpDropOffTypes[byteIndex];
17278
17537
  if (byte === undefined) {
17279
- throw new Error(`Pick up type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17538
+ throw new Error(`Pick up type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17280
17539
  }
17281
17540
  const pickUpValue = isSecondPair
17282
17541
  ? (byte >> 6) & 0x03 // Upper 2 bits for second pair
@@ -17286,17 +17545,17 @@ let Route$1 = class Route {
17286
17545
  /**
17287
17546
  * Retrieves the drop-off type for a specific stop and trip.
17288
17547
  *
17289
- * @param stopId - The identifier of the stop.
17548
+ * @param stopIndex - The index of the stop in the route.
17290
17549
  * @param tripIndex - The index of the trip.
17291
17550
  * @returns The drop-off type at the specified stop and trip.
17292
17551
  */
17293
- dropOffTypeAt(stopId, tripIndex) {
17294
- const globalIndex = tripIndex * this.stops.length + this.stopIndex(stopId);
17552
+ dropOffTypeAt(stopIndex, tripIndex) {
17553
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
17295
17554
  const byteIndex = Math.floor(globalIndex / 2);
17296
17555
  const isSecondPair = globalIndex % 2 === 1;
17297
17556
  const byte = this.pickUpDropOffTypes[byteIndex];
17298
17557
  if (byte === undefined) {
17299
- throw new Error(`Drop off type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17558
+ throw new Error(`Drop off type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17300
17559
  }
17301
17560
  const dropOffValue = isSecondPair
17302
17561
  ? (byte >> 4) & 0x03 // Bits 4-5 for second pair
@@ -17308,14 +17567,14 @@ let Route$1 = class Route {
17308
17567
  * optionally constrained by a latest trip index and a time before which the trip
17309
17568
  * should not depart.
17310
17569
  * *
17311
- * @param stopId - The StopId of the stop where the trip should be found.
17570
+ * @param stopIndex - The route index of the stop where the trip should be found.
17312
17571
  * @param [after=Time.origin()] - The earliest time after which the trip should depart.
17313
17572
  * If not provided, searches all available trips.
17314
17573
  * @param [beforeTrip] - (Optional) The index of the trip before which the search should be constrained.
17315
17574
  * If not provided, searches all available trips.
17316
17575
  * @returns The index of the earliest trip meeting the criteria, or undefined if no such trip is found.
17317
17576
  */
17318
- findEarliestTrip(stopId, after = Time.origin(), beforeTrip) {
17577
+ findEarliestTrip(stopIndex, after = Time.origin(), beforeTrip) {
17319
17578
  if (this.nbTrips <= 0)
17320
17579
  return undefined;
17321
17580
  let hi = this.nbTrips - 1;
@@ -17327,7 +17586,7 @@ let Route$1 = class Route {
17327
17586
  let lb = -1;
17328
17587
  while (lo <= hi) {
17329
17588
  const mid = (lo + hi) >>> 1;
17330
- const depMid = this.departureFrom(stopId, mid);
17589
+ const depMid = this.departureFrom(stopIndex, mid);
17331
17590
  if (depMid.isBefore(after)) {
17332
17591
  lo = mid + 1;
17333
17592
  }
@@ -17338,8 +17597,8 @@ let Route$1 = class Route {
17338
17597
  }
17339
17598
  if (lb === -1)
17340
17599
  return undefined;
17341
- for (let t = lb; t < (beforeTrip !== null && beforeTrip !== void 0 ? beforeTrip : this.nbTrips); t++) {
17342
- const pickup = this.pickUpTypeFrom(stopId, t);
17600
+ for (let t = lb; t < (beforeTrip ?? this.nbTrips); t++) {
17601
+ const pickup = this.pickUpTypeFrom(stopIndex, t);
17343
17602
  if (pickup !== 'NOT_AVAILABLE') {
17344
17603
  return t;
17345
17604
  }
@@ -17347,17 +17606,74 @@ let Route$1 = class Route {
17347
17606
  return undefined;
17348
17607
  }
17349
17608
  /**
17350
- * Retrieves the index of a stop within the route.
17609
+ * Retrieves the indices of a stop within the route.
17351
17610
  * @param stopId The StopId of the stop to locate in the route.
17352
- * @returns The index of the stop in the route.
17611
+ * @returns An array of indices where the stop appears in the route, or an empty array if the stop is not found.
17353
17612
  */
17354
- stopIndex(stopId) {
17613
+ stopRouteIndices(stopId) {
17355
17614
  const stopIndex = this.stopIndices.get(stopId);
17356
17615
  if (stopIndex === undefined) {
17357
- throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
17616
+ return [];
17358
17617
  }
17359
17618
  return stopIndex;
17360
17619
  }
17620
+ /**
17621
+ * Retrieves the id of a stop at a given index in a route.
17622
+ * @param stopRouteIndex The route index of the stop.
17623
+ * @returns The id of the stop at the given index in the route.
17624
+ */
17625
+ stopId(stopRouteIndex) {
17626
+ const stopId = this.stops[stopRouteIndex];
17627
+ if (stopId === undefined) {
17628
+ throw new Error(`StopId for stop at index ${stopRouteIndex} not found in route ${this.serviceRouteId}`);
17629
+ }
17630
+ return stopId;
17631
+ }
17632
+ };
17633
+
17634
+ // Each value uses 20 bits, allowing values from 0 to 1,048,575 (2^20 - 1)
17635
+ const VALUE_MASK = (1n << 20n) - 1n; // 0xFFFFF
17636
+ const MAX_VALUE = 1_048_575; // 2^20 - 1
17637
+ // Bit positions for each value in the 60-bit bigint
17638
+ const TRIP_INDEX_SHIFT = 0n;
17639
+ const ROUTE_ID_SHIFT = 20n;
17640
+ const STOP_INDEX_SHIFT = 40n;
17641
+ /**
17642
+ * Validates that a value fits within 20 bits (0 to 1,048,575)
17643
+ * @param value - The value to validate
17644
+ * @param name - The name of the value for error reporting
17645
+ * @throws Error if the value is out of range
17646
+ */
17647
+ const validateValue = (value, name) => {
17648
+ if (value < 0 || value > MAX_VALUE) {
17649
+ throw new Error(`${name} must be between 0 and ${MAX_VALUE}, got ${value}`);
17650
+ }
17651
+ };
17652
+ /**
17653
+ * Encodes a stop index, route ID, and trip index into a single trip boarding ID.
17654
+ * @param stopIndex - The index of the stop within the route (0 to 1,048,575)
17655
+ * @param routeId - The route identifier (0 to 1,048,575)
17656
+ * @param tripIndex - The index of the trip within the route (0 to 1,048,575)
17657
+ * @returns The encoded trip ID as a bigint
17658
+ */
17659
+ const encode = (stopIndex, routeId, tripIndex) => {
17660
+ validateValue(stopIndex, 'stopIndex');
17661
+ validateValue(routeId, 'routeId');
17662
+ validateValue(tripIndex, 'tripIndex');
17663
+ return ((BigInt(stopIndex) << STOP_INDEX_SHIFT) |
17664
+ (BigInt(routeId) << ROUTE_ID_SHIFT) |
17665
+ (BigInt(tripIndex) << TRIP_INDEX_SHIFT));
17666
+ };
17667
+ /**
17668
+ * Decodes a trip boarding ID back into its constituent stop index, route ID, and trip index.
17669
+ * @param tripBoardingId - The encoded trip ID
17670
+ * @returns A tuple containing [stopIndex, routeId, tripIndex]
17671
+ */
17672
+ const decode = (tripBoardingId) => {
17673
+ const stopIndex = Number((tripBoardingId >> STOP_INDEX_SHIFT) & VALUE_MASK);
17674
+ const routeId = Number((tripBoardingId >> ROUTE_ID_SHIFT) & VALUE_MASK);
17675
+ const tripIndex = Number((tripBoardingId >> TRIP_INDEX_SHIFT) & VALUE_MASK);
17676
+ return [stopIndex, routeId, tripIndex];
17361
17677
  };
17362
17678
 
17363
17679
  const isLittleEndian = (() => {
@@ -17428,9 +17744,15 @@ const bytesToUint16Array = (bytes) => {
17428
17744
  const serializeStopsAdjacency = (stopsAdjacency) => {
17429
17745
  return stopsAdjacency.map((value) => {
17430
17746
  return {
17431
- transfers: value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: serializeTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
17432
- minTransferTime: transfer.minTransferTime.toSeconds(),
17433
- })))),
17747
+ transfers: value.transfers
17748
+ ? value.transfers.map((transfer) => ({
17749
+ destination: transfer.destination,
17750
+ type: serializeTransferType(transfer.type),
17751
+ ...(transfer.minTransferTime !== undefined && {
17752
+ minTransferTime: transfer.minTransferTime.toSeconds(),
17753
+ }),
17754
+ }))
17755
+ : [],
17434
17756
  routes: value.routes,
17435
17757
  };
17436
17758
  });
@@ -17466,15 +17788,22 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
17466
17788
  for (let j = 0; j < value.transfers.length; j++) {
17467
17789
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17468
17790
  const transfer = value.transfers[j];
17469
- const newTransfer = Object.assign({ destination: transfer.destination, type: parseTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
17470
- minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
17471
- }));
17791
+ const newTransfer = {
17792
+ destination: transfer.destination,
17793
+ type: parseTransferType(transfer.type),
17794
+ ...(transfer.minTransferTime !== undefined && {
17795
+ minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
17796
+ }),
17797
+ };
17472
17798
  transfers.push(newTransfer);
17473
17799
  }
17474
- result.push({
17475
- transfers: transfers,
17800
+ const stopAdjacency = {
17476
17801
  routes: value.routes,
17477
- });
17802
+ };
17803
+ if (transfers.length > 0) {
17804
+ stopAdjacency.transfers = transfers;
17805
+ }
17806
+ result.push(stopAdjacency);
17478
17807
  }
17479
17808
  return result;
17480
17809
  };
@@ -17484,7 +17813,7 @@ const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
17484
17813
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17485
17814
  const value = protoRoutesAdjacency[i];
17486
17815
  const stops = bytesToUint32Array(value.stops);
17487
- routesAdjacency.push(new Route$1(bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
17816
+ routesAdjacency.push(new Route$1(i, bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
17488
17817
  }
17489
17818
  return routesAdjacency;
17490
17819
  };
@@ -17578,6 +17907,38 @@ const serializeRouteType = (type) => {
17578
17907
  return RouteType.MONORAIL;
17579
17908
  }
17580
17909
  };
17910
+ const serializeTripContinuations = (tripContinuations) => {
17911
+ const result = [];
17912
+ for (const [tripBoardingId, boardings] of tripContinuations.entries()) {
17913
+ const [originStopIndex, originRouteId, originTripIndex] = decode(tripBoardingId);
17914
+ result.push({
17915
+ originStopIndex,
17916
+ originRouteId,
17917
+ originTripIndex,
17918
+ continuations: boardings.map((tripBoarding) => ({
17919
+ hopOnStopIndex: tripBoarding.hopOnStopIndex,
17920
+ routeId: tripBoarding.routeId,
17921
+ tripIndex: tripBoarding.tripIndex,
17922
+ })),
17923
+ });
17924
+ }
17925
+ return result;
17926
+ };
17927
+ const deserializeTripContinuations = (protoTripContinuations) => {
17928
+ const result = new Map();
17929
+ for (let i = 0; i < protoTripContinuations.length; i++) {
17930
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17931
+ const entry = protoTripContinuations[i];
17932
+ const tripBoardingId = encode(entry.originStopIndex, entry.originRouteId, entry.originTripIndex);
17933
+ const tripBoardings = entry.continuations.map((protoTripBoarding) => ({
17934
+ hopOnStopIndex: protoTripBoarding.hopOnStopIndex,
17935
+ routeId: protoTripBoarding.routeId,
17936
+ tripIndex: protoTripBoarding.tripIndex,
17937
+ }));
17938
+ result.set(tripBoardingId, tripBoardings);
17939
+ }
17940
+ return result;
17941
+ };
17581
17942
 
17582
17943
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
17583
17944
  const ALL_TRANSPORT_MODES = new Set([
@@ -17592,20 +17953,27 @@ const ALL_TRANSPORT_MODES = new Set([
17592
17953
  'TROLLEYBUS',
17593
17954
  'MONORAIL',
17594
17955
  ]);
17595
- const CURRENT_VERSION = '0.0.7';
17956
+ const EMPTY_TRIP_CONTINUATIONS = [];
17957
+ const CURRENT_VERSION = '0.0.9';
17596
17958
  /**
17597
17959
  * The internal transit timetable format.
17598
17960
  */
17599
17961
  class Timetable {
17600
- constructor(stopsAdjacency, routesAdjacency, routes) {
17962
+ stopsAdjacency;
17963
+ routesAdjacency;
17964
+ serviceRoutes;
17965
+ tripContinuations;
17966
+ activeStops;
17967
+ constructor(stopsAdjacency, routesAdjacency, routes, tripContinuations) {
17601
17968
  this.stopsAdjacency = stopsAdjacency;
17602
17969
  this.routesAdjacency = routesAdjacency;
17603
17970
  this.serviceRoutes = routes;
17971
+ this.tripContinuations = tripContinuations;
17604
17972
  this.activeStops = new Set();
17605
17973
  for (let i = 0; i < stopsAdjacency.length; i++) {
17606
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17607
17974
  const stop = stopsAdjacency[i];
17608
- if (stop.routes.length > 0 || stop.transfers.length > 0) {
17975
+ if (stop.routes.length > 0 ||
17976
+ (stop.transfers && stop.transfers.length > 0)) {
17609
17977
  this.activeStops.add(i);
17610
17978
  }
17611
17979
  }
@@ -17621,6 +17989,7 @@ class Timetable {
17621
17989
  stopsAdjacency: serializeStopsAdjacency(this.stopsAdjacency),
17622
17990
  routesAdjacency: serializeRoutesAdjacency(this.routesAdjacency),
17623
17991
  serviceRoutes: serializeServiceRoutesMap(this.serviceRoutes),
17992
+ tripContinuations: serializeTripContinuations(this.tripContinuations || new Map()),
17624
17993
  };
17625
17994
  const writer = new BinaryWriter();
17626
17995
  Timetable$1.encode(protoTimetable, writer);
@@ -17638,7 +18007,7 @@ class Timetable {
17638
18007
  if (protoTimetable.version !== CURRENT_VERSION) {
17639
18008
  throw new Error(`Unsupported timetable version ${protoTimetable.version}`);
17640
18009
  }
17641
- return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes));
18010
+ return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes), deserializeTripContinuations(protoTimetable.tripContinuations));
17642
18011
  }
17643
18012
  /**
17644
18013
  * Checks if the given stop is active on the timetable.
@@ -17668,8 +18037,26 @@ class Timetable {
17668
18037
  * @returns An array of transfer options available at the stop.
17669
18038
  */
17670
18039
  getTransfers(stopId) {
17671
- var _a, _b;
17672
- return (_b = (_a = this.stopsAdjacency[stopId]) === null || _a === void 0 ? void 0 : _a.transfers) !== null && _b !== void 0 ? _b : [];
18040
+ const stopAdjacency = this.stopsAdjacency[stopId];
18041
+ if (!stopAdjacency) {
18042
+ throw new Error(`Stop ID ${stopId} not found`);
18043
+ }
18044
+ return stopAdjacency.transfers || [];
18045
+ }
18046
+ /**
18047
+ * Retrieves all trip continuation options available at the specified stop for a given trip.
18048
+ *
18049
+ * @param stopIndex - The index in the route of the stop to get trip continuations for.
18050
+ * @param routeId - The ID of the route to get continuations for.
18051
+ * @param tripIndex - The index of the trip to get continuations for.
18052
+ * @returns An array of trip continuation options available at the stop for the specified trip.
18053
+ */
18054
+ getContinuousTrips(stopIndex, routeId, tripIndex) {
18055
+ const tripContinuations = this.tripContinuations?.get(encode(stopIndex, routeId, tripIndex));
18056
+ if (!tripContinuations) {
18057
+ return EMPTY_TRIP_CONTINUATIONS;
18058
+ }
18059
+ return tripContinuations;
17673
18060
  }
17674
18061
  /**
17675
18062
  * Retrieves the service route associated with the given route.
@@ -17709,12 +18096,12 @@ class Timetable {
17709
18096
  }
17710
18097
  /**
17711
18098
  * Finds routes that are reachable from a set of stop IDs.
17712
- * Also identifies the first stop available to hop on each route among
18099
+ * Also identifies the first stop index available to hop on each route among
17713
18100
  * the input stops.
17714
18101
  *
17715
18102
  * @param fromStops - The set of stop IDs to find reachable routes from.
17716
18103
  * @param transportModes - The set of transport modes to consider for reachable routes.
17717
- * @returns A map of reachable routes to the first stop available to hop on each route.
18104
+ * @returns A map of reachable routes to the first stop index available to hop on each route.
17718
18105
  */
17719
18106
  findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
17720
18107
  const reachableRoutes = new Map();
@@ -17727,15 +18114,17 @@ class Timetable {
17727
18114
  });
17728
18115
  for (let j = 0; j < validRoutes.length; j++) {
17729
18116
  const route = validRoutes[j];
17730
- const hopOnStop = reachableRoutes.get(route);
17731
- if (hopOnStop) {
17732
- if (route.isBefore(originStop, hopOnStop)) {
18117
+ const originStopIndices = route.stopRouteIndices(originStop);
18118
+ const originStopIndex = originStopIndices[0];
18119
+ const existingHopOnStopIndex = reachableRoutes.get(route);
18120
+ if (existingHopOnStopIndex !== undefined) {
18121
+ if (originStopIndex < existingHopOnStopIndex) {
17733
18122
  // if the current stop is before the existing hop on stop, replace it
17734
- reachableRoutes.set(route, originStop);
18123
+ reachableRoutes.set(route, originStopIndex);
17735
18124
  }
17736
18125
  }
17737
18126
  else {
17738
- reachableRoutes.set(route, originStop);
18127
+ reachableRoutes.set(route, originStopIndex);
17739
18128
  }
17740
18129
  }
17741
18130
  }
@@ -19711,35 +20100,22 @@ const parseCsv = (stream, numericColumns = []) => {
19711
20100
  * @param profile A configuration object defining the specificities of the GTFS feed.
19712
20101
  * @returns A map of all the valid routes.
19713
20102
  */
19714
- const parseRoutes = (routesStream_1, ...args_1) => __awaiter(void 0, [routesStream_1, ...args_1], void 0, function* (routesStream, profile = standardProfile) {
19715
- var _a, e_1, _b, _c;
20103
+ const parseRoutes = async (routesStream, profile = standardProfile) => {
19716
20104
  const routes = new Map();
19717
- try {
19718
- for (var _d = true, _e = __asyncValues(parseCsv(routesStream, ['route_type'])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19719
- _c = _f.value;
19720
- _d = false;
19721
- const rawLine = _c;
19722
- const line = rawLine;
19723
- const routeType = profile.routeTypeParser(line.route_type);
19724
- if (routeType === undefined) {
19725
- log.info(`Unsupported route type ${line.route_type} for route ${line.route_id}.`);
19726
- continue;
19727
- }
19728
- routes.set(line.route_id, {
19729
- name: line.route_short_name,
19730
- type: routeType,
19731
- });
19732
- }
19733
- }
19734
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
19735
- finally {
19736
- try {
19737
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
20105
+ for await (const rawLine of parseCsv(routesStream, ['route_type'])) {
20106
+ const line = rawLine;
20107
+ const routeType = profile.routeTypeParser(line.route_type);
20108
+ if (routeType === undefined) {
20109
+ log.info(`Unsupported route type ${line.route_type} for route ${line.route_id}.`);
20110
+ continue;
19738
20111
  }
19739
- finally { if (e_1) throw e_1.error; }
20112
+ routes.set(line.route_id, {
20113
+ name: line.route_short_name,
20114
+ type: routeType,
20115
+ });
19740
20116
  }
19741
20117
  return routes;
19742
- });
20118
+ };
19743
20119
  /**
19744
20120
  * Creates an array of ServiceRoute objects by combining GTFS route data with service route mappings.
19745
20121
  *
@@ -19802,46 +20178,33 @@ const weekdays = {
19802
20178
  * @param date The active date.
19803
20179
  * @param calendarStream A readable stream for the GTFS calendar.txt file.
19804
20180
  */
19805
- const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(void 0, void 0, void 0, function* () {
19806
- var _a, e_1, _b, _c;
20181
+ const parseCalendar = async (calendarStream, serviceIds, date) => {
19807
20182
  const activeDate = toGtfsDate(date);
19808
20183
  const weekday = date.weekday;
19809
20184
  const weekdayIndex = weekdays[weekday];
19810
- try {
19811
- for (var _d = true, _e = __asyncValues(parseCsv(calendarStream, [
19812
- 'monday',
19813
- 'tuesday',
19814
- 'wednesday',
19815
- 'thursday',
19816
- 'friday',
19817
- 'saturday',
19818
- 'sunday',
19819
- 'start_date',
19820
- 'end_date',
19821
- ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19822
- _c = _f.value;
19823
- _d = false;
19824
- const rawLine = _c;
19825
- const line = rawLine;
19826
- if (activeDate < line.start_date || activeDate > line.end_date) {
19827
- // If the service is not valid on this date
19828
- continue;
19829
- }
19830
- if (line[weekdayIndex] !== 1) {
19831
- // If the service is not valid on this week day
19832
- continue;
19833
- }
19834
- serviceIds.add(line['service_id']);
20185
+ for await (const rawLine of parseCsv(calendarStream, [
20186
+ 'monday',
20187
+ 'tuesday',
20188
+ 'wednesday',
20189
+ 'thursday',
20190
+ 'friday',
20191
+ 'saturday',
20192
+ 'sunday',
20193
+ 'start_date',
20194
+ 'end_date',
20195
+ ])) {
20196
+ const line = rawLine;
20197
+ if (activeDate < line.start_date || activeDate > line.end_date) {
20198
+ // If the service is not valid on this date
20199
+ continue;
19835
20200
  }
19836
- }
19837
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
19838
- finally {
19839
- try {
19840
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
20201
+ if (line[weekdayIndex] !== 1) {
20202
+ // If the service is not valid on this week day
20203
+ continue;
19841
20204
  }
19842
- finally { if (e_1) throw e_1.error; }
20205
+ serviceIds.add(line['service_id']);
19843
20206
  }
19844
- });
20207
+ };
19845
20208
  /**
19846
20209
  * Parses a gtfs calendar_dates.txt file and finds the service ids valid at a given date.
19847
20210
  *
@@ -19849,39 +20212,24 @@ const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(void 0, vo
19849
20212
  * @param date The active date, in the format "YYYYMMDD".
19850
20213
  * @param calendarDatesStream A readable stream for the GTFS calendar_dates.txt file.
19851
20214
  */
19852
- const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(void 0, void 0, void 0, function* () {
19853
- var _a, e_2, _b, _c;
20215
+ const parseCalendarDates = async (calendarDatesStream, serviceIds, date) => {
19854
20216
  const activeDate = toGtfsDate(date);
19855
- try {
19856
- for (var _d = true, _e = __asyncValues(parseCsv(calendarDatesStream, [
19857
- 'date',
19858
- 'exception_type',
19859
- ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19860
- _c = _f.value;
19861
- _d = false;
19862
- const rawLine = _c;
19863
- const line = rawLine;
19864
- if (line.date !== activeDate) {
19865
- // No rule on the active date
19866
- }
19867
- else if (line.exception_type === 2 && serviceIds.has(line.service_id)) {
19868
- // Service has been removed for the specified date.
19869
- serviceIds.delete(line.service_id);
19870
- }
19871
- else if (line.exception_type === 1) {
19872
- // Service is present on the active date
19873
- serviceIds.add(line.service_id);
19874
- }
20217
+ for await (const rawLine of parseCsv(calendarDatesStream, [
20218
+ 'date',
20219
+ 'exception_type',
20220
+ ])) {
20221
+ const line = rawLine;
20222
+ if (line.date !== activeDate) ;
20223
+ else if (line.exception_type === 2 && serviceIds.has(line.service_id)) {
20224
+ // Service has been removed for the specified date.
20225
+ serviceIds.delete(line.service_id);
19875
20226
  }
19876
- }
19877
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
19878
- finally {
19879
- try {
19880
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
20227
+ else if (line.exception_type === 1) {
20228
+ // Service is present on the active date
20229
+ serviceIds.add(line.service_id);
19881
20230
  }
19882
- finally { if (e_2) throw e_2.error; }
19883
20231
  }
19884
- });
20232
+ };
19885
20233
 
19886
20234
  /**
19887
20235
  * Parses the stops.txt file from a GTFS feed.
@@ -19889,33 +20237,30 @@ const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(
19889
20237
  * @param stopsStream The readable stream containing the stops data.
19890
20238
  * @return A mapping of stop IDs to corresponding stop details.
19891
20239
  */
19892
- const parseStops = (stopsStream) => __awaiter(void 0, void 0, void 0, function* () {
19893
- var _a, e_1, _b, _c;
20240
+ const parseStops = async (stopsStream) => {
19894
20241
  const parsedStops = new Map();
19895
20242
  let i = 0;
19896
- try {
19897
- for (var _d = true, _e = __asyncValues(parseCsv(stopsStream, [
19898
- 'stop_lat',
19899
- 'stop_lon',
19900
- 'location_type',
19901
- ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19902
- _c = _f.value;
19903
- _d = false;
19904
- const rawLine = _c;
19905
- const line = rawLine;
19906
- const stop = Object.assign(Object.assign(Object.assign({ id: i, sourceStopId: line.stop_id, name: line.stop_name, lat: line.stop_lat, lon: line.stop_lon, locationType: line.location_type
19907
- ? parseGtfsLocationType(line.location_type)
19908
- : 'SIMPLE_STOP_OR_PLATFORM' }, (line.platform_code && { platform: line.platform_code })), { children: [] }), (line.parent_station && { parentSourceId: line.parent_station }));
19909
- parsedStops.set(line.stop_id, stop);
19910
- i = i + 1;
19911
- }
19912
- }
19913
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
19914
- finally {
19915
- try {
19916
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
19917
- }
19918
- finally { if (e_1) throw e_1.error; }
20243
+ for await (const rawLine of parseCsv(stopsStream, [
20244
+ 'stop_lat',
20245
+ 'stop_lon',
20246
+ 'location_type',
20247
+ ])) {
20248
+ const line = rawLine;
20249
+ const stop = {
20250
+ id: i,
20251
+ sourceStopId: line.stop_id,
20252
+ name: line.stop_name,
20253
+ lat: line.stop_lat,
20254
+ lon: line.stop_lon,
20255
+ locationType: line.location_type
20256
+ ? parseGtfsLocationType(line.location_type)
20257
+ : 'SIMPLE_STOP_OR_PLATFORM',
20258
+ ...(line.platform_code && { platform: line.platform_code }),
20259
+ children: [],
20260
+ ...(line.parent_station && { parentSourceId: line.parent_station }),
20261
+ };
20262
+ parsedStops.set(line.stop_id, stop);
20263
+ i = i + 1;
19919
20264
  }
19920
20265
  for (const [sourceStopId, stop] of parsedStops) {
19921
20266
  if (stop.parentSourceId) {
@@ -19929,7 +20274,7 @@ const parseStops = (stopsStream) => __awaiter(void 0, void 0, void 0, function*
19929
20274
  }
19930
20275
  }
19931
20276
  return parsedStops;
19932
- });
20277
+ };
19933
20278
  const parseGtfsLocationType = (gtfsLocationType) => {
19934
20279
  switch (gtfsLocationType) {
19935
20280
  case 0:
@@ -19952,58 +20297,158 @@ const parseGtfsLocationType = (gtfsLocationType) => {
19952
20297
  * @param stopsStream The readable stream containing the stops data.
19953
20298
  * @return A mapping of stop IDs to corresponding stop details.
19954
20299
  */
19955
- const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
19956
- var _a, e_1, _b, _c;
20300
+ const parseTransfers = async (transfersStream, stopsMap) => {
19957
20301
  const transfers = new Map();
19958
- try {
19959
- for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
19960
- 'transfer_type',
19961
- 'min_transfer_time',
19962
- ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19963
- _c = _f.value;
19964
- _d = false;
19965
- const rawLine = _c;
19966
- const transferEntry = rawLine;
19967
- if (transferEntry.transfer_type === 3 ||
19968
- transferEntry.transfer_type === 5) {
19969
- continue;
19970
- }
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
- if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
19980
- console.warn(`Missing transfer origin or destination stop.`);
20302
+ const tripContinuations = [];
20303
+ for await (const rawLine of parseCsv(transfersStream, [
20304
+ 'transfer_type',
20305
+ 'min_transfer_time',
20306
+ ])) {
20307
+ const transferEntry = rawLine;
20308
+ if (transferEntry.transfer_type === 3 ||
20309
+ transferEntry.transfer_type === 5) {
20310
+ continue;
20311
+ }
20312
+ if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
20313
+ console.warn(`Missing transfer origin or destination stop.`);
20314
+ continue;
20315
+ }
20316
+ const fromStop = stopsMap.get(transferEntry.from_stop_id);
20317
+ const toStop = stopsMap.get(transferEntry.to_stop_id);
20318
+ if (!fromStop || !toStop) {
20319
+ console.warn(`Transfer references non-existent stop(s): from_stop_id=${transferEntry.from_stop_id}, to_stop_id=${transferEntry.to_stop_id}`);
20320
+ continue;
20321
+ }
20322
+ if (transferEntry.transfer_type === 4) {
20323
+ if (transferEntry.from_trip_id === undefined ||
20324
+ transferEntry.from_trip_id === '' ||
20325
+ transferEntry.to_trip_id === undefined ||
20326
+ transferEntry.to_trip_id === '') {
20327
+ console.warn(`Unsupported in-seat transfer, missing from_trip_id and/or to_trip_id.`);
19981
20328
  continue;
19982
20329
  }
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
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19987
- const fromStop = stopsMap.get(transferEntry.from_stop_id);
19988
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19989
- const toStop = stopsMap.get(transferEntry.to_stop_id);
19990
- const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
20330
+ const tripBoardingEntry = {
20331
+ fromStop: fromStop.id,
20332
+ fromTrip: transferEntry.from_trip_id,
20333
+ toStop: toStop.id,
20334
+ toTrip: transferEntry.to_trip_id,
20335
+ };
20336
+ tripContinuations.push(tripBoardingEntry);
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
+ }
20351
+ const transfer = {
20352
+ destination: toStop.id,
20353
+ type: parseGtfsTransferType(transferEntry.transfer_type),
20354
+ ...(transferEntry.min_transfer_time !== undefined && {
19991
20355
  minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
19992
- }));
19993
- const fromStopTransfers = transfers.get(fromStop.id) || [];
19994
- fromStopTransfers.push(transfer);
19995
- transfers.set(fromStop.id, fromStopTransfers);
20356
+ }),
20357
+ };
20358
+ const fromStopTransfers = transfers.get(fromStop.id) || [];
20359
+ fromStopTransfers.push(transfer);
20360
+ transfers.set(fromStop.id, fromStopTransfers);
20361
+ }
20362
+ return {
20363
+ transfers,
20364
+ tripContinuations,
20365
+ };
20366
+ };
20367
+ /**
20368
+ * Disambiguates stops involved in a transfer.
20369
+ *
20370
+ * The GTFS specification only refers to a stopId in the trip-to-trip transfers and not the
20371
+ * specific stop index in the route. For routes that have multiple stops with the same stopId,
20372
+ * we need to determine which are the from / to stop indices in respective routes.
20373
+ * We do so by picking the stop indices leading to the most coherent transfer.
20374
+ * (we pick the closest from stop index happening after the to stop index).
20375
+ */
20376
+ const disambiguateTransferStopsIndices = (fromStop, fromRoute, fromTripIndex, toStop, toRoute, toTripIndex) => {
20377
+ const fromStopIndices = fromRoute.stopRouteIndices(fromStop);
20378
+ const toStopIndices = toRoute.stopRouteIndices(toStop);
20379
+ let bestFromStopIndex;
20380
+ let bestToStopIndex;
20381
+ let bestTimeDifference = Infinity;
20382
+ for (const originStopIndex of fromStopIndices) {
20383
+ const fromArrivalTime = fromRoute.arrivalAt(originStopIndex, fromTripIndex);
20384
+ for (const toStopIndex of toStopIndices) {
20385
+ const toDepartureTime = toRoute.departureFrom(toStopIndex, toTripIndex);
20386
+ if (toDepartureTime.isAfter(fromArrivalTime)) {
20387
+ const timeDifference = toDepartureTime.toMinutes() - fromArrivalTime.toMinutes();
20388
+ if (timeDifference < bestTimeDifference) {
20389
+ bestTimeDifference = timeDifference;
20390
+ bestFromStopIndex = originStopIndex;
20391
+ bestToStopIndex = toStopIndex;
20392
+ }
20393
+ }
19996
20394
  }
19997
20395
  }
19998
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
19999
- finally {
20000
- try {
20001
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
20396
+ if (bestFromStopIndex !== undefined && bestToStopIndex !== undefined) {
20397
+ return {
20398
+ fromStopIndex: bestFromStopIndex,
20399
+ toStopIndex: bestToStopIndex,
20400
+ };
20401
+ }
20402
+ return undefined;
20403
+ };
20404
+ /**
20405
+ * Builds trip continuations map from GTFS trip continuation data.
20406
+ *
20407
+ * This function processes GTFS in-seat transfer data and creates a mapping
20408
+ * from trip boarding IDs to continuation boarding information. It disambiguates
20409
+ * stop indices when routes have multiple stops with the same ID by finding
20410
+ * the most coherent transfer timing.
20411
+ *
20412
+ * @param tripsMapping Mapping from GTFS trip IDs to internal trip representations
20413
+ * @param tripContinuations Array of GTFS trip continuation data from transfers.txt
20414
+ * @param timetable The timetable containing route and timing information
20415
+ * @param activeStopIds Set of stop IDs that are active/enabled in the system
20416
+ * @returns A map from trip boarding IDs to arrays of continuation boarding options
20417
+ */
20418
+ const buildTripContinuations = (tripsMapping, tripContinuations, timetable, activeStopIds) => {
20419
+ const continuations = new Map();
20420
+ for (const gtfsContinuation of tripContinuations) {
20421
+ if (!activeStopIds.has(gtfsContinuation.fromStop) ||
20422
+ !activeStopIds.has(gtfsContinuation.toStop)) {
20423
+ continue;
20424
+ }
20425
+ const fromTripMapping = tripsMapping.get(gtfsContinuation.fromTrip);
20426
+ const toTripMapping = tripsMapping.get(gtfsContinuation.toTrip);
20427
+ if (!fromTripMapping || !toTripMapping) {
20428
+ continue;
20429
+ }
20430
+ const fromRoute = timetable.getRoute(fromTripMapping.routeId);
20431
+ const toRoute = timetable.getRoute(toTripMapping.routeId);
20432
+ if (!fromRoute || !toRoute) {
20433
+ continue;
20434
+ }
20435
+ const bestStopIndices = disambiguateTransferStopsIndices(gtfsContinuation.fromStop, fromRoute, fromTripMapping.tripRouteIndex, gtfsContinuation.toStop, toRoute, toTripMapping.tripRouteIndex);
20436
+ if (!bestStopIndices) {
20437
+ // No valid continuation found
20438
+ continue;
20002
20439
  }
20003
- finally { if (e_1) throw e_1.error; }
20440
+ const tripBoardingId = encode(bestStopIndices.fromStopIndex, fromTripMapping.routeId, fromTripMapping.tripRouteIndex);
20441
+ const continuationBoarding = {
20442
+ hopOnStopIndex: bestStopIndices.toStopIndex,
20443
+ routeId: toTripMapping.routeId,
20444
+ tripIndex: toTripMapping.tripRouteIndex,
20445
+ };
20446
+ const existingContinuations = continuations.get(tripBoardingId) || [];
20447
+ existingContinuations.push(continuationBoarding);
20448
+ continuations.set(tripBoardingId, existingContinuations);
20004
20449
  }
20005
- return transfers;
20006
- });
20450
+ return continuations;
20451
+ };
20007
20452
  const parseGtfsTransferType = (gtfsTransferType) => {
20008
20453
  switch (gtfsTransferType) {
20009
20454
  case 0:
@@ -20065,11 +20510,13 @@ const finalizeRouteFromBuilder = (builder) => {
20065
20510
  const stopTimesArray = new Uint16Array(stopsCount * tripsCount * 2);
20066
20511
  const allPickUpTypes = [];
20067
20512
  const allDropOffTypes = [];
20513
+ const gtfsTripIds = [];
20068
20514
  for (let tripIndex = 0; tripIndex < tripsCount; tripIndex++) {
20069
20515
  const trip = builder.trips[tripIndex];
20070
20516
  if (!trip) {
20071
20517
  throw new Error(`Missing trip data at index ${tripIndex}`);
20072
20518
  }
20519
+ gtfsTripIds.push(trip.gtfsTripId);
20073
20520
  const baseIndex = tripIndex * stopsCount * 2;
20074
20521
  for (let stopIndex = 0; stopIndex < stopsCount; stopIndex++) {
20075
20522
  const timeIndex = baseIndex + stopIndex * 2;
@@ -20091,12 +20538,15 @@ const finalizeRouteFromBuilder = (builder) => {
20091
20538
  }
20092
20539
  // Use 2-bit encoding for pickup/drop-off types
20093
20540
  const pickUpDropOffTypesArray = encodePickUpDropOffTypes(allPickUpTypes, allDropOffTypes);
20094
- return {
20095
- serviceRouteId: builder.serviceRouteId,
20096
- stops: stopsArray,
20097
- stopTimes: stopTimesArray,
20098
- pickUpDropOffTypes: pickUpDropOffTypesArray,
20099
- };
20541
+ return [
20542
+ {
20543
+ serviceRouteId: builder.serviceRouteId,
20544
+ stops: stopsArray,
20545
+ stopTimes: stopTimesArray,
20546
+ pickUpDropOffTypes: pickUpDropOffTypesArray,
20547
+ },
20548
+ gtfsTripIds,
20549
+ ];
20100
20550
  };
20101
20551
  /**
20102
20552
  * Parses the trips.txt file from a GTFS feed
@@ -20106,40 +20556,28 @@ const finalizeRouteFromBuilder = (builder) => {
20106
20556
  * @param serviceRoutes A mapping of route IDs to route details.
20107
20557
  * @returns A mapping of trip IDs to corresponding route IDs.
20108
20558
  */
20109
- const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) => __awaiter(void 0, void 0, void 0, function* () {
20110
- var _a, e_1, _b, _c;
20559
+ const parseTrips = async (tripsStream, serviceIds, validGtfsRoutes) => {
20111
20560
  const trips = new Map();
20112
- try {
20113
- for (var _d = true, _e = __asyncValues(parseCsv(tripsStream, ['stop_sequence'])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20114
- _c = _f.value;
20115
- _d = false;
20116
- const rawLine = _c;
20117
- const line = rawLine;
20118
- if (!serviceIds.has(line.service_id)) {
20119
- // The trip doesn't correspond to an active service
20120
- continue;
20121
- }
20122
- if (!validGtfsRoutes.has(line.route_id)) {
20123
- // The trip doesn't correspond to a supported route
20124
- continue;
20125
- }
20126
- trips.set(line.trip_id, line.route_id);
20561
+ for await (const rawLine of parseCsv(tripsStream, ['stop_sequence'])) {
20562
+ const line = rawLine;
20563
+ if (!serviceIds.has(line.service_id)) {
20564
+ // The trip doesn't correspond to an active service
20565
+ continue;
20127
20566
  }
20128
- }
20129
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
20130
- finally {
20131
- try {
20132
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
20567
+ if (!validGtfsRoutes.has(line.route_id)) {
20568
+ // The trip doesn't correspond to a supported route
20569
+ continue;
20133
20570
  }
20134
- finally { if (e_1) throw e_1.error; }
20571
+ trips.set(line.trip_id, line.route_id);
20135
20572
  }
20136
20573
  return trips;
20137
- });
20574
+ };
20138
20575
  const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
20139
- // TODO somehow works when it's a map
20140
20576
  const stopsAdjacency = new Array(nbStops);
20141
20577
  for (let i = 0; i < nbStops; i++) {
20142
- stopsAdjacency[i] = { routes: [], transfers: [] };
20578
+ stopsAdjacency[i] = {
20579
+ routes: [],
20580
+ };
20143
20581
  }
20144
20582
  for (let index = 0; index < routes.length; index++) {
20145
20583
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -20164,7 +20602,11 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
20164
20602
  const transfer = transfers[i];
20165
20603
  if (activeStops.has(stop) || activeStops.has(transfer.destination)) {
20166
20604
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20167
- stopsAdjacency[stop].transfers.push(transfer);
20605
+ const stopAdj = stopsAdjacency[stop];
20606
+ if (!stopAdj.transfers) {
20607
+ stopAdj.transfers = [];
20608
+ }
20609
+ stopAdj.transfers.push(transfer);
20168
20610
  activeStops.add(transfer.destination);
20169
20611
  activeStops.add(stop);
20170
20612
  }
@@ -20181,9 +20623,7 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
20181
20623
  * @param activeStopIds A set of valid stop IDs.
20182
20624
  * @returns A mapping of route IDs to route details. The routes returned correspond to the set of trips from GTFS that share the same stop list.
20183
20625
  */
20184
- const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds) => __awaiter(void 0, void 0, void 0, function* () {
20185
- var _a, e_2, _b, _c;
20186
- var _d, _e;
20626
+ const parseStopTimes = async (stopTimesStream, stopsMap, activeTripIds, activeStopIds) => {
20187
20627
  /**
20188
20628
  * Adds a trip to the appropriate route builder
20189
20629
  */
@@ -20228,6 +20668,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
20228
20668
  }
20229
20669
  routeBuilder.trips.push({
20230
20670
  firstDeparture,
20671
+ gtfsTripId: currentTripId,
20231
20672
  arrivalTimes: arrivalTimes,
20232
20673
  departureTimes: departureTimes,
20233
20674
  pickUpTypes: pickUpTypes,
@@ -20250,63 +20691,62 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
20250
20691
  let pickUpTypes = [];
20251
20692
  let dropOffTypes = [];
20252
20693
  let currentTripId = undefined;
20253
- try {
20254
- for (var _f = true, _g = __asyncValues(parseCsv(stopTimesStream, ['stop_sequence'])), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
20255
- _c = _h.value;
20256
- _f = false;
20257
- const rawLine = _c;
20258
- const line = rawLine;
20259
- if (line.trip_id === currentTripId && line.stop_sequence <= previousSeq) {
20260
- console.warn(`Stop sequences not increasing for trip ${line.trip_id}: ${line.stop_sequence} > ${previousSeq}.`);
20261
- continue;
20262
- }
20263
- if (!line.arrival_time && !line.departure_time) {
20264
- console.warn(`Missing arrival or departure time for ${line.trip_id} at stop ${line.stop_id}.`);
20265
- continue;
20266
- }
20267
- if (line.pickup_type === '1' && line.drop_off_type === '1') {
20268
- continue;
20269
- }
20270
- if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
20271
- addTrip(currentTripId);
20272
- }
20273
- currentTripId = line.trip_id;
20274
- const stopData = stopsMap.get(line.stop_id);
20275
- if (!stopData) {
20276
- console.warn(`Unknown stop ID: ${line.stop_id}`);
20277
- continue;
20278
- }
20279
- stops.push(stopData.id);
20280
- const departure = (_d = line.departure_time) !== null && _d !== void 0 ? _d : line.arrival_time;
20281
- const arrival = (_e = line.arrival_time) !== null && _e !== void 0 ? _e : line.departure_time;
20282
- if (!arrival || !departure) {
20283
- console.warn(`Missing time data for ${line.trip_id} at stop ${line.stop_id}`);
20284
- continue;
20285
- }
20286
- arrivalTimes.push(toTime(arrival).toMinutes());
20287
- departureTimes.push(toTime(departure).toMinutes());
20288
- pickUpTypes.push(parsePickupDropOffType(line.pickup_type));
20289
- dropOffTypes.push(parsePickupDropOffType(line.drop_off_type));
20290
- previousSeq = line.stop_sequence;
20694
+ for await (const rawLine of parseCsv(stopTimesStream, ['stop_sequence'])) {
20695
+ const line = rawLine;
20696
+ if (line.trip_id === currentTripId && line.stop_sequence <= previousSeq) {
20697
+ console.warn(`Stop sequences not increasing for trip ${line.trip_id}: ${line.stop_sequence} > ${previousSeq}.`);
20698
+ continue;
20291
20699
  }
20292
- }
20293
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
20294
- finally {
20295
- try {
20296
- if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
20700
+ if (!line.arrival_time && !line.departure_time) {
20701
+ console.warn(`Missing arrival or departure time for ${line.trip_id} at stop ${line.stop_id}.`);
20702
+ continue;
20703
+ }
20704
+ if (line.pickup_type === '1' && line.drop_off_type === '1') {
20705
+ // Warning: could potentially lead to issues if there is an in-seat transfer
20706
+ // at this stop - it can be not boardable nor alightable but still useful for an in-seat transfer.
20707
+ // This doesn't seem to happen in practice for now so keeping this condition to save memory.
20708
+ continue;
20709
+ }
20710
+ if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
20711
+ addTrip(currentTripId);
20297
20712
  }
20298
- finally { if (e_2) throw e_2.error; }
20713
+ currentTripId = line.trip_id;
20714
+ const stopData = stopsMap.get(line.stop_id);
20715
+ if (!stopData) {
20716
+ console.warn(`Unknown stop ID: ${line.stop_id}`);
20717
+ continue;
20718
+ }
20719
+ stops.push(stopData.id);
20720
+ const departure = line.departure_time ?? line.arrival_time;
20721
+ const arrival = line.arrival_time ?? line.departure_time;
20722
+ if (!arrival || !departure) {
20723
+ console.warn(`Missing time data for ${line.trip_id} at stop ${line.stop_id}`);
20724
+ continue;
20725
+ }
20726
+ arrivalTimes.push(toTime(arrival).toMinutes());
20727
+ departureTimes.push(toTime(departure).toMinutes());
20728
+ pickUpTypes.push(parsePickupDropOffType(line.pickup_type));
20729
+ dropOffTypes.push(parsePickupDropOffType(line.drop_off_type));
20730
+ previousSeq = line.stop_sequence;
20299
20731
  }
20300
20732
  if (currentTripId) {
20301
20733
  addTrip(currentTripId);
20302
20734
  }
20303
20735
  const routesAdjacency = [];
20736
+ const tripsMapping = new Map();
20304
20737
  for (const [, routeBuilder] of routeBuilders) {
20305
- const routeData = finalizeRouteFromBuilder(routeBuilder);
20306
- routesAdjacency.push(new Route$1(routeData.stopTimes, routeData.pickUpDropOffTypes, routeData.stops, routeData.serviceRouteId));
20738
+ const [routeData, gtfsTripIds] = finalizeRouteFromBuilder(routeBuilder);
20739
+ const routeId = routesAdjacency.length;
20740
+ routesAdjacency.push(new Route$1(routeId, routeData.stopTimes, routeData.pickUpDropOffTypes, routeData.stops, routeData.serviceRouteId));
20741
+ gtfsTripIds.forEach((tripId, index) => {
20742
+ tripsMapping.set(tripId, {
20743
+ routeId,
20744
+ tripRouteIndex: index,
20745
+ });
20746
+ });
20307
20747
  }
20308
- return { routes: routesAdjacency, serviceRoutesMap };
20309
- });
20748
+ return { routes: routesAdjacency, serviceRoutesMap, tripsMapping };
20749
+ };
20310
20750
  const parsePickupDropOffType = (gtfsType) => {
20311
20751
  switch (gtfsType) {
20312
20752
  default:
@@ -20330,6 +20770,8 @@ const STOP_TIMES_FILE = 'stop_times.txt';
20330
20770
  const STOPS_FILE = 'stops.txt';
20331
20771
  const TRANSFERS_FILE = 'transfers.txt';
20332
20772
  class GtfsParser {
20773
+ path;
20774
+ profile;
20333
20775
  constructor(path, profile = standardProfile) {
20334
20776
  // TODO: support input from multiple sources
20335
20777
  this.path = path;
@@ -20341,74 +20783,81 @@ class GtfsParser {
20341
20783
  * @param date The active date.
20342
20784
  * @returns The parsed timetable.
20343
20785
  */
20344
- parseTimetable(date) {
20345
- return __awaiter(this, void 0, void 0, function* () {
20346
- log.setLevel('INFO');
20347
- const zip = new StreamZip.async({ file: this.path });
20348
- const entries = yield zip.entries();
20349
- const datetime = DateTime.fromJSDate(date);
20350
- const activeServiceIds = new Set();
20351
- const activeStopIds = new Set();
20352
- log.info(`Parsing ${STOPS_FILE}`);
20353
- const stopsStart = performance.now();
20354
- const stopsStream = yield zip.stream(STOPS_FILE);
20355
- const parsedStops = yield parseStops(stopsStream);
20356
- const stopsEnd = performance.now();
20357
- log.info(`${parsedStops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
20358
- if (entries[CALENDAR_FILE]) {
20359
- log.info(`Parsing ${CALENDAR_FILE}`);
20360
- const calendarStart = performance.now();
20361
- const calendarStream = yield zip.stream(CALENDAR_FILE);
20362
- yield parseCalendar(calendarStream, activeServiceIds, datetime);
20363
- const calendarEnd = performance.now();
20364
- log.info(`${activeServiceIds.size} valid services. (${(calendarEnd - calendarStart).toFixed(2)}ms)`);
20365
- }
20366
- if (entries[CALENDAR_DATES_FILE]) {
20367
- log.info(`Parsing ${CALENDAR_DATES_FILE}`);
20368
- const calendarDatesStart = performance.now();
20369
- const calendarDatesStream = yield zip.stream(CALENDAR_DATES_FILE);
20370
- yield parseCalendarDates(calendarDatesStream, activeServiceIds, datetime);
20371
- const calendarDatesEnd = performance.now();
20372
- log.info(`${activeServiceIds.size} valid services. (${(calendarDatesEnd - calendarDatesStart).toFixed(2)}ms)`);
20373
- }
20374
- log.info(`Parsing ${ROUTES_FILE}`);
20375
- const routesStart = performance.now();
20376
- const routesStream = yield zip.stream(ROUTES_FILE);
20377
- const validGtfsRoutes = yield parseRoutes(routesStream, this.profile);
20378
- const routesEnd = performance.now();
20379
- log.info(`${validGtfsRoutes.size} valid GTFS routes. (${(routesEnd - routesStart).toFixed(2)}ms)`);
20380
- log.info(`Parsing ${TRIPS_FILE}`);
20381
- const tripsStart = performance.now();
20382
- const tripsStream = yield zip.stream(TRIPS_FILE);
20383
- const trips = yield parseTrips(tripsStream, activeServiceIds, validGtfsRoutes);
20384
- const tripsEnd = performance.now();
20385
- log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
20386
- let transfers = new Map();
20387
- if (entries[TRANSFERS_FILE]) {
20388
- log.info(`Parsing ${TRANSFERS_FILE}`);
20389
- const transfersStart = performance.now();
20390
- const transfersStream = yield zip.stream(TRANSFERS_FILE);
20391
- transfers = yield parseTransfers(transfersStream, parsedStops);
20392
- const transfersEnd = performance.now();
20393
- log.info(`${transfers.size} valid transfers. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
20394
- }
20395
- log.info(`Parsing ${STOP_TIMES_FILE}`);
20396
- const stopTimesStart = performance.now();
20397
- const stopTimesStream = yield zip.stream(STOP_TIMES_FILE);
20398
- const { routes, serviceRoutesMap } = yield parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
20399
- const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
20400
- const stopTimesEnd = performance.now();
20401
- log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
20402
- log.info('Building stops adjacency structure');
20403
- const stopsAdjacencyStart = performance.now();
20404
- const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
20405
- const stopsAdjacencyEnd = performance.now();
20406
- log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
20407
- yield zip.close();
20408
- const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
20409
- log.info('Parsing complete.');
20410
- return timetable;
20411
- });
20786
+ async parseTimetable(date) {
20787
+ log.setLevel('INFO');
20788
+ const zip = new StreamZip.async({ file: this.path });
20789
+ const entries = await zip.entries();
20790
+ const datetime = DateTime.fromJSDate(date);
20791
+ const activeServiceIds = new Set();
20792
+ const activeStopIds = new Set();
20793
+ log.info(`Parsing ${STOPS_FILE}`);
20794
+ const stopsStart = performance.now();
20795
+ const stopsStream = await zip.stream(STOPS_FILE);
20796
+ const parsedStops = await parseStops(stopsStream);
20797
+ const stopsEnd = performance.now();
20798
+ log.info(`${parsedStops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
20799
+ if (entries[CALENDAR_FILE]) {
20800
+ log.info(`Parsing ${CALENDAR_FILE}`);
20801
+ const calendarStart = performance.now();
20802
+ const calendarStream = await zip.stream(CALENDAR_FILE);
20803
+ await parseCalendar(calendarStream, activeServiceIds, datetime);
20804
+ const calendarEnd = performance.now();
20805
+ log.info(`${activeServiceIds.size} valid services. (${(calendarEnd - calendarStart).toFixed(2)}ms)`);
20806
+ }
20807
+ if (entries[CALENDAR_DATES_FILE]) {
20808
+ log.info(`Parsing ${CALENDAR_DATES_FILE}`);
20809
+ const calendarDatesStart = performance.now();
20810
+ const calendarDatesStream = await zip.stream(CALENDAR_DATES_FILE);
20811
+ await parseCalendarDates(calendarDatesStream, activeServiceIds, datetime);
20812
+ const calendarDatesEnd = performance.now();
20813
+ log.info(`${activeServiceIds.size} valid services. (${(calendarDatesEnd - calendarDatesStart).toFixed(2)}ms)`);
20814
+ }
20815
+ log.info(`Parsing ${ROUTES_FILE}`);
20816
+ const routesStart = performance.now();
20817
+ const routesStream = await zip.stream(ROUTES_FILE);
20818
+ const validGtfsRoutes = await parseRoutes(routesStream, this.profile);
20819
+ const routesEnd = performance.now();
20820
+ log.info(`${validGtfsRoutes.size} valid GTFS routes. (${(routesEnd - routesStart).toFixed(2)}ms)`);
20821
+ log.info(`Parsing ${TRIPS_FILE}`);
20822
+ const tripsStart = performance.now();
20823
+ const tripsStream = await zip.stream(TRIPS_FILE);
20824
+ const trips = await parseTrips(tripsStream, activeServiceIds, validGtfsRoutes);
20825
+ const tripsEnd = performance.now();
20826
+ log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
20827
+ let transfers = new Map();
20828
+ let tripContinuationsMap = [];
20829
+ if (entries[TRANSFERS_FILE]) {
20830
+ log.info(`Parsing ${TRANSFERS_FILE}`);
20831
+ const transfersStart = performance.now();
20832
+ const transfersStream = await zip.stream(TRANSFERS_FILE);
20833
+ const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = await parseTransfers(transfersStream, parsedStops);
20834
+ transfers = parsedTransfers;
20835
+ tripContinuationsMap = parsedTripContinuations;
20836
+ const transfersEnd = performance.now();
20837
+ log.info(`${transfers.size} valid transfers and ${tripContinuationsMap.length} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
20838
+ }
20839
+ log.info(`Parsing ${STOP_TIMES_FILE}`);
20840
+ const stopTimesStart = performance.now();
20841
+ const stopTimesStream = await zip.stream(STOP_TIMES_FILE);
20842
+ const { routes, serviceRoutesMap, tripsMapping } = await parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
20843
+ const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
20844
+ const stopTimesEnd = performance.now();
20845
+ log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
20846
+ log.info('Building stops adjacency structure');
20847
+ const stopsAdjacencyStart = performance.now();
20848
+ const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
20849
+ const stopsAdjacencyEnd = performance.now();
20850
+ log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
20851
+ await zip.close();
20852
+ // temporary timetable for building continuations
20853
+ const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
20854
+ log.info('Building in-seat trip continuations');
20855
+ const tripContinuationsStart = performance.now();
20856
+ const tripContinuations = buildTripContinuations(tripsMapping, tripContinuationsMap, timetable, activeStopIds);
20857
+ const tripContinuationsEnd = performance.now();
20858
+ log.info(`${tripContinuations.size} in-seat trip continuations origins created. (${(tripContinuationsEnd - tripContinuationsStart).toFixed(2)}ms)`);
20859
+ log.info('Parsing complete.');
20860
+ return new Timetable(stopsAdjacency, routes, serviceRoutes, tripContinuations);
20412
20861
  }
20413
20862
  /**
20414
20863
  * Parses a GTFS feed to extract all stops.
@@ -20416,18 +20865,16 @@ class GtfsParser {
20416
20865
  * @param activeStops The set of active stop IDs to include in the index.
20417
20866
  * @returns An index of stops.
20418
20867
  */
20419
- parseStops() {
20420
- return __awaiter(this, void 0, void 0, function* () {
20421
- const zip = new StreamZip.async({ file: this.path });
20422
- log.info(`Parsing ${STOPS_FILE}`);
20423
- const stopsStart = performance.now();
20424
- const stopsStream = yield zip.stream(STOPS_FILE);
20425
- const stops = yield parseStops(stopsStream);
20426
- const stopsEnd = performance.now();
20427
- log.info(`${stops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
20428
- yield zip.close();
20429
- return new StopsIndex(Array.from(stops.values()));
20430
- });
20868
+ async parseStops() {
20869
+ const zip = new StreamZip.async({ file: this.path });
20870
+ log.info(`Parsing ${STOPS_FILE}`);
20871
+ const stopsStart = performance.now();
20872
+ const stopsStream = await zip.stream(STOPS_FILE);
20873
+ const stops = await parseStops(stopsStream);
20874
+ const stopsEnd = performance.now();
20875
+ log.info(`${stops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
20876
+ await zip.close();
20877
+ return new StopsIndex(Array.from(stops.values()));
20431
20878
  }
20432
20879
  }
20433
20880
 
@@ -20481,133 +20928,324 @@ const chGtfsProfile = {
20481
20928
  };
20482
20929
 
20483
20930
  class Plotter {
20931
+ result;
20932
+ ROUND_COLORS = [
20933
+ '#60a5fa', // Round 1
20934
+ '#ff9800', // Round 2
20935
+ '#14b8a6', // Round 3
20936
+ '#fb7185', // Round 4
20937
+ '#ffdf00', // Round 5
20938
+ '#b600ff', // Round 6
20939
+ '#ee82ee', // Round 7+
20940
+ ];
20484
20941
  constructor(result) {
20485
20942
  this.result = result;
20486
20943
  }
20487
20944
  /**
20488
- * Plots the path three as a DOT for debugging purposes.
20489
- *
20490
- * @returns A string representing the DOT graph of the path tree.
20945
+ * Gets the color for a round based on the specified palette.
20491
20946
  */
20492
- plotDotGraph() {
20493
- const earliestArrivalsPerRound = this.result.earliestArrivalsPerRound;
20494
- const dotParts = [
20495
- 'digraph PathTree {',
20496
- ' graph [overlap=false];',
20497
- ' node [shape=ellipse style=filled fillcolor=lightgrey];',
20498
- ];
20499
- earliestArrivalsPerRound.forEach((arrivalsInRound, round) => {
20500
- arrivalsInRound.forEach((tripLeg) => {
20501
- const { origin, leg } = tripLeg;
20502
- if (!leg)
20503
- return; // Skip if leg is undefined
20504
- const fromStop = this.result['stopsIndex'].findStopById(leg.from.id);
20505
- const toStop = this.result['stopsIndex'].findStopById(leg.to.id);
20506
- const originStop = this.result['stopsIndex'].findStopById(origin);
20507
- if (fromStop && toStop && originStop) {
20508
- const fromName = fromStop.platform
20509
- ? `${fromStop.name} (Pl. ${fromStop.platform})`
20510
- : fromStop.name;
20511
- const toName = toStop.platform
20512
- ? `${toStop.name} (Pl. ${toStop.platform})`
20513
- : toStop.name;
20514
- const originName = originStop.platform
20515
- ? `${originStop.name} (Pl. ${originStop.platform})`
20516
- : originStop.name;
20517
- const isVehicle = 'route' in leg;
20518
- const routeLabelContent = isVehicle
20519
- ? `${leg.route.name}\n${leg.departureTime.toString()} - ${leg.arrivalTime.toString()}`
20520
- : leg.minTransferTime
20521
- ? leg.minTransferTime.toString()
20522
- : '';
20523
- const intermediateNode = `IntermediateNode${fromStop.id}_${toStop.id}`;
20524
- const lineColor = isVehicle ? '' : ', color="red", fontcolor="red"';
20525
- const labelColor = isVehicle ? '' : ' fontcolor="red"';
20526
- dotParts.push(` "${fromName} (Origin: ${originName}) [R${round}]\n(${fromStop.id})" -> "${intermediateNode}" [shape=point${lineColor}];`);
20527
- dotParts.push(` "${intermediateNode}" [label="${routeLabelContent}" shape=rect style=filled fillcolor=white${labelColor} border=0];`);
20528
- dotParts.push(` "${intermediateNode}" -> "${toName} (Origin: ${originName}) [R${round}]\n(${toStop.id})" [${lineColor.replace(', ', '')}];`);
20529
- }
20530
- });
20531
- });
20532
- dotParts.push('}');
20533
- return dotParts.join('\n');
20947
+ getRoundColor(round) {
20948
+ if (round === 0)
20949
+ return '#888888';
20950
+ const colorIndex = Math.min(round - 1, this.ROUND_COLORS.length - 1);
20951
+ return this.ROUND_COLORS[colorIndex] ?? '#ee82ee';
20534
20952
  }
20535
- }
20536
-
20537
- class Query {
20538
- constructor(builder) {
20539
- this.from = builder.fromValue;
20540
- this.to = builder.toValue;
20541
- this.departureTime = builder.departureTimeValue;
20542
- this.options = builder.optionsValue;
20953
+ /**
20954
+ * Escapes special characters in DOT strings to prevent syntax errors.
20955
+ */
20956
+ escapeDotString(str) {
20957
+ return str
20958
+ .replace(/\\/g, '\\\\')
20959
+ .replace(/"/g, '\\"')
20960
+ .replace(/\n/g, '\\n')
20961
+ .replace(/\r/g, '\\r')
20962
+ .replace(/\t/g, '\\t');
20543
20963
  }
20544
- }
20545
- Query.Builder = class {
20546
- constructor() {
20547
- this.toValue = new Set();
20548
- // lastDepartureTimeValue?: Date;
20549
- // via: StopId[] = [];
20550
- this.optionsValue = {
20551
- maxTransfers: 5,
20552
- minTransferTime: Duration.fromSeconds(120),
20553
- transportModes: ALL_TRANSPORT_MODES,
20554
- };
20964
+ /**
20965
+ * Determines station type (origin/destination) information.
20966
+ */
20967
+ getStationInfo(stopId) {
20968
+ const isOrigin = this.result.routingState.graph[0]?.has(stopId) ?? false;
20969
+ const isDestination = this.result.routingState.destinations.includes(stopId);
20970
+ return { isOrigin, isDestination };
20555
20971
  }
20556
20972
  /**
20557
- * Sets the starting stop.
20973
+ * Formats a stop name for display, including platform information.
20558
20974
  */
20559
- from(from) {
20560
- this.fromValue = from;
20561
- return this;
20975
+ formatStopName(stopId) {
20976
+ const stop = this.result.stopsIndex.findStopById(stopId);
20977
+ if (!stop)
20978
+ return `Unknown Stop (${stopId})`;
20979
+ const escapedName = this.escapeDotString(stop.name);
20980
+ const escapedPlatform = stop.platform
20981
+ ? this.escapeDotString(stop.platform)
20982
+ : '';
20983
+ return escapedPlatform
20984
+ ? `${escapedName}\\nPl. ${escapedPlatform}`
20985
+ : escapedName;
20562
20986
  }
20563
20987
  /**
20564
- * Sets the destination stops(s), routing will stop when all the provided stops are reached.
20988
+ * Gets the appropriate fill color for a station based on its type.
20565
20989
  */
20566
- to(to) {
20567
- this.toValue = to instanceof Set ? to : new Set([to]);
20568
- return this;
20990
+ getStationFillColor(isOrigin, isDestination) {
20991
+ if (isOrigin)
20992
+ return '#60a5fa';
20993
+ if (isDestination)
20994
+ return '#ee82ee';
20995
+ return 'white';
20569
20996
  }
20570
20997
  /**
20571
- * Sets the departure time for the query.
20572
- * Note that the router will favor routes that depart shortly after the provided departure time,
20573
- * even if a later route might arrive at the same time.
20574
- * Range queries will allow to specify a range of departure times in the future.
20998
+ * Creates a DOT node for a station.
20575
20999
  */
20576
- departureTime(departureTime) {
20577
- this.departureTimeValue = departureTime;
20578
- return this;
21000
+ createStationNode(stopId) {
21001
+ const stop = this.result.stopsIndex.findStopById(stopId);
21002
+ if (!stop)
21003
+ return '';
21004
+ const displayName = this.formatStopName(stopId);
21005
+ const stopIdStr = this.escapeDotString(String(stopId));
21006
+ const nodeId = `s_${stopId}`;
21007
+ const stationInfo = this.getStationInfo(stopId);
21008
+ const fillColor = this.getStationFillColor(stationInfo.isOrigin, stationInfo.isDestination);
21009
+ return ` "${nodeId}" [label="${displayName}\\n${stopIdStr}" shape=box style=filled fillcolor="${fillColor}"];`;
20579
21010
  }
20580
21011
  /**
20581
- * Sets the maximum number of transfers allowed.
21012
+ * Creates a vehicle edge with route information oval in the middle.
20582
21013
  */
20583
- maxTransfers(maxTransfers) {
20584
- this.optionsValue.maxTransfers = maxTransfers;
20585
- return this;
21014
+ createVehicleEdge(edge, round) {
21015
+ const fromNodeId = `s_${edge.from}`;
21016
+ const toNodeId = `s_${edge.to}`;
21017
+ const roundColor = this.getRoundColor(round);
21018
+ const routeOvalId = `e_${edge.from}_${edge.to}_${edge.routeId}_${round}`;
21019
+ const route = this.result.timetable.getRoute(edge.routeId);
21020
+ const serviceRouteInfo = route
21021
+ ? this.result.timetable.getServiceRouteInfo(route)
21022
+ : null;
21023
+ const routeName = serviceRouteInfo?.name ?? `Route ${String(edge.routeId)}`;
21024
+ const routeType = serviceRouteInfo?.type || 'UNKNOWN';
21025
+ const departureTime = route
21026
+ ? route.departureFrom(edge.from, edge.tripIndex).toString()
21027
+ : 'N/A';
21028
+ const arrivalTime = edge.arrival.toString();
21029
+ const escapedRouteName = this.escapeDotString(routeName);
21030
+ const escapedRouteType = this.escapeDotString(routeType);
21031
+ const routeInfo = `${edge.routeId}:${edge.tripIndex}`;
21032
+ const ovalLabel = `${escapedRouteType} ${escapedRouteName}\\n${routeInfo}\\n${departureTime} → ${arrivalTime}`;
21033
+ return [
21034
+ ` "${routeOvalId}" [label="${ovalLabel}" shape=oval style=filled fillcolor="white" color="${roundColor}"];`,
21035
+ ` "${fromNodeId}" -> "${routeOvalId}" [color="${roundColor}"];`,
21036
+ ` "${routeOvalId}" -> "${toNodeId}" [color="${roundColor}"];`,
21037
+ ];
20586
21038
  }
20587
21039
  /**
20588
- * Sets the minimum transfer time to use when no transfer time is provided in the data.
21040
+ * Creates a transfer edge with transfer information oval in the middle.
20589
21041
  */
20590
- minTransferTime(minTransferTime) {
20591
- this.optionsValue.minTransferTime = minTransferTime;
20592
- return this;
21042
+ createTransferEdge(edge, round) {
21043
+ const fromNodeId = `s_${edge.from}`;
21044
+ const toNodeId = `s_${edge.to}`;
21045
+ const roundColor = this.getRoundColor(round);
21046
+ const transferOvalId = `e_${edge.from}_${edge.to}_${round}`;
21047
+ const transferTime = edge.minTransferTime?.toString() || 'N/A';
21048
+ const escapedTransferTime = this.escapeDotString(transferTime);
21049
+ const ovalLabel = `Transfer\\n${escapedTransferTime}`;
21050
+ return [
21051
+ ` "${transferOvalId}" [label="${ovalLabel}" shape=oval style="dashed,filled" fillcolor="white" color="${roundColor}"];`,
21052
+ ` "${fromNodeId}" -> "${transferOvalId}" [color="${roundColor}" style="dashed"];`,
21053
+ ` "${transferOvalId}" -> "${toNodeId}" [color="${roundColor}" style="dashed"];`,
21054
+ ];
20593
21055
  }
20594
21056
  /**
20595
- * Sets the transport modes to consider.
21057
+ * Creates a continuation edge to visually link trip continuations.
20596
21058
  */
20597
- transportModes(transportModes) {
20598
- this.optionsValue.transportModes = transportModes;
20599
- return this;
21059
+ createContinuationEdge(fromEdge, toEdge, round) {
21060
+ const fromStationId = `s_${fromEdge.to}`;
21061
+ const toStationId = `s_${toEdge.from}`;
21062
+ const roundColor = this.getRoundColor(round);
21063
+ const continuationOvalId = `continuation_${fromEdge.to}_${toEdge.from}_${round}`;
21064
+ const fromRoute = this.result.timetable.getRoute(fromEdge.routeId);
21065
+ const toRoute = this.result.timetable.getRoute(toEdge.routeId);
21066
+ const fromServiceRouteInfo = fromRoute
21067
+ ? this.result.timetable.getServiceRouteInfo(fromRoute)
21068
+ : null;
21069
+ const toServiceRouteInfo = toRoute
21070
+ ? this.result.timetable.getServiceRouteInfo(toRoute)
21071
+ : null;
21072
+ const fromRouteName = fromServiceRouteInfo?.name ?? `Route ${String(fromEdge.routeId)}`;
21073
+ const toRouteName = toServiceRouteInfo?.name ?? `Route ${String(toEdge.routeId)}`;
21074
+ const fromRouteType = fromServiceRouteInfo?.type || 'UNKNOWN';
21075
+ const toRouteType = toServiceRouteInfo?.type || 'UNKNOWN';
21076
+ const fromArrivalTime = fromEdge.arrival.toString();
21077
+ const toDepartureTime = toRoute
21078
+ ? toRoute.departureFrom(toEdge.from, toEdge.tripIndex).toString()
21079
+ : 'N/A';
21080
+ const escapedFromRouteName = this.escapeDotString(fromRouteName);
21081
+ const escapedToRouteName = this.escapeDotString(toRouteName);
21082
+ const escapedFromRouteType = this.escapeDotString(fromRouteType);
21083
+ const escapedToRouteType = this.escapeDotString(toRouteType);
21084
+ const fromRouteInfo = `${fromEdge.routeId}:${fromEdge.tripIndex}`;
21085
+ const toRouteInfo = `${toEdge.routeId}:${toEdge.tripIndex}`;
21086
+ const ovalLabel = `${escapedFromRouteType} ${escapedFromRouteName} (${fromRouteInfo}) ${fromArrivalTime}\\n↓\\n${escapedToRouteType} ${escapedToRouteName} (${toRouteInfo}) ${toDepartureTime}`;
21087
+ return [
21088
+ ` "${continuationOvalId}" [label="${ovalLabel}" shape=oval style="filled,bold" fillcolor="#ffffcc" color="${roundColor}" penwidth="2"];`,
21089
+ ` "${fromStationId}" -> "${continuationOvalId}" [color="${roundColor}" style="bold" penwidth="3"];`,
21090
+ ` "${continuationOvalId}" -> "${toStationId}" [color="${roundColor}" style="bold" penwidth="3"];`,
21091
+ ];
21092
+ }
21093
+ /**
21094
+ * Collects all stations and edges for the graph.
21095
+ */
21096
+ collectGraphData() {
21097
+ const stations = new Set();
21098
+ const edges = [];
21099
+ const continuationEdges = [];
21100
+ const graph = this.result.routingState.graph;
21101
+ // Collect all stops that appear in the graph
21102
+ graph.forEach((roundMap) => {
21103
+ roundMap.forEach((edge, stopId) => {
21104
+ stations.add(stopId);
21105
+ if ('from' in edge && 'to' in edge) {
21106
+ stations.add(edge.from);
21107
+ stations.add(edge.to);
21108
+ }
21109
+ });
21110
+ });
21111
+ // Create edges for each round
21112
+ graph.forEach((roundMap, round) => {
21113
+ if (round === 0) {
21114
+ // Skip round 0 as it contains only origin nodes
21115
+ return;
21116
+ }
21117
+ roundMap.forEach((edge) => {
21118
+ if ('from' in edge && 'to' in edge) {
21119
+ if ('routeId' in edge) {
21120
+ const vehicleEdgeParts = this.createVehicleEdge(edge, round);
21121
+ edges.push(...vehicleEdgeParts);
21122
+ if (edge.continuationOf) {
21123
+ let currentEdge = edge;
21124
+ let previousEdge = edge.continuationOf;
21125
+ while (previousEdge) {
21126
+ const continuationEdgeParts = this.createContinuationEdge(previousEdge, currentEdge, round);
21127
+ continuationEdges.push(...continuationEdgeParts);
21128
+ currentEdge = previousEdge;
21129
+ previousEdge = previousEdge.continuationOf;
21130
+ }
21131
+ }
21132
+ }
21133
+ else {
21134
+ const transferEdgeParts = this.createTransferEdge(edge, round);
21135
+ edges.push(...transferEdgeParts);
21136
+ }
21137
+ }
21138
+ });
21139
+ });
21140
+ edges.push(...continuationEdges);
21141
+ return { stations, edges };
20600
21142
  }
20601
- build() {
20602
- return new Query(this);
21143
+ /**
21144
+ * Plots the routing graph as a DOT graph for visualization.
21145
+ */
21146
+ plotDotGraph() {
21147
+ const { stations, edges } = this.collectGraphData();
21148
+ const dotParts = [
21149
+ 'digraph RoutingGraph {',
21150
+ ' graph [overlap=false, splines=true, rankdir=TB, bgcolor=white, nodesep=0.8, ranksep=1.2, concentrate=true];',
21151
+ ' node [fontname="Arial" margin=0.1];',
21152
+ ' edge [fontname="Arial" fontsize=10];',
21153
+ '',
21154
+ ' // Stations',
21155
+ ];
21156
+ stations.forEach((stopId) => {
21157
+ const stationNode = this.createStationNode(stopId);
21158
+ if (stationNode) {
21159
+ dotParts.push(stationNode);
21160
+ }
21161
+ });
21162
+ dotParts.push('', ' // Edges');
21163
+ dotParts.push(...edges);
21164
+ dotParts.push('}');
21165
+ return dotParts.join('\n');
20603
21166
  }
20604
- };
21167
+ }
21168
+
21169
+ class Query {
21170
+ from;
21171
+ to;
21172
+ departureTime;
21173
+ lastDepartureTime;
21174
+ options;
21175
+ constructor(builder) {
21176
+ this.from = builder.fromValue;
21177
+ this.to = builder.toValue;
21178
+ this.departureTime = builder.departureTimeValue;
21179
+ this.options = builder.optionsValue;
21180
+ }
21181
+ static Builder = class {
21182
+ fromValue;
21183
+ toValue = new Set();
21184
+ departureTimeValue;
21185
+ // lastDepartureTimeValue?: Date;
21186
+ // via: StopId[] = [];
21187
+ optionsValue = {
21188
+ maxTransfers: 5,
21189
+ minTransferTime: Duration.fromSeconds(120),
21190
+ transportModes: ALL_TRANSPORT_MODES,
21191
+ };
21192
+ /**
21193
+ * Sets the starting stop.
21194
+ */
21195
+ from(from) {
21196
+ this.fromValue = from;
21197
+ return this;
21198
+ }
21199
+ /**
21200
+ * Sets the destination stops(s), routing will stop when all the provided stops are reached.
21201
+ */
21202
+ to(to) {
21203
+ this.toValue = to instanceof Set ? to : new Set([to]);
21204
+ return this;
21205
+ }
21206
+ /**
21207
+ * Sets the departure time for the query.
21208
+ * Note that the router will favor routes that depart shortly after the provided departure time,
21209
+ * even if a later route might arrive at the same time.
21210
+ * Range queries will allow to specify a range of departure times in the future.
21211
+ */
21212
+ departureTime(departureTime) {
21213
+ this.departureTimeValue = departureTime;
21214
+ return this;
21215
+ }
21216
+ /**
21217
+ * Sets the maximum number of transfers allowed.
21218
+ */
21219
+ maxTransfers(maxTransfers) {
21220
+ this.optionsValue.maxTransfers = maxTransfers;
21221
+ return this;
21222
+ }
21223
+ /**
21224
+ * Sets the minimum transfer time to use when no transfer time is provided in the data.
21225
+ */
21226
+ minTransferTime(minTransferTime) {
21227
+ this.optionsValue.minTransferTime = minTransferTime;
21228
+ return this;
21229
+ }
21230
+ /**
21231
+ * Sets the transport modes to consider.
21232
+ */
21233
+ transportModes(transportModes) {
21234
+ this.optionsValue.transportModes = transportModes;
21235
+ return this;
21236
+ }
21237
+ build() {
21238
+ return new Query(this);
21239
+ }
21240
+ };
21241
+ }
20605
21242
 
20606
21243
  /**
20607
21244
  * Represents a resolved route consisting of multiple legs,
20608
21245
  * which can be either vehicle legs or transfer legs.
20609
21246
  */
20610
21247
  class Route {
21248
+ legs;
20611
21249
  constructor(legs) {
20612
21250
  this.legs = legs;
20613
21251
  }
@@ -20677,11 +21315,10 @@ class Route {
20677
21315
  toString() {
20678
21316
  return this.legs
20679
21317
  .map((leg, index) => {
20680
- var _a;
20681
21318
  const fromStop = `From: ${leg.from.name}${leg.from.platform ? ` (Pl. ${leg.from.platform})` : ''}`;
20682
21319
  const toStop = `To: ${leg.to.name}${leg.to.platform ? ` (Pl. ${leg.to.platform})` : ''}`;
20683
21320
  const transferDetails = 'minTransferTime' in leg
20684
- ? `Minimum Transfer Time: ${(_a = leg.minTransferTime) === null || _a === void 0 ? void 0 : _a.toString()}`
21321
+ ? `Minimum Transfer Time: ${leg.minTransferTime?.toString()}`
20685
21322
  : '';
20686
21323
  const travelDetails = 'route' in leg && 'departureTime' in leg && 'arrivalTime' in leg
20687
21324
  ? `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`
@@ -20717,9 +21354,14 @@ class Route {
20717
21354
  };
20718
21355
  }
20719
21356
  else {
20720
- return Object.assign({ from: leg.from.sourceStopId, to: leg.to.sourceStopId, type: leg.type }, (leg.minTransferTime !== undefined && {
20721
- minTransferTime: leg.minTransferTime.toString(),
20722
- }));
21357
+ return {
21358
+ from: leg.from.sourceStopId,
21359
+ to: leg.to.sourceStopId,
21360
+ type: leg.type,
21361
+ ...(leg.minTransferTime !== undefined && {
21362
+ minTransferTime: leg.minTransferTime.toString(),
21363
+ }),
21364
+ };
20723
21365
  }
20724
21366
  });
20725
21367
  return jsonLegs;
@@ -20727,11 +21369,15 @@ class Route {
20727
21369
  }
20728
21370
 
20729
21371
  class Result {
20730
- constructor(query, earliestArrivals, earliestArrivalsPerRound, stopsIndex) {
21372
+ query;
21373
+ routingState;
21374
+ stopsIndex;
21375
+ timetable;
21376
+ constructor(query, routingState, stopsIndex, timetable) {
20731
21377
  this.query = query;
20732
- this.earliestArrivals = earliestArrivals;
20733
- this.earliestArrivalsPerRound = earliestArrivalsPerRound;
21378
+ this.routingState = routingState;
20734
21379
  this.stopsIndex = stopsIndex;
21380
+ this.timetable = timetable;
20735
21381
  }
20736
21382
  /**
20737
21383
  * Reconstructs the best route to a stop.
@@ -20741,17 +21387,17 @@ class Result {
20741
21387
  * @returns a route to the destination stop if it exists.
20742
21388
  */
20743
21389
  bestRoute(to) {
20744
- var _a, _b, _c;
20745
21390
  const destinationList = to instanceof Set
20746
21391
  ? Array.from(to)
20747
21392
  : to
20748
21393
  ? [to]
20749
21394
  : Array.from(this.query.to);
20750
21395
  const destinations = destinationList.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
21396
+ // find the first reached destination
20751
21397
  let fastestDestination = undefined;
20752
21398
  let fastestTime = undefined;
20753
21399
  for (const destination of destinations) {
20754
- const arrivalTime = this.earliestArrivals.get(destination.id);
21400
+ const arrivalTime = this.routingState.earliestArrivals.get(destination.id);
20755
21401
  if (arrivalTime !== undefined) {
20756
21402
  if (fastestTime === undefined ||
20757
21403
  arrivalTime.arrival.isBefore(fastestTime.arrival)) {
@@ -20766,19 +21412,84 @@ class Result {
20766
21412
  const route = [];
20767
21413
  let currentStop = fastestDestination;
20768
21414
  let round = fastestTime.legNumber;
20769
- while (fastestTime.origin !== currentStop) {
20770
- const tripLeg = (_a = this.earliestArrivalsPerRound[round]) === null || _a === void 0 ? void 0 : _a.get(currentStop);
20771
- if (!(tripLeg === null || tripLeg === void 0 ? void 0 : tripLeg.leg)) {
20772
- throw new Error(`No leg found for a trip leg: start stop=${(_c = (_b = tripLeg === null || tripLeg === void 0 ? void 0 : tripLeg.leg) === null || _b === void 0 ? void 0 : _b.from.id) !== null && _c !== void 0 ? _c : 'unknown'}, end stop=${currentStop}, round=${round}, origin=${fastestTime.origin}`);
21415
+ while (round > 0) {
21416
+ const edge = this.routingState.graph[round]?.get(currentStop);
21417
+ if (!edge) {
21418
+ throw new Error(`No edge arriving at stop ${currentStop} at round ${round}`);
20773
21419
  }
20774
- route.unshift(tripLeg.leg);
20775
- currentStop = tripLeg.leg.from.id;
20776
- if ('route' in tripLeg.leg) {
21420
+ let leg;
21421
+ if ('routeId' in edge) {
21422
+ let vehicleEdge = edge;
21423
+ // Handle leg reconstruction for in-seat trip continuations
21424
+ const chainedEdges = [vehicleEdge];
21425
+ while ('routeId' in vehicleEdge && vehicleEdge.continuationOf) {
21426
+ chainedEdges.push(vehicleEdge.continuationOf);
21427
+ vehicleEdge = vehicleEdge.continuationOf;
21428
+ }
21429
+ leg = this.buildVehicleLeg(chainedEdges);
21430
+ }
21431
+ else if ('type' in edge) {
21432
+ leg = this.buildTransferLeg(edge);
21433
+ }
21434
+ else {
21435
+ break;
21436
+ }
21437
+ route.unshift(leg);
21438
+ currentStop = leg.from.id;
21439
+ if ('routeId' in edge) {
20777
21440
  round -= 1;
20778
21441
  }
20779
21442
  }
20780
21443
  return new Route(route);
20781
21444
  }
21445
+ /**
21446
+ * Builds a vehicle leg from a chain of vehicle edges.
21447
+ *
21448
+ * @param edges Array of vehicle edges representing continuous trips on transit vehicles
21449
+ * @returns A vehicle leg with departure/arrival information and route details
21450
+ * @throws Error if the edges array is empty
21451
+ */
21452
+ buildVehicleLeg(edges) {
21453
+ if (edges.length === 0) {
21454
+ throw new Error('Cannot build vehicle leg from empty edges');
21455
+ }
21456
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21457
+ const firstEdge = edges[edges.length - 1];
21458
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21459
+ const lastEdge = edges[0];
21460
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21461
+ const firstRoute = this.timetable.getRoute(firstEdge.routeId);
21462
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21463
+ const lastRoute = this.timetable.getRoute(lastEdge.routeId);
21464
+ return {
21465
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21466
+ from: this.stopsIndex.findStopById(firstRoute.stopId(firstEdge.from)),
21467
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21468
+ to: this.stopsIndex.findStopById(lastRoute.stopId(lastEdge.to)),
21469
+ // The route info comes from the first boarded route in case on continuous trips
21470
+ route: this.timetable.getServiceRouteInfo(firstRoute),
21471
+ departureTime: firstRoute.departureFrom(firstEdge.from, firstEdge.tripIndex),
21472
+ arrivalTime: lastEdge.arrival,
21473
+ pickUpType: firstRoute.pickUpTypeFrom(firstEdge.from, firstEdge.tripIndex),
21474
+ dropOffType: lastRoute.dropOffTypeAt(lastEdge.to, lastEdge.tripIndex),
21475
+ };
21476
+ }
21477
+ /**
21478
+ * Builds a transfer leg from a transfer edge.
21479
+ *
21480
+ * @param edge Transfer edge representing a walking connection between stops
21481
+ * @returns A transfer leg with from/to stops and transfer details
21482
+ */
21483
+ buildTransferLeg(edge) {
21484
+ return {
21485
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21486
+ from: this.stopsIndex.findStopById(edge.from),
21487
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21488
+ to: this.stopsIndex.findStopById(edge.to),
21489
+ minTransferTime: edge.minTransferTime,
21490
+ type: edge.type,
21491
+ };
21492
+ }
20782
21493
  /**
20783
21494
  * Returns the arrival time at any stop reachable in less time / transfers than the destination(s) of the query)
20784
21495
  *
@@ -20789,12 +21500,25 @@ class Result {
20789
21500
  arrivalAt(stop, maxTransfers) {
20790
21501
  const equivalentStops = this.stopsIndex.equivalentStops(stop);
20791
21502
  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
21503
  for (const equivalentStop of equivalentStops) {
20797
- const arrivalTime = relevantArrivals.get(equivalentStop.id);
21504
+ let arrivalTime;
21505
+ if (maxTransfers === undefined) {
21506
+ arrivalTime = this.routingState.earliestArrivals.get(equivalentStop.id);
21507
+ }
21508
+ else {
21509
+ // We have no guarantee that the stop was visited in the last round,
21510
+ // so we need to check all rounds if it's not found in the last one.
21511
+ for (let i = maxTransfers + 1; i >= 0; i--) {
21512
+ const arrivalEdge = this.routingState.graph[i]?.get(equivalentStop.id);
21513
+ if (arrivalEdge !== undefined) {
21514
+ arrivalTime = {
21515
+ arrival: arrivalEdge.arrival,
21516
+ legNumber: i,
21517
+ };
21518
+ break;
21519
+ }
21520
+ }
21521
+ }
20798
21522
  if (arrivalTime !== undefined) {
20799
21523
  if (earliestArrival === undefined ||
20800
21524
  arrivalTime.arrival.isBefore(earliestArrival.arrival)) {
@@ -20808,36 +21532,237 @@ class Result {
20808
21532
 
20809
21533
  const UNREACHED = Time.infinity();
20810
21534
  /**
20811
- * A public transportation network router implementing the RAPTOR algorithm for
20812
- * efficient journey planning and routing. For more information on the RAPTOR
20813
- * algorithm, refer to its detailed explanation in the research paper:
21535
+ * A public transportation router implementing the RAPTOR algorithm.
21536
+ * For more information on the RAPTOR algorithm,
21537
+ * refer to its detailed explanation in the research paper:
20814
21538
  * https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
20815
21539
  */
20816
21540
  class Router {
21541
+ timetable;
21542
+ stopsIndex;
20817
21543
  constructor(timetable, stopsIndex) {
20818
21544
  this.timetable = timetable;
20819
21545
  this.stopsIndex = stopsIndex;
20820
21546
  }
20821
21547
  /**
20822
- * Evaluates possible transfers for a given query on a transport
20823
- * network, updating the earliest arrivals at various stops and marking new
20824
- * stops that can be reached through these transfers.
21548
+ * The main Raptor algorithm implementation.
21549
+ *
21550
+ * @param query The query containing the main parameters for the routing.
21551
+ * @returns A result object containing data structures allowing to reconstruct routes and .
21552
+ */
21553
+ route(query) {
21554
+ const routingState = this.initRoutingState(query);
21555
+ const markedStops = new Set();
21556
+ for (const originStop of routingState.graph[0].keys()) {
21557
+ markedStops.add(originStop);
21558
+ }
21559
+ // Initial transfer consideration for origins
21560
+ const newlyMarkedStops = this.considerTransfers(query, 0, markedStops, routingState);
21561
+ for (const newStop of newlyMarkedStops) {
21562
+ markedStops.add(newStop);
21563
+ }
21564
+ for (let round = 1; round <= query.options.maxTransfers + 1; round++) {
21565
+ const edgesAtCurrentRound = new Map();
21566
+ routingState.graph.push(edgesAtCurrentRound);
21567
+ const reachableRoutes = this.timetable.findReachableRoutes(markedStops, query.options.transportModes);
21568
+ markedStops.clear();
21569
+ // for each route that can be reached with at least round - 1 trips
21570
+ for (const [route, hopOnStopIndex] of reachableRoutes) {
21571
+ const newlyMarkedStops = this.scanRoute(route, hopOnStopIndex, round, routingState);
21572
+ for (const newStop of newlyMarkedStops) {
21573
+ markedStops.add(newStop);
21574
+ }
21575
+ }
21576
+ // process in-seat trip continuations
21577
+ let continuations = this.findTripContinuations(markedStops, edgesAtCurrentRound);
21578
+ while (continuations.length > 0) {
21579
+ const stopsFromContinuations = new Set();
21580
+ for (const continuation of continuations) {
21581
+ const route = this.timetable.getRoute(continuation.routeId);
21582
+ const routeScanResults = this.scanRoute(route, continuation.hopOnStopIndex, round, routingState, continuation);
21583
+ for (const newStop of routeScanResults) {
21584
+ stopsFromContinuations.add(newStop);
21585
+ }
21586
+ }
21587
+ for (const newStop of stopsFromContinuations) {
21588
+ markedStops.add(newStop);
21589
+ }
21590
+ continuations = this.findTripContinuations(stopsFromContinuations, edgesAtCurrentRound);
21591
+ }
21592
+ const newlyMarkedStops = this.considerTransfers(query, round, markedStops, routingState);
21593
+ for (const newStop of newlyMarkedStops) {
21594
+ markedStops.add(newStop);
21595
+ }
21596
+ if (markedStops.size === 0)
21597
+ break;
21598
+ }
21599
+ return new Result(query, routingState, this.stopsIndex, this.timetable);
21600
+ }
21601
+ /**
21602
+ * Finds trip continuations for the given marked stops and edges at the current round.
21603
+ * @param markedStops The set of marked stops.
21604
+ * @param edgesAtCurrentRound The map of edges at the current round.
21605
+ * @returns An array of trip continuations.
21606
+ */
21607
+ findTripContinuations(markedStops, edgesAtCurrentRound) {
21608
+ const continuations = [];
21609
+ for (const stopId of markedStops) {
21610
+ const arrival = edgesAtCurrentRound.get(stopId);
21611
+ if (!arrival || !('routeId' in arrival))
21612
+ continue;
21613
+ const continuousTrips = this.timetable.getContinuousTrips(arrival.to, arrival.routeId, arrival.tripIndex);
21614
+ for (let i = 0; i < continuousTrips.length; i++) {
21615
+ const trip = continuousTrips[i];
21616
+ continuations.push({
21617
+ routeId: trip.routeId,
21618
+ hopOnStopIndex: trip.hopOnStopIndex,
21619
+ tripIndex: trip.tripIndex,
21620
+ previousEdge: arrival,
21621
+ });
21622
+ }
21623
+ }
21624
+ return continuations;
21625
+ }
21626
+ /**
21627
+ * Initializes the routing state for the RAPTOR algorithm.
21628
+ *
21629
+ * This method sets up the initial data structures needed for route planning,
21630
+ * including origin and destination stops (considering equivalent stops),
21631
+ * earliest arrival times, and marked stops for processing.
21632
+ *
21633
+ * @param query The routing query containing origin, destination, and departure time
21634
+ * @returns The initialized routing state with all necessary data structures
20825
21635
  */
20826
- considerTransfers(query, markedStops, arrivalsAtCurrentRound, earliestArrivals, round) {
20827
- var _a, _b;
21636
+ initRoutingState(query) {
21637
+ const { from, to, departureTime } = query;
21638
+ // Consider children or siblings of the "from" stop as potential origins
21639
+ const origins = this.stopsIndex
21640
+ .equivalentStops(from)
21641
+ .map((origin) => origin.id);
21642
+ // Consider children or siblings of the "to" stop(s) as potential destinations
21643
+ const destinations = Array.from(to)
21644
+ .flatMap((destination) => this.stopsIndex.equivalentStops(destination))
21645
+ .map((destination) => destination.id);
21646
+ const earliestArrivals = new Map();
21647
+ const earliestArrivalsWithoutAnyLeg = new Map();
21648
+ const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
21649
+ const initialState = {
21650
+ arrival: departureTime,
21651
+ legNumber: 0,
21652
+ };
21653
+ for (const originStop of origins) {
21654
+ earliestArrivals.set(originStop, initialState);
21655
+ earliestArrivalsWithoutAnyLeg.set(originStop, initialState);
21656
+ }
21657
+ return {
21658
+ destinations,
21659
+ earliestArrivals,
21660
+ graph: earliestArrivalsPerRound,
21661
+ };
21662
+ }
21663
+ /**
21664
+ * Scans a route to find the earliest possible trips (if not provided) and updates arrival times.
21665
+ *
21666
+ * This method implements the core route scanning logic of the RAPTOR algorithm.
21667
+ * It iterates through all stops on a given route starting from the hop-on stop,
21668
+ * maintaining the current best trip and updating arrival times when improvements
21669
+ * are found. The method also handles boarding new trips when earlier departures
21670
+ * are available if no given trip is provided as a parameter.
21671
+ *
21672
+ * @param route The route to scan for possible trips
21673
+ * @param hopOnStopIndex The stop index where passengers can board the route
21674
+ * @param round The current round number in the RAPTOR algorithm
21675
+ * @param routingState The current routing state containing arrival times and marked stops
21676
+ */
21677
+ scanRoute(route, hopOnStopIndex, round, routingState, tripContinuation) {
21678
+ const newlyMarkedStops = new Set();
21679
+ let activeTrip = tripContinuation
21680
+ ? {
21681
+ routeId: route.id,
21682
+ hopOnStopIndex,
21683
+ tripIndex: tripContinuation.tripIndex,
21684
+ }
21685
+ : undefined;
21686
+ const edgesAtCurrentRound = routingState.graph[round];
21687
+ const edgesAtPreviousRound = routingState.graph[round - 1];
21688
+ // Compute target pruning criteria only once per route
21689
+ const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState.earliestArrivals, routingState.destinations);
21690
+ for (let currentStopIndex = hopOnStopIndex; currentStopIndex < route.getNbStops(); currentStopIndex++) {
21691
+ const currentStop = route.stops[currentStopIndex];
21692
+ // If we're currently on a trip,
21693
+ // check if arrival at the stop improves the earliest arrival time
21694
+ if (activeTrip !== undefined) {
21695
+ const arrivalTime = route.arrivalAt(currentStopIndex, activeTrip.tripIndex);
21696
+ const dropOffType = route.dropOffTypeAt(currentStopIndex, activeTrip.tripIndex);
21697
+ const earliestArrivalAtCurrentStop = routingState.earliestArrivals.get(currentStop)?.arrival ?? UNREACHED;
21698
+ if (dropOffType !== 'NOT_AVAILABLE' &&
21699
+ arrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
21700
+ arrivalTime.isBefore(earliestArrivalAtAnyDestination)) {
21701
+ const edge = {
21702
+ arrival: arrivalTime,
21703
+ routeId: route.id,
21704
+ tripIndex: activeTrip.tripIndex,
21705
+ from: activeTrip.hopOnStopIndex,
21706
+ to: currentStopIndex,
21707
+ };
21708
+ if (tripContinuation) {
21709
+ // In case of continuous trip, we set a pointer to the previous edge
21710
+ edge.continuationOf = tripContinuation.previousEdge;
21711
+ }
21712
+ edgesAtCurrentRound.set(currentStop, edge);
21713
+ routingState.earliestArrivals.set(currentStop, {
21714
+ arrival: arrivalTime,
21715
+ legNumber: round,
21716
+ });
21717
+ newlyMarkedStops.add(currentStop);
21718
+ }
21719
+ }
21720
+ if (tripContinuation) {
21721
+ // If it's a trip continuation, no need to check for earlier trips
21722
+ continue;
21723
+ }
21724
+ // check if we can board an earlier trip at the current stop
21725
+ // if there was no current trip, find the first one reachable
21726
+ const earliestArrivalOnPreviousRound = edgesAtPreviousRound.get(currentStop)?.arrival;
21727
+ // TODO if the last edge is not a transfer, and if there is no trip continuation of type 1 (guaranteed)
21728
+ // Add the minTransferTime to make sure there's at least 2 minutes to transfer.
21729
+ // If platforms are collapsed, make sure to apply the station level transfer time
21730
+ // (or later at route reconstruction time)
21731
+ if (earliestArrivalOnPreviousRound !== undefined &&
21732
+ (activeTrip === undefined ||
21733
+ earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStopIndex, activeTrip.tripIndex)) ||
21734
+ earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStopIndex, activeTrip.tripIndex)))) {
21735
+ const earliestTrip = route.findEarliestTrip(currentStopIndex, earliestArrivalOnPreviousRound, activeTrip?.tripIndex);
21736
+ if (earliestTrip !== undefined) {
21737
+ activeTrip = {
21738
+ routeId: route.id,
21739
+ tripIndex: earliestTrip,
21740
+ hopOnStopIndex: currentStopIndex,
21741
+ };
21742
+ }
21743
+ }
21744
+ }
21745
+ return newlyMarkedStops;
21746
+ }
21747
+ /**
21748
+ * Processes all currently marked stops to find available transfers
21749
+ * and determines if using these transfers would result in earlier arrival times
21750
+ * at destination stops. It handles different transfer types including in-seat
21751
+ * transfers and walking transfers with appropriate minimum transfer times.
21752
+ *
21753
+ * @param query The routing query containing transfer options and constraints
21754
+ * @param round The current round number in the RAPTOR algorithm
21755
+ * @param routingState The current routing state containing arrival times and marked stops
21756
+ */
21757
+ considerTransfers(query, round, markedStops, routingState) {
20828
21758
  const { options } = query;
21759
+ const arrivalsAtCurrentRound = routingState.graph[round];
20829
21760
  const newlyMarkedStops = new Set();
20830
- const markedStopsArray = Array.from(markedStops);
20831
- for (let i = 0; i < markedStopsArray.length; i++) {
20832
- const stop = markedStopsArray[i];
21761
+ for (const stop of markedStops) {
20833
21762
  const currentArrival = arrivalsAtCurrentRound.get(stop);
20834
- if (!currentArrival)
20835
- continue;
20836
21763
  // Skip transfers if the last leg was also a transfer
20837
- const previousLeg = currentArrival.leg;
20838
- if (previousLeg && !('route' in previousLeg)) {
21764
+ if (!currentArrival || 'type' in currentArrival)
20839
21765
  continue;
20840
- }
20841
21766
  const transfers = this.timetable.getTransfers(stop);
20842
21767
  for (let j = 0; j < transfers.length; j++) {
20843
21768
  const transfer = transfers[j];
@@ -20846,40 +21771,32 @@ class Router {
20846
21771
  transferTime = transfer.minTransferTime;
20847
21772
  }
20848
21773
  else if (transfer.type === 'IN_SEAT') {
21774
+ // TODO not needed anymore now that trip continuations are handled separately
20849
21775
  transferTime = Duration.zero();
20850
21776
  }
20851
21777
  else {
20852
21778
  transferTime = options.minTransferTime;
20853
21779
  }
20854
21780
  const arrivalAfterTransfer = currentArrival.arrival.plus(transferTime);
20855
- const originalArrival = (_b = (_a = arrivalsAtCurrentRound.get(transfer.destination)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21781
+ const originalArrival = arrivalsAtCurrentRound.get(transfer.destination)?.arrival ??
21782
+ UNREACHED;
20856
21783
  if (arrivalAfterTransfer.isBefore(originalArrival)) {
20857
- const origin = currentArrival.origin;
20858
21784
  arrivalsAtCurrentRound.set(transfer.destination, {
20859
21785
  arrival: arrivalAfterTransfer,
20860
- legNumber: round,
20861
- origin: origin,
20862
- leg: {
20863
- from: this.stopsIndex.findStopById(stop),
20864
- to: this.stopsIndex.findStopById(transfer.destination),
20865
- minTransferTime: transfer.minTransferTime,
20866
- type: transfer.type,
20867
- },
21786
+ from: stop,
21787
+ to: transfer.destination,
21788
+ minTransferTime: transfer.minTransferTime,
21789
+ type: transfer.type,
20868
21790
  });
20869
- earliestArrivals.set(transfer.destination, {
21791
+ routingState.earliestArrivals.set(transfer.destination, {
20870
21792
  arrival: arrivalAfterTransfer,
20871
21793
  legNumber: round,
20872
- origin: origin,
20873
21794
  });
20874
21795
  newlyMarkedStops.add(transfer.destination);
20875
21796
  }
20876
21797
  }
20877
21798
  }
20878
- const newlyMarkedStopsArray = Array.from(newlyMarkedStops);
20879
- for (let i = 0; i < newlyMarkedStopsArray.length; i++) {
20880
- const newStop = newlyMarkedStopsArray[i];
20881
- markedStops.add(newStop);
20882
- }
21799
+ return newlyMarkedStops;
20883
21800
  }
20884
21801
  /**
20885
21802
  * Finds the earliest arrival time at any stop from a given set of destinations.
@@ -20889,121 +21806,14 @@ class Router {
20889
21806
  * @returns The earliest arrival time among the provided destinations.
20890
21807
  */
20891
21808
  earliestArrivalAtAnyStop(earliestArrivals, destinations) {
20892
- var _a, _b;
20893
21809
  let earliestArrivalAtAnyDestination = UNREACHED;
20894
21810
  for (let i = 0; i < destinations.length; i++) {
20895
21811
  const destination = destinations[i];
20896
- const arrival = (_b = (_a = earliestArrivals.get(destination.id)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21812
+ const arrival = earliestArrivals.get(destination)?.arrival ?? UNREACHED;
20897
21813
  earliestArrivalAtAnyDestination = Time.min(earliestArrivalAtAnyDestination, arrival);
20898
21814
  }
20899
21815
  return earliestArrivalAtAnyDestination;
20900
21816
  }
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
21817
  }
21008
21818
 
21009
21819
  /**
@@ -21140,7 +21950,6 @@ const startRepl = (stopsPath, timetablePath) => {
21140
21950
  replServer.defineCommand('route', {
21141
21951
  help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
21142
21952
  action(routeQuery) {
21143
- var _a;
21144
21953
  this.clearBufferedCommand();
21145
21954
  const parts = routeQuery.split(' ').filter(Boolean);
21146
21955
  const withTransfersIndex = parts.indexOf('with');
@@ -21196,7 +22005,7 @@ const startRepl = (stopsPath, timetablePath) => {
21196
22005
  console.log(`Destination not reachable`);
21197
22006
  }
21198
22007
  else {
21199
- console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${(_a = stopsIndex.findStopById(arrivalTime.origin)) === null || _a === void 0 ? void 0 : _a.name}.`);
22008
+ console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${fromStop.name}.`);
21200
22009
  }
21201
22010
  const bestRoute = result.bestRoute(toStop.sourceStopId);
21202
22011
  if (bestRoute) {
@@ -21277,6 +22086,192 @@ const startRepl = (stopsPath, timetablePath) => {
21277
22086
  this.displayPrompt();
21278
22087
  },
21279
22088
  });
22089
+ const formatPickupDropoffType = (type) => {
22090
+ switch (type) {
22091
+ case 'REGULAR':
22092
+ return 'R';
22093
+ case 'NOT_AVAILABLE':
22094
+ return 'N';
22095
+ case 'MUST_PHONE_AGENCY':
22096
+ return 'A';
22097
+ case 'MUST_COORDINATE_WITH_DRIVER':
22098
+ return 'D';
22099
+ default:
22100
+ return '?';
22101
+ }
22102
+ };
22103
+ replServer.defineCommand('inspect', {
22104
+ help: 'Inspect a route or stop using .inspect route <routeId> or .inspect stop <stopId>',
22105
+ action(inspectQuery) {
22106
+ this.clearBufferedCommand();
22107
+ const parts = inspectQuery.trim().split(' ');
22108
+ if (parts.length !== 2) {
22109
+ console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
22110
+ this.displayPrompt();
22111
+ return;
22112
+ }
22113
+ const [type, idStr] = parts;
22114
+ if (type !== 'route' && type !== 'stop') {
22115
+ console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
22116
+ this.displayPrompt();
22117
+ return;
22118
+ }
22119
+ const inspectRoute = (routeIdStr) => {
22120
+ const routeId = parseInt(routeIdStr.trim());
22121
+ if (isNaN(routeId)) {
22122
+ console.log('Usage: .inspect route <routeId>');
22123
+ return;
22124
+ }
22125
+ const route = timetable.getRoute(routeId);
22126
+ if (!route) {
22127
+ console.log(`Route ${routeId} not found`);
22128
+ return;
22129
+ }
22130
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
22131
+ const routeName = serviceRouteInfo.name;
22132
+ const routeType = serviceRouteInfo.type;
22133
+ console.log(`\n=== Route ${routeId} ===`);
22134
+ console.log(`Service Route: ${routeName}`);
22135
+ console.log(`Type: ${routeType}`);
22136
+ console.log(`Number of stops: ${route.getNbStops()}`);
22137
+ console.log(`Number of trips: ${route.getNbTrips()}`);
22138
+ console.log('\n--- Stops ---');
22139
+ for (let i = 0; i < route.stops.length; i++) {
22140
+ const stopId = route.stopId(i);
22141
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22142
+ const stop = stopsIndex.findStopById(stopId);
22143
+ const platform = stop.platform ? ` (Pl. ${stop.platform})` : '';
22144
+ console.log(`${i + 1}. ${stop.name}${platform} (${stopId}, ${stop.sourceStopId})`);
22145
+ }
22146
+ console.log('\n--- Trips ---');
22147
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
22148
+ console.log(`\nTrip ${tripIndex}:`);
22149
+ for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
22150
+ const stopId = route.stopId(stopIndex);
22151
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22152
+ const stop = stopsIndex.findStopById(stopId);
22153
+ const departure = route.departureFrom(stopIndex, tripIndex);
22154
+ const arrival = route.arrivalAt(stopIndex, tripIndex);
22155
+ const pickupType = route.pickUpTypeFrom(stopIndex, tripIndex);
22156
+ const dropOffType = route.dropOffTypeAt(stopIndex, tripIndex);
22157
+ const pickupStr = formatPickupDropoffType(pickupType);
22158
+ const dropOffStr = formatPickupDropoffType(dropOffType);
22159
+ console.log(` ${stopIndex + 1}. ${stop.name}: arr ${arrival.toString()} (${pickupStr}) → dep ${departure.toString()} (${dropOffStr})`);
22160
+ }
22161
+ }
22162
+ console.log();
22163
+ };
22164
+ const inspectStop = (stopIdStr) => {
22165
+ let stop;
22166
+ const stopBySourceId = stopsIndex.findStopBySourceStopId(stopIdStr);
22167
+ if (stopBySourceId !== undefined) {
22168
+ stop = stopBySourceId;
22169
+ }
22170
+ else if (!isNaN(Number(stopIdStr))) {
22171
+ const stopById = stopsIndex.findStopById(Number(stopIdStr));
22172
+ if (stopById !== undefined) {
22173
+ stop = stopById;
22174
+ }
22175
+ }
22176
+ else {
22177
+ const stops = stopsIndex.findStopsByName(stopIdStr);
22178
+ if (stops.length > 0) {
22179
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22180
+ stop = stops[0];
22181
+ }
22182
+ }
22183
+ if (!stop) {
22184
+ console.log(`Stop not found: ${stopIdStr}`);
22185
+ return;
22186
+ }
22187
+ console.log(`\n=== Stop ${stop.id} ===`);
22188
+ console.log(`Name: ${stop.name}`);
22189
+ if (stop.platform) {
22190
+ console.log(`Platform: ${stop.platform}`);
22191
+ }
22192
+ console.log(`Source ID: ${stop.sourceStopId}`);
22193
+ const routes = timetable.routesPassingThrough(stop.id);
22194
+ console.log(`Number of routes: ${routes.length}`);
22195
+ const equivalentStops = stopsIndex
22196
+ .equivalentStops(stop.sourceStopId)
22197
+ .filter((equivStop) => equivStop.id !== stop.id);
22198
+ console.log(`Number of equivalent stops: ${equivalentStops.length}`);
22199
+ if (equivalentStops.length > 0) {
22200
+ console.log('\n--- Equivalent Stops ---');
22201
+ equivalentStops.forEach((equivStop, index) => {
22202
+ const platform = equivStop.platform
22203
+ ? ` (Pl. ${equivStop.platform})`
22204
+ : '';
22205
+ console.log(`${index + 1}. ${equivStop.name}${platform} (${equivStop.id}, ${equivStop.sourceStopId})`);
22206
+ });
22207
+ }
22208
+ if (routes.length > 0) {
22209
+ console.log('\n--- Routes ---');
22210
+ routes.forEach((route, index) => {
22211
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
22212
+ console.log(`${index + 1}. Route ${route.id}: ${serviceRouteInfo.name} (${serviceRouteInfo.type})`);
22213
+ });
22214
+ }
22215
+ const transfers = timetable.getTransfers(stop.id);
22216
+ console.log(`Number of transfers: ${transfers.length}`);
22217
+ if (transfers.length > 0) {
22218
+ console.log('\n--- Transfers ---');
22219
+ transfers.forEach((transfer, index) => {
22220
+ const destStop = stopsIndex.findStopById(transfer.destination);
22221
+ const platform = destStop?.platform
22222
+ ? ` (Pl. ${destStop.platform})`
22223
+ : '';
22224
+ const minTime = transfer.minTransferTime
22225
+ ? ` (min: ${Math.floor(transfer.minTransferTime.toSeconds() / 60)}min)`
22226
+ : '';
22227
+ console.log(`${index + 1}. ${transfer.type} to ${destStop?.name ?? 'Unknown'}${platform} (${transfer.destination}, ${destStop?.sourceStopId ?? 'N/A'})${minTime}`);
22228
+ });
22229
+ }
22230
+ let totalContinuations = 0;
22231
+ console.log('\n--- Trip Continuations ---');
22232
+ routes.forEach((route) => {
22233
+ const serviceRouteInfo = timetable.getServiceRouteInfo(route);
22234
+ const stopIndices = route.stopRouteIndices(stop.id);
22235
+ for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
22236
+ for (const stopIndex of stopIndices) {
22237
+ const continuations = timetable.getContinuousTrips(stopIndex, route.id, tripIndex);
22238
+ for (const continuation of continuations) {
22239
+ totalContinuations++;
22240
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22241
+ const destRoute = timetable.getRoute(continuation.routeId);
22242
+ const destStopId = destRoute.stopId(continuation.hopOnStopIndex);
22243
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
22244
+ const destStop = stopsIndex.findStopById(destStopId);
22245
+ const destPlatform = destStop.platform
22246
+ ? ` (Pl. ${destStop.platform})`
22247
+ : '';
22248
+ const destServiceRouteInfo = timetable.getServiceRouteInfo(destRoute);
22249
+ const originTime = route.departureFrom(stopIndex, tripIndex);
22250
+ const continuationTime = destRoute.departureFrom(continuation.hopOnStopIndex, continuation.tripIndex);
22251
+ console.log(`${totalContinuations}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
22252
+ `Route ${continuation.routeId} (${destServiceRouteInfo.name}) Trip ${continuation.tripIndex} at ${continuationTime.toString()} ` +
22253
+ `at ${destStop.name}${destPlatform} (${destStopId}, ${destStop.sourceStopId})`);
22254
+ }
22255
+ }
22256
+ }
22257
+ });
22258
+ if (totalContinuations === 0) {
22259
+ console.log('No trip continuations found.');
22260
+ }
22261
+ else {
22262
+ console.log(`\nTotal trip continuations: ${totalContinuations}`);
22263
+ }
22264
+ console.log();
22265
+ };
22266
+ if (type === 'route') {
22267
+ inspectRoute(idStr ?? '');
22268
+ }
22269
+ else {
22270
+ inspectStop(idStr ?? '');
22271
+ }
22272
+ this.displayPrompt();
22273
+ },
22274
+ });
21280
22275
  };
21281
22276
 
21282
22277
  const program = new Command();
@@ -21296,7 +22291,7 @@ program
21296
22291
  .option('-s, --stopsOutputPath <path>', 'Path to output stops file', '/tmp/stops')
21297
22292
  .option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
21298
22293
  .option('-v, --verbose', 'Verbose mode', false)
21299
- .action((gtfsPath, options) => __awaiter(void 0, void 0, void 0, function* () {
22294
+ .action(async (gtfsPath, options) => {
21300
22295
  if (options.verbose) {
21301
22296
  log.setDefaultLevel(log.levels.INFO);
21302
22297
  }
@@ -21304,11 +22299,11 @@ program
21304
22299
  log.setDefaultLevel(log.levels.ERROR);
21305
22300
  }
21306
22301
  const parser = new GtfsParser(gtfsPath, profiles[options.profileName]);
21307
- const stopsIndex = yield parser.parseStops();
22302
+ const stopsIndex = await parser.parseStops();
21308
22303
  fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
21309
- const timetable = yield parser.parseTimetable(new Date(options.date));
22304
+ const timetable = await parser.parseTimetable(new Date(options.date));
21310
22305
  fs.writeFileSync(options.timetableOutputPath, timetable.serialize());
21311
- }));
22306
+ });
21312
22307
  program
21313
22308
  .command('parse-stops')
21314
22309
  .description('Parse a GTFS feed and output a stops file.')
@@ -21316,7 +22311,7 @@ program
21316
22311
  .option('-s, --outputPath <path>', 'Path to output stops file', '/tmp/stops')
21317
22312
  .option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
21318
22313
  .option('-v, --verbose', 'Verbose mode', false)
21319
- .action((gtfsPath, options) => __awaiter(void 0, void 0, void 0, function* () {
22314
+ .action(async (gtfsPath, options) => {
21320
22315
  if (options.verbose) {
21321
22316
  log.setDefaultLevel(log.levels.INFO);
21322
22317
  }
@@ -21324,9 +22319,9 @@ program
21324
22319
  log.setDefaultLevel(log.levels.ERROR);
21325
22320
  }
21326
22321
  const parser = new GtfsParser(gtfsPath, profiles[options.profileName]);
21327
- const stopsIndex = yield parser.parseStops();
22322
+ const stopsIndex = await parser.parseStops();
21328
22323
  fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
21329
- }));
22324
+ });
21330
22325
  program
21331
22326
  .command('repl')
21332
22327
  .description('Find stops matching a textual query')