minotor 8.0.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.github/workflows/minotor.yml +1 -1
  2. package/CHANGELOG.md +3 -8
  3. package/README.md +1 -1
  4. package/dist/cli.mjs +352 -256
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/gtfs/transfers.d.ts +21 -6
  7. package/dist/gtfs/trips.d.ts +2 -2
  8. package/dist/parser.cjs.js +296 -188
  9. package/dist/parser.cjs.js.map +1 -1
  10. package/dist/parser.esm.js +296 -188
  11. package/dist/parser.esm.js.map +1 -1
  12. package/dist/router.cjs.js +1 -1
  13. package/dist/router.cjs.js.map +1 -1
  14. package/dist/router.esm.js +1 -1
  15. package/dist/router.esm.js.map +1 -1
  16. package/dist/router.umd.js +1 -1
  17. package/dist/router.umd.js.map +1 -1
  18. package/dist/routing/router.d.ts +4 -4
  19. package/dist/timetable/io.d.ts +3 -3
  20. package/dist/timetable/proto/timetable.d.ts +6 -4
  21. package/dist/timetable/route.d.ts +13 -21
  22. package/dist/timetable/timetable.d.ts +13 -11
  23. package/dist/timetable/tripBoardingId.d.ts +34 -0
  24. package/package.json +1 -1
  25. package/src/__e2e__/timetable/timetable.bin +2 -2
  26. package/src/cli/repl.ts +53 -67
  27. package/src/gtfs/__tests__/parser.test.ts +19 -4
  28. package/src/gtfs/__tests__/transfers.test.ts +598 -318
  29. package/src/gtfs/__tests__/trips.test.ts +3 -44
  30. package/src/gtfs/parser.ts +26 -8
  31. package/src/gtfs/transfers.ts +151 -20
  32. package/src/gtfs/trips.ts +1 -39
  33. package/src/routing/__tests__/result.test.ts +10 -10
  34. package/src/routing/__tests__/router.test.ts +11 -9
  35. package/src/routing/result.ts +2 -2
  36. package/src/routing/router.ts +34 -22
  37. package/src/timetable/__tests__/io.test.ts +8 -7
  38. package/src/timetable/__tests__/route.test.ts +66 -80
  39. package/src/timetable/__tests__/timetable.test.ts +32 -29
  40. package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
  41. package/src/timetable/io.ts +21 -20
  42. package/src/timetable/proto/timetable.proto +6 -4
  43. package/src/timetable/proto/timetable.ts +84 -48
  44. package/src/timetable/route.ts +39 -56
  45. package/src/timetable/timetable.ts +37 -26
  46. package/src/timetable/tripBoardingId.ts +94 -0
  47. package/dist/timetable/tripId.d.ts +0 -15
  48. package/src/timetable/__tests__/tripId.test.ts +0 -27
  49. package/src/timetable/tripId.ts +0 -29
  50. /package/dist/timetable/__tests__/{tripId.test.d.ts → tripBoardingId.test.d.ts} +0 -0
@@ -12297,12 +12297,12 @@ const Transfer = {
12297
12297
  },
12298
12298
  };
12299
12299
  function createBaseTripBoarding() {
12300
- return { hopOnStop: 0, routeId: 0, tripIndex: 0 };
12300
+ return { hopOnStopIndex: 0, routeId: 0, tripIndex: 0 };
12301
12301
  }
12302
12302
  const TripBoarding = {
12303
12303
  encode(message, writer = new BinaryWriter()) {
12304
- if (message.hopOnStop !== 0) {
12305
- writer.uint32(8).uint32(message.hopOnStop);
12304
+ if (message.hopOnStopIndex !== 0) {
12305
+ writer.uint32(8).uint32(message.hopOnStopIndex);
12306
12306
  }
12307
12307
  if (message.routeId !== 0) {
12308
12308
  writer.uint32(16).uint32(message.routeId);
@@ -12323,7 +12323,7 @@ const TripBoarding = {
12323
12323
  if (tag !== 8) {
12324
12324
  break;
12325
12325
  }
12326
- message.hopOnStop = reader.uint32();
12326
+ message.hopOnStopIndex = reader.uint32();
12327
12327
  continue;
12328
12328
  }
12329
12329
  case 2: {
@@ -12350,15 +12350,15 @@ const TripBoarding = {
12350
12350
  },
12351
12351
  fromJSON(object) {
12352
12352
  return {
12353
- hopOnStop: isSet(object.hopOnStop) ? globalThis.Number(object.hopOnStop) : 0,
12353
+ hopOnStopIndex: isSet(object.hopOnStopIndex) ? globalThis.Number(object.hopOnStopIndex) : 0,
12354
12354
  routeId: isSet(object.routeId) ? globalThis.Number(object.routeId) : 0,
12355
12355
  tripIndex: isSet(object.tripIndex) ? globalThis.Number(object.tripIndex) : 0,
12356
12356
  };
12357
12357
  },
12358
12358
  toJSON(message) {
12359
12359
  const obj = {};
12360
- if (message.hopOnStop !== 0) {
12361
- obj.hopOnStop = Math.round(message.hopOnStop);
12360
+ if (message.hopOnStopIndex !== 0) {
12361
+ obj.hopOnStopIndex = Math.round(message.hopOnStopIndex);
12362
12362
  }
12363
12363
  if (message.routeId !== 0) {
12364
12364
  obj.routeId = Math.round(message.routeId);
@@ -12374,22 +12374,28 @@ const TripBoarding = {
12374
12374
  fromPartial(object) {
12375
12375
  var _a, _b, _c;
12376
12376
  const message = createBaseTripBoarding();
12377
- message.hopOnStop = (_a = object.hopOnStop) !== null && _a !== void 0 ? _a : 0;
12377
+ message.hopOnStopIndex = (_a = object.hopOnStopIndex) !== null && _a !== void 0 ? _a : 0;
12378
12378
  message.routeId = (_b = object.routeId) !== null && _b !== void 0 ? _b : 0;
12379
12379
  message.tripIndex = (_c = object.tripIndex) !== null && _c !== void 0 ? _c : 0;
12380
12380
  return message;
12381
12381
  },
12382
12382
  };
12383
12383
  function createBaseTripContinuationEntry() {
12384
- return { key: 0, value: [] };
12384
+ return { originStopIndex: 0, originRouteId: 0, originTripIndex: 0, continuations: [] };
12385
12385
  }
12386
12386
  const TripContinuationEntry = {
12387
12387
  encode(message, writer = new BinaryWriter()) {
12388
- if (message.key !== 0) {
12389
- writer.uint32(8).uint32(message.key);
12388
+ if (message.originStopIndex !== 0) {
12389
+ writer.uint32(8).uint32(message.originStopIndex);
12390
12390
  }
12391
- for (const v of message.value) {
12392
- TripBoarding.encode(v, writer.uint32(18).fork()).join();
12391
+ if (message.originRouteId !== 0) {
12392
+ writer.uint32(16).uint32(message.originRouteId);
12393
+ }
12394
+ if (message.originTripIndex !== 0) {
12395
+ writer.uint32(24).uint32(message.originTripIndex);
12396
+ }
12397
+ for (const v of message.continuations) {
12398
+ TripBoarding.encode(v, writer.uint32(34).fork()).join();
12393
12399
  }
12394
12400
  return writer;
12395
12401
  },
@@ -12404,14 +12410,28 @@ const TripContinuationEntry = {
12404
12410
  if (tag !== 8) {
12405
12411
  break;
12406
12412
  }
12407
- message.key = reader.uint32();
12413
+ message.originStopIndex = reader.uint32();
12408
12414
  continue;
12409
12415
  }
12410
12416
  case 2: {
12411
- if (tag !== 18) {
12417
+ if (tag !== 16) {
12412
12418
  break;
12413
12419
  }
12414
- message.value.push(TripBoarding.decode(reader, reader.uint32()));
12420
+ message.originRouteId = reader.uint32();
12421
+ continue;
12422
+ }
12423
+ case 3: {
12424
+ if (tag !== 24) {
12425
+ break;
12426
+ }
12427
+ message.originTripIndex = reader.uint32();
12428
+ continue;
12429
+ }
12430
+ case 4: {
12431
+ if (tag !== 34) {
12432
+ break;
12433
+ }
12434
+ message.continuations.push(TripBoarding.decode(reader, reader.uint32()));
12415
12435
  continue;
12416
12436
  }
12417
12437
  }
@@ -12424,18 +12444,28 @@ const TripContinuationEntry = {
12424
12444
  },
12425
12445
  fromJSON(object) {
12426
12446
  return {
12427
- key: isSet(object.key) ? globalThis.Number(object.key) : 0,
12428
- value: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.value) ? object.value.map((e) => TripBoarding.fromJSON(e)) : [],
12447
+ originStopIndex: isSet(object.originStopIndex) ? globalThis.Number(object.originStopIndex) : 0,
12448
+ originRouteId: isSet(object.originRouteId) ? globalThis.Number(object.originRouteId) : 0,
12449
+ originTripIndex: isSet(object.originTripIndex) ? globalThis.Number(object.originTripIndex) : 0,
12450
+ continuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.continuations)
12451
+ ? object.continuations.map((e) => TripBoarding.fromJSON(e))
12452
+ : [],
12429
12453
  };
12430
12454
  },
12431
12455
  toJSON(message) {
12432
12456
  var _a;
12433
12457
  const obj = {};
12434
- if (message.key !== 0) {
12435
- obj.key = Math.round(message.key);
12458
+ if (message.originStopIndex !== 0) {
12459
+ obj.originStopIndex = Math.round(message.originStopIndex);
12436
12460
  }
12437
- if ((_a = message.value) === null || _a === void 0 ? void 0 : _a.length) {
12438
- obj.value = message.value.map((e) => TripBoarding.toJSON(e));
12461
+ if (message.originRouteId !== 0) {
12462
+ obj.originRouteId = Math.round(message.originRouteId);
12463
+ }
12464
+ if (message.originTripIndex !== 0) {
12465
+ obj.originTripIndex = Math.round(message.originTripIndex);
12466
+ }
12467
+ if ((_a = message.continuations) === null || _a === void 0 ? void 0 : _a.length) {
12468
+ obj.continuations = message.continuations.map((e) => TripBoarding.toJSON(e));
12439
12469
  }
12440
12470
  return obj;
12441
12471
  },
@@ -12443,15 +12473,17 @@ const TripContinuationEntry = {
12443
12473
  return TripContinuationEntry.fromPartial(base !== null && base !== void 0 ? base : {});
12444
12474
  },
12445
12475
  fromPartial(object) {
12446
- var _a, _b;
12476
+ var _a, _b, _c, _d;
12447
12477
  const message = createBaseTripContinuationEntry();
12448
- message.key = (_a = object.key) !== null && _a !== void 0 ? _a : 0;
12449
- message.value = ((_b = object.value) === null || _b === void 0 ? void 0 : _b.map((e) => TripBoarding.fromPartial(e))) || [];
12478
+ message.originStopIndex = (_a = object.originStopIndex) !== null && _a !== void 0 ? _a : 0;
12479
+ message.originRouteId = (_b = object.originRouteId) !== null && _b !== void 0 ? _b : 0;
12480
+ message.originTripIndex = (_c = object.originTripIndex) !== null && _c !== void 0 ? _c : 0;
12481
+ message.continuations = ((_d = object.continuations) === null || _d === void 0 ? void 0 : _d.map((e) => TripBoarding.fromPartial(e))) || [];
12450
12482
  return message;
12451
12483
  },
12452
12484
  };
12453
12485
  function createBaseStopAdjacency() {
12454
- return { routes: [], transfers: [], tripContinuations: [] };
12486
+ return { routes: [], transfers: [] };
12455
12487
  }
12456
12488
  const StopAdjacency = {
12457
12489
  encode(message, writer = new BinaryWriter()) {
@@ -12463,9 +12495,6 @@ const StopAdjacency = {
12463
12495
  for (const v of message.transfers) {
12464
12496
  Transfer.encode(v, writer.uint32(18).fork()).join();
12465
12497
  }
12466
- for (const v of message.tripContinuations) {
12467
- TripContinuationEntry.encode(v, writer.uint32(26).fork()).join();
12468
- }
12469
12498
  return writer;
12470
12499
  },
12471
12500
  decode(input, length) {
@@ -12496,13 +12525,6 @@ const StopAdjacency = {
12496
12525
  message.transfers.push(Transfer.decode(reader, reader.uint32()));
12497
12526
  continue;
12498
12527
  }
12499
- case 3: {
12500
- if (tag !== 26) {
12501
- break;
12502
- }
12503
- message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
12504
- continue;
12505
- }
12506
12528
  }
12507
12529
  if ((tag & 7) === 4 || tag === 0) {
12508
12530
  break;
@@ -12517,13 +12539,10 @@ const StopAdjacency = {
12517
12539
  transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
12518
12540
  ? object.transfers.map((e) => Transfer.fromJSON(e))
12519
12541
  : [],
12520
- tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
12521
- ? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
12522
- : [],
12523
12542
  };
12524
12543
  },
12525
12544
  toJSON(message) {
12526
- var _a, _b, _c;
12545
+ var _a, _b;
12527
12546
  const obj = {};
12528
12547
  if ((_a = message.routes) === null || _a === void 0 ? void 0 : _a.length) {
12529
12548
  obj.routes = message.routes.map((e) => Math.round(e));
@@ -12531,20 +12550,16 @@ const StopAdjacency = {
12531
12550
  if ((_b = message.transfers) === null || _b === void 0 ? void 0 : _b.length) {
12532
12551
  obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
12533
12552
  }
12534
- if ((_c = message.tripContinuations) === null || _c === void 0 ? void 0 : _c.length) {
12535
- obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
12536
- }
12537
12553
  return obj;
12538
12554
  },
12539
12555
  create(base) {
12540
12556
  return StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
12541
12557
  },
12542
12558
  fromPartial(object) {
12543
- var _a, _b, _c;
12559
+ var _a, _b;
12544
12560
  const message = createBaseStopAdjacency();
12545
12561
  message.routes = ((_a = object.routes) === null || _a === void 0 ? void 0 : _a.map((e) => e)) || [];
12546
12562
  message.transfers = ((_b = object.transfers) === null || _b === void 0 ? void 0 : _b.map((e) => Transfer.fromPartial(e))) || [];
12547
- message.tripContinuations = ((_c = object.tripContinuations) === null || _c === void 0 ? void 0 : _c.map((e) => TripContinuationEntry.fromPartial(e))) || [];
12548
12563
  return message;
12549
12564
  },
12550
12565
  };
@@ -12643,7 +12658,7 @@ const ServiceRoute = {
12643
12658
  },
12644
12659
  };
12645
12660
  function createBaseTimetable() {
12646
- return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [] };
12661
+ return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [], tripContinuations: [] };
12647
12662
  }
12648
12663
  const Timetable$1 = {
12649
12664
  encode(message, writer = new BinaryWriter()) {
@@ -12659,6 +12674,9 @@ const Timetable$1 = {
12659
12674
  for (const v of message.serviceRoutes) {
12660
12675
  ServiceRoute.encode(v, writer.uint32(34).fork()).join();
12661
12676
  }
12677
+ for (const v of message.tripContinuations) {
12678
+ TripContinuationEntry.encode(v, writer.uint32(42).fork()).join();
12679
+ }
12662
12680
  return writer;
12663
12681
  },
12664
12682
  decode(input, length) {
@@ -12696,6 +12714,13 @@ const Timetable$1 = {
12696
12714
  message.serviceRoutes.push(ServiceRoute.decode(reader, reader.uint32()));
12697
12715
  continue;
12698
12716
  }
12717
+ case 5: {
12718
+ if (tag !== 42) {
12719
+ break;
12720
+ }
12721
+ message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
12722
+ continue;
12723
+ }
12699
12724
  }
12700
12725
  if ((tag & 7) === 4 || tag === 0) {
12701
12726
  break;
@@ -12716,10 +12741,13 @@ const Timetable$1 = {
12716
12741
  serviceRoutes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.serviceRoutes)
12717
12742
  ? object.serviceRoutes.map((e) => ServiceRoute.fromJSON(e))
12718
12743
  : [],
12744
+ tripContinuations: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.tripContinuations)
12745
+ ? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
12746
+ : [],
12719
12747
  };
12720
12748
  },
12721
12749
  toJSON(message) {
12722
- var _a, _b, _c;
12750
+ var _a, _b, _c, _d;
12723
12751
  const obj = {};
12724
12752
  if (message.version !== "") {
12725
12753
  obj.version = message.version;
@@ -12733,18 +12761,22 @@ const Timetable$1 = {
12733
12761
  if ((_c = message.serviceRoutes) === null || _c === void 0 ? void 0 : _c.length) {
12734
12762
  obj.serviceRoutes = message.serviceRoutes.map((e) => ServiceRoute.toJSON(e));
12735
12763
  }
12764
+ if ((_d = message.tripContinuations) === null || _d === void 0 ? void 0 : _d.length) {
12765
+ obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
12766
+ }
12736
12767
  return obj;
12737
12768
  },
12738
12769
  create(base) {
12739
12770
  return Timetable$1.fromPartial(base !== null && base !== void 0 ? base : {});
12740
12771
  },
12741
12772
  fromPartial(object) {
12742
- var _a, _b, _c, _d;
12773
+ var _a, _b, _c, _d, _e;
12743
12774
  const message = createBaseTimetable();
12744
12775
  message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
12745
12776
  message.stopsAdjacency = ((_b = object.stopsAdjacency) === null || _b === void 0 ? void 0 : _b.map((e) => StopAdjacency.fromPartial(e))) || [];
12746
12777
  message.routesAdjacency = ((_c = object.routesAdjacency) === null || _c === void 0 ? void 0 : _c.map((e) => Route$1.fromPartial(e))) || [];
12747
12778
  message.serviceRoutes = ((_d = object.serviceRoutes) === null || _d === void 0 ? void 0 : _d.map((e) => ServiceRoute.fromPartial(e))) || [];
12779
+ message.tripContinuations = ((_e = object.tripContinuations) === null || _e === void 0 ? void 0 : _e.map((e) => TripContinuationEntry.fromPartial(e))) || [];
12748
12780
  return message;
12749
12781
  },
12750
12782
  };
@@ -13044,7 +13076,14 @@ class Route {
13044
13076
  this.stopIndices = new Map();
13045
13077
  for (let i = 0; i < stops.length; i++) {
13046
13078
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
13047
- this.stopIndices.set(stops[i], i);
13079
+ const stopId = stops[i];
13080
+ const existingIndices = this.stopIndices.get(stopId);
13081
+ if (existingIndices) {
13082
+ existingIndices.push(i);
13083
+ }
13084
+ else {
13085
+ this.stopIndices.set(stopId, [i]);
13086
+ }
13048
13087
  }
13049
13088
  }
13050
13089
  /**
@@ -13132,24 +13171,6 @@ class Route {
13132
13171
  serviceRouteId: this.serviceRouteId,
13133
13172
  };
13134
13173
  }
13135
- /**
13136
- * Checks if stop A is before stop B in the route.
13137
- *
13138
- * @param stopA - The StopId of the first stop.
13139
- * @param stopB - The StopId of the second stop.
13140
- * @returns True if stop A is before stop B, false otherwise.
13141
- */
13142
- isBefore(stopA, stopB) {
13143
- const stopAIndex = this.stopIndices.get(stopA);
13144
- if (stopAIndex === undefined) {
13145
- throw new Error(`Stop index not found for ${stopA} in route ${this.serviceRouteId}`);
13146
- }
13147
- const stopBIndex = this.stopIndices.get(stopB);
13148
- if (stopBIndex === undefined) {
13149
- throw new Error(`Stop index not found for ${stopB} in route ${this.serviceRouteId}`);
13150
- }
13151
- return stopAIndex < stopBIndex;
13152
- }
13153
13174
  /**
13154
13175
  * Retrieves the number of stops in the route.
13155
13176
  *
@@ -13178,47 +13199,47 @@ class Route {
13178
13199
  /**
13179
13200
  * Retrieves the arrival time at a specific stop for a given trip.
13180
13201
  *
13181
- * @param stopId - The identifier of the stop.
13202
+ * @param stopIndex - The index of the stop in the route.
13182
13203
  * @param tripIndex - The index of the trip.
13183
13204
  * @returns The arrival time at the specified stop and trip as a Time object.
13184
13205
  */
13185
- arrivalAt(stopId, tripIndex) {
13186
- const arrivalIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2;
13206
+ arrivalAt(stopIndex, tripIndex) {
13207
+ const arrivalIndex = (tripIndex * this.stops.length + stopIndex) * 2;
13187
13208
  const arrival = this.stopTimes[arrivalIndex];
13188
13209
  if (arrival === undefined) {
13189
- throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13210
+ throw new Error(`Arrival time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13190
13211
  }
13191
13212
  return Time.fromMinutes(arrival);
13192
13213
  }
13193
13214
  /**
13194
13215
  * Retrieves the departure time at a specific stop for a given trip.
13195
13216
  *
13196
- * @param stopId - The identifier of the stop.
13217
+ * @param stopIndex - The index of the stop in the route.
13197
13218
  * @param tripIndex - The index of the trip.
13198
13219
  * @returns The departure time at the specified stop and trip as a Time object.
13199
13220
  */
13200
- departureFrom(stopId, tripIndex) {
13201
- const departureIndex = (tripIndex * this.stops.length + this.stopRouteIndex(stopId)) * 2 + 1;
13221
+ departureFrom(stopIndex, tripIndex) {
13222
+ const departureIndex = (tripIndex * this.stops.length + stopIndex) * 2 + 1;
13202
13223
  const departure = this.stopTimes[departureIndex];
13203
13224
  if (departure === undefined) {
13204
- throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13225
+ throw new Error(`Departure time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13205
13226
  }
13206
13227
  return Time.fromMinutes(departure);
13207
13228
  }
13208
13229
  /**
13209
13230
  * Retrieves the pick-up type for a specific stop and trip.
13210
13231
  *
13211
- * @param stopId - The identifier of the stop.
13232
+ * @param stopIndex - The index of the stop in the route.
13212
13233
  * @param tripIndex - The index of the trip.
13213
13234
  * @returns The pick-up type at the specified stop and trip.
13214
13235
  */
13215
- pickUpTypeFrom(stopId, tripIndex) {
13216
- const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
13236
+ pickUpTypeFrom(stopIndex, tripIndex) {
13237
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
13217
13238
  const byteIndex = Math.floor(globalIndex / 2);
13218
13239
  const isSecondPair = globalIndex % 2 === 1;
13219
13240
  const byte = this.pickUpDropOffTypes[byteIndex];
13220
13241
  if (byte === undefined) {
13221
- throw new Error(`Pick up type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13242
+ throw new Error(`Pick up type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13222
13243
  }
13223
13244
  const pickUpValue = isSecondPair
13224
13245
  ? (byte >> 6) & 0x03 // Upper 2 bits for second pair
@@ -13228,17 +13249,17 @@ class Route {
13228
13249
  /**
13229
13250
  * Retrieves the drop-off type for a specific stop and trip.
13230
13251
  *
13231
- * @param stopId - The identifier of the stop.
13252
+ * @param stopIndex - The index of the stop in the route.
13232
13253
  * @param tripIndex - The index of the trip.
13233
13254
  * @returns The drop-off type at the specified stop and trip.
13234
13255
  */
13235
- dropOffTypeAt(stopId, tripIndex) {
13236
- const globalIndex = tripIndex * this.stops.length + this.stopRouteIndex(stopId);
13256
+ dropOffTypeAt(stopIndex, tripIndex) {
13257
+ const globalIndex = tripIndex * this.stops.length + stopIndex;
13237
13258
  const byteIndex = Math.floor(globalIndex / 2);
13238
13259
  const isSecondPair = globalIndex % 2 === 1;
13239
13260
  const byte = this.pickUpDropOffTypes[byteIndex];
13240
13261
  if (byte === undefined) {
13241
- throw new Error(`Drop off type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13262
+ throw new Error(`Drop off type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
13242
13263
  }
13243
13264
  const dropOffValue = isSecondPair
13244
13265
  ? (byte >> 4) & 0x03 // Bits 4-5 for second pair
@@ -13250,14 +13271,14 @@ class Route {
13250
13271
  * optionally constrained by a latest trip index and a time before which the trip
13251
13272
  * should not depart.
13252
13273
  * *
13253
- * @param stopId - The StopId of the stop where the trip should be found.
13274
+ * @param stopIndex - The route index of the stop where the trip should be found.
13254
13275
  * @param [after=Time.origin()] - The earliest time after which the trip should depart.
13255
13276
  * If not provided, searches all available trips.
13256
13277
  * @param [beforeTrip] - (Optional) The index of the trip before which the search should be constrained.
13257
13278
  * If not provided, searches all available trips.
13258
13279
  * @returns The index of the earliest trip meeting the criteria, or undefined if no such trip is found.
13259
13280
  */
13260
- findEarliestTrip(stopId, after = Time.origin(), beforeTrip) {
13281
+ findEarliestTrip(stopIndex, after = Time.origin(), beforeTrip) {
13261
13282
  if (this.nbTrips <= 0)
13262
13283
  return undefined;
13263
13284
  let hi = this.nbTrips - 1;
@@ -13269,7 +13290,7 @@ class Route {
13269
13290
  let lb = -1;
13270
13291
  while (lo <= hi) {
13271
13292
  const mid = (lo + hi) >>> 1;
13272
- const depMid = this.departureFrom(stopId, mid);
13293
+ const depMid = this.departureFrom(stopIndex, mid);
13273
13294
  if (depMid.isBefore(after)) {
13274
13295
  lo = mid + 1;
13275
13296
  }
@@ -13281,7 +13302,7 @@ class Route {
13281
13302
  if (lb === -1)
13282
13303
  return undefined;
13283
13304
  for (let t = lb; t < (beforeTrip !== null && beforeTrip !== void 0 ? beforeTrip : this.nbTrips); t++) {
13284
- const pickup = this.pickUpTypeFrom(stopId, t);
13305
+ const pickup = this.pickUpTypeFrom(stopIndex, t);
13285
13306
  if (pickup !== 'NOT_AVAILABLE') {
13286
13307
  return t;
13287
13308
  }
@@ -13289,14 +13310,14 @@ class Route {
13289
13310
  return undefined;
13290
13311
  }
13291
13312
  /**
13292
- * Retrieves the index of a stop within the route.
13313
+ * Retrieves the indices of a stop within the route.
13293
13314
  * @param stopId The StopId of the stop to locate in the route.
13294
- * @returns The index of the stop in the route.
13315
+ * @returns An array of indices where the stop appears in the route, or an empty array if the stop is not found.
13295
13316
  */
13296
- stopRouteIndex(stopId) {
13317
+ stopRouteIndices(stopId) {
13297
13318
  const stopIndex = this.stopIndices.get(stopId);
13298
13319
  if (stopIndex === undefined) {
13299
- throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
13320
+ return [];
13300
13321
  }
13301
13322
  return stopIndex;
13302
13323
  }
@@ -13314,6 +13335,51 @@ class Route {
13314
13335
  }
13315
13336
  }
13316
13337
 
13338
+ // Each value uses 20 bits, allowing values from 0 to 1,048,575 (2^20 - 1)
13339
+ const VALUE_MASK = (BigInt(1) << BigInt(20)) - BigInt(1); // 0xFFFFF
13340
+ const MAX_VALUE = 1048575; // 2^20 - 1
13341
+ // Bit positions for each value in the 60-bit bigint
13342
+ const TRIP_INDEX_SHIFT = BigInt(0);
13343
+ const ROUTE_ID_SHIFT = BigInt(20);
13344
+ const STOP_INDEX_SHIFT = BigInt(40);
13345
+ /**
13346
+ * Validates that a value fits within 20 bits (0 to 1,048,575)
13347
+ * @param value - The value to validate
13348
+ * @param name - The name of the value for error reporting
13349
+ * @throws Error if the value is out of range
13350
+ */
13351
+ const validateValue = (value, name) => {
13352
+ if (value < 0 || value > MAX_VALUE) {
13353
+ throw new Error(`${name} must be between 0 and ${MAX_VALUE}, got ${value}`);
13354
+ }
13355
+ };
13356
+ /**
13357
+ * Encodes a stop index, route ID, and trip index into a single trip boarding ID.
13358
+ * @param stopIndex - The index of the stop within the route (0 to 1,048,575)
13359
+ * @param routeId - The route identifier (0 to 1,048,575)
13360
+ * @param tripIndex - The index of the trip within the route (0 to 1,048,575)
13361
+ * @returns The encoded trip ID as a bigint
13362
+ */
13363
+ const encode = (stopIndex, routeId, tripIndex) => {
13364
+ validateValue(stopIndex, 'stopIndex');
13365
+ validateValue(routeId, 'routeId');
13366
+ validateValue(tripIndex, 'tripIndex');
13367
+ return ((BigInt(stopIndex) << STOP_INDEX_SHIFT) |
13368
+ (BigInt(routeId) << ROUTE_ID_SHIFT) |
13369
+ (BigInt(tripIndex) << TRIP_INDEX_SHIFT));
13370
+ };
13371
+ /**
13372
+ * Decodes a trip boarding ID back into its constituent stop index, route ID, and trip index.
13373
+ * @param tripBoardingId - The encoded trip ID
13374
+ * @returns A tuple containing [stopIndex, routeId, tripIndex]
13375
+ */
13376
+ const decode = (tripBoardingId) => {
13377
+ const stopIndex = Number((tripBoardingId >> STOP_INDEX_SHIFT) & VALUE_MASK);
13378
+ const routeId = Number((tripBoardingId >> ROUTE_ID_SHIFT) & VALUE_MASK);
13379
+ const tripIndex = Number((tripBoardingId >> TRIP_INDEX_SHIFT) & VALUE_MASK);
13380
+ return [stopIndex, routeId, tripIndex];
13381
+ };
13382
+
13317
13383
  const isLittleEndian = (() => {
13318
13384
  const buffer = new ArrayBuffer(4);
13319
13385
  const view = new DataView(buffer);
@@ -13388,9 +13454,6 @@ const serializeStopsAdjacency = (stopsAdjacency) => {
13388
13454
  }))))
13389
13455
  : [],
13390
13456
  routes: value.routes,
13391
- tripContinuations: value.tripContinuations
13392
- ? serializeTripContinuations(value.tripContinuations)
13393
- : [],
13394
13457
  };
13395
13458
  });
13396
13459
  };
@@ -13436,10 +13499,6 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
13436
13499
  if (transfers.length > 0) {
13437
13500
  stopAdjacency.transfers = transfers;
13438
13501
  }
13439
- const deserializedTripContinuations = deserializeTripContinuations(value.tripContinuations);
13440
- if (deserializedTripContinuations.size > 0) {
13441
- stopAdjacency.tripContinuations = deserializedTripContinuations;
13442
- }
13443
13502
  result.push(stopAdjacency);
13444
13503
  }
13445
13504
  return result;
@@ -13546,11 +13605,14 @@ const serializeRouteType = (type) => {
13546
13605
  };
13547
13606
  const serializeTripContinuations = (tripContinuations) => {
13548
13607
  const result = [];
13549
- for (const [key, value] of tripContinuations.entries()) {
13608
+ for (const [tripBoardingId, boardings] of tripContinuations.entries()) {
13609
+ const [originStopIndex, originRouteId, originTripIndex] = decode(tripBoardingId);
13550
13610
  result.push({
13551
- key: key,
13552
- value: value.map((tripBoarding) => ({
13553
- hopOnStop: tripBoarding.hopOnStop,
13611
+ originStopIndex,
13612
+ originRouteId,
13613
+ originTripIndex,
13614
+ continuations: boardings.map((tripBoarding) => ({
13615
+ hopOnStopIndex: tripBoarding.hopOnStopIndex,
13554
13616
  routeId: tripBoarding.routeId,
13555
13617
  tripIndex: tripBoarding.tripIndex,
13556
13618
  })),
@@ -13563,28 +13625,17 @@ const deserializeTripContinuations = (protoTripContinuations) => {
13563
13625
  for (let i = 0; i < protoTripContinuations.length; i++) {
13564
13626
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
13565
13627
  const entry = protoTripContinuations[i];
13566
- const tripBoardings = entry.value.map((protoTripBoarding) => ({
13567
- hopOnStop: protoTripBoarding.hopOnStop,
13628
+ const tripBoardingId = encode(entry.originStopIndex, entry.originRouteId, entry.originTripIndex);
13629
+ const tripBoardings = entry.continuations.map((protoTripBoarding) => ({
13630
+ hopOnStopIndex: protoTripBoarding.hopOnStopIndex,
13568
13631
  routeId: protoTripBoarding.routeId,
13569
13632
  tripIndex: protoTripBoarding.tripIndex,
13570
13633
  }));
13571
- result.set(entry.key, tripBoardings);
13634
+ result.set(tripBoardingId, tripBoardings);
13572
13635
  }
13573
13636
  return result;
13574
13637
  };
13575
13638
 
13576
- // const ROUTE_ID_BITS = 17;
13577
- const TRIP_INDEX_BITS = 15;
13578
- /**
13579
- * Encodes a route ID and trip index into a single trip ID.
13580
- * @param routeId - The route identifier, needs to fit on 17 bits
13581
- * @param tripIndex - The index of the trip within the route, needs to fit on 15 bits
13582
- * @returns The encoded trip ID
13583
- */
13584
- const encode = (routeId, tripIndex) => {
13585
- return (routeId << TRIP_INDEX_BITS) | tripIndex;
13586
- };
13587
-
13588
13639
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
13589
13640
  const ALL_TRANSPORT_MODES = new Set([
13590
13641
  'TRAM',
@@ -13599,21 +13650,21 @@ const ALL_TRANSPORT_MODES = new Set([
13599
13650
  'MONORAIL',
13600
13651
  ]);
13601
13652
  const EMPTY_TRIP_CONTINUATIONS = [];
13602
- const CURRENT_VERSION = '0.0.8';
13653
+ const CURRENT_VERSION = '0.0.9';
13603
13654
  /**
13604
13655
  * The internal transit timetable format.
13605
13656
  */
13606
13657
  class Timetable {
13607
- constructor(stopsAdjacency, routesAdjacency, routes) {
13658
+ constructor(stopsAdjacency, routesAdjacency, routes, tripContinuations) {
13608
13659
  this.stopsAdjacency = stopsAdjacency;
13609
13660
  this.routesAdjacency = routesAdjacency;
13610
13661
  this.serviceRoutes = routes;
13662
+ this.tripContinuations = tripContinuations;
13611
13663
  this.activeStops = new Set();
13612
13664
  for (let i = 0; i < stopsAdjacency.length; i++) {
13613
13665
  const stop = stopsAdjacency[i];
13614
13666
  if (stop.routes.length > 0 ||
13615
- (stop.transfers && stop.transfers.length > 0) ||
13616
- (stop.tripContinuations && stop.tripContinuations.size > 0)) {
13667
+ (stop.transfers && stop.transfers.length > 0)) {
13617
13668
  this.activeStops.add(i);
13618
13669
  }
13619
13670
  }
@@ -13629,6 +13680,7 @@ class Timetable {
13629
13680
  stopsAdjacency: serializeStopsAdjacency(this.stopsAdjacency),
13630
13681
  routesAdjacency: serializeRoutesAdjacency(this.routesAdjacency),
13631
13682
  serviceRoutes: serializeServiceRoutesMap(this.serviceRoutes),
13683
+ tripContinuations: serializeTripContinuations(this.tripContinuations || new Map()),
13632
13684
  };
13633
13685
  const writer = new BinaryWriter();
13634
13686
  Timetable$1.encode(protoTimetable, writer);
@@ -13646,7 +13698,7 @@ class Timetable {
13646
13698
  if (protoTimetable.version !== CURRENT_VERSION) {
13647
13699
  throw new Error(`Unsupported timetable version ${protoTimetable.version}`);
13648
13700
  }
13649
- return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes));
13701
+ return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes), deserializeTripContinuations(protoTimetable.tripContinuations));
13650
13702
  }
13651
13703
  /**
13652
13704
  * Checks if the given stop is active on the timetable.
@@ -13685,18 +13737,18 @@ class Timetable {
13685
13737
  /**
13686
13738
  * Retrieves all trip continuation options available at the specified stop for a given trip.
13687
13739
  *
13688
- * @param stopId - The ID of the stop to get trip continuations for.
13740
+ * @param stopIndex - The index in the route of the stop to get trip continuations for.
13741
+ * @param routeId - The ID of the route to get continuations for.
13689
13742
  * @param tripIndex - The index of the trip to get continuations for.
13690
13743
  * @returns An array of trip continuation options available at the stop for the specified trip.
13691
13744
  */
13692
- getContinuousTrips(stopId, routeId, tripIndex) {
13745
+ getContinuousTrips(stopIndex, routeId, tripIndex) {
13693
13746
  var _a;
13694
- const stopAdjacency = this.stopsAdjacency[stopId];
13695
- if (!stopAdjacency) {
13696
- throw new Error(`Stop ID ${stopId} not found`);
13747
+ const tripContinuations = (_a = this.tripContinuations) === null || _a === void 0 ? void 0 : _a.get(encode(stopIndex, routeId, tripIndex));
13748
+ if (!tripContinuations) {
13749
+ return EMPTY_TRIP_CONTINUATIONS;
13697
13750
  }
13698
- return (((_a = stopAdjacency.tripContinuations) === null || _a === void 0 ? void 0 : _a.get(encode(routeId, tripIndex))) ||
13699
- EMPTY_TRIP_CONTINUATIONS);
13751
+ return tripContinuations;
13700
13752
  }
13701
13753
  /**
13702
13754
  * Retrieves the service route associated with the given route.
@@ -13736,12 +13788,12 @@ class Timetable {
13736
13788
  }
13737
13789
  /**
13738
13790
  * Finds routes that are reachable from a set of stop IDs.
13739
- * Also identifies the first stop available to hop on each route among
13791
+ * Also identifies the first stop index available to hop on each route among
13740
13792
  * the input stops.
13741
13793
  *
13742
13794
  * @param fromStops - The set of stop IDs to find reachable routes from.
13743
13795
  * @param transportModes - The set of transport modes to consider for reachable routes.
13744
- * @returns A map of reachable routes to the first stop available to hop on each route.
13796
+ * @returns A map of reachable routes to the first stop index available to hop on each route.
13745
13797
  */
13746
13798
  findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
13747
13799
  const reachableRoutes = new Map();
@@ -13754,15 +13806,17 @@ class Timetable {
13754
13806
  });
13755
13807
  for (let j = 0; j < validRoutes.length; j++) {
13756
13808
  const route = validRoutes[j];
13757
- const hopOnStop = reachableRoutes.get(route);
13758
- if (hopOnStop) {
13759
- if (route.isBefore(originStop, hopOnStop)) {
13809
+ const originStopIndices = route.stopRouteIndices(originStop);
13810
+ const originStopIndex = originStopIndices[0];
13811
+ const existingHopOnStopIndex = reachableRoutes.get(route);
13812
+ if (existingHopOnStopIndex !== undefined) {
13813
+ if (originStopIndex < existingHopOnStopIndex) {
13760
13814
  // if the current stop is before the existing hop on stop, replace it
13761
- reachableRoutes.set(route, originStop);
13815
+ reachableRoutes.set(route, originStopIndex);
13762
13816
  }
13763
13817
  }
13764
13818
  else {
13765
- reachableRoutes.set(route, originStop);
13819
+ reachableRoutes.set(route, originStopIndex);
13766
13820
  }
13767
13821
  }
13768
13822
  }
@@ -15982,7 +16036,7 @@ const parseGtfsLocationType = (gtfsLocationType) => {
15982
16036
  const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
15983
16037
  var _a, e_1, _b, _c;
15984
16038
  const transfers = new Map();
15985
- const tripContinuations = new Map();
16039
+ const tripContinuations = [];
15986
16040
  try {
15987
16041
  for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
15988
16042
  'transfer_type',
@@ -16000,10 +16054,12 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
16000
16054
  console.warn(`Missing transfer origin or destination stop.`);
16001
16055
  continue;
16002
16056
  }
16003
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16004
16057
  const fromStop = stopsMap.get(transferEntry.from_stop_id);
16005
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16006
16058
  const toStop = stopsMap.get(transferEntry.to_stop_id);
16059
+ if (!fromStop || !toStop) {
16060
+ console.warn(`Transfer references non-existent stop(s): from_stop_id=${transferEntry.from_stop_id}, to_stop_id=${transferEntry.to_stop_id}`);
16061
+ continue;
16062
+ }
16007
16063
  if (transferEntry.transfer_type === 4) {
16008
16064
  if (transferEntry.from_trip_id === undefined ||
16009
16065
  transferEntry.from_trip_id === '' ||
@@ -16013,17 +16069,12 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
16013
16069
  continue;
16014
16070
  }
16015
16071
  const tripBoardingEntry = {
16072
+ fromStop: fromStop.id,
16016
16073
  fromTrip: transferEntry.from_trip_id,
16074
+ toStop: toStop.id,
16017
16075
  toTrip: transferEntry.to_trip_id,
16018
- hopOnStop: toStop.id,
16019
16076
  };
16020
- const existingBoardings = tripContinuations.get(fromStop.id);
16021
- if (existingBoardings) {
16022
- existingBoardings.push(tripBoardingEntry);
16023
- }
16024
- else {
16025
- tripContinuations.set(fromStop.id, [tripBoardingEntry]);
16026
- }
16077
+ tripContinuations.push(tripBoardingEntry);
16027
16078
  continue;
16028
16079
  }
16029
16080
  if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
@@ -16038,7 +16089,7 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
16038
16089
  transferEntry.min_transfer_time === undefined) {
16039
16090
  console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
16040
16091
  }
16041
- const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
16092
+ const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time !== undefined && {
16042
16093
  minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
16043
16094
  }));
16044
16095
  const fromStopTransfers = transfers.get(fromStop.id) || [];
@@ -16058,6 +16109,91 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0,
16058
16109
  tripContinuations,
16059
16110
  };
16060
16111
  });
16112
+ /**
16113
+ * Disambiguates stops involved in a transfer.
16114
+ *
16115
+ * The GTFS specification only refers to a stopId in the trip-to-trip transfers and not the
16116
+ * specific stop index in the route. For routes that have multiple stops with the same stopId,
16117
+ * we need to determine which are the from / to stop indices in respective routes.
16118
+ * We do so by picking the stop indices leading to the most coherent transfer.
16119
+ * (we pick the closest from stop index happening after the to stop index).
16120
+ */
16121
+ const disambiguateTransferStopsIndices = (fromStop, fromRoute, fromTripIndex, toStop, toRoute, toTripIndex) => {
16122
+ const fromStopIndices = fromRoute.stopRouteIndices(fromStop);
16123
+ const toStopIndices = toRoute.stopRouteIndices(toStop);
16124
+ let bestFromStopIndex;
16125
+ let bestToStopIndex;
16126
+ let bestTimeDifference = Infinity;
16127
+ for (const originStopIndex of fromStopIndices) {
16128
+ const fromArrivalTime = fromRoute.arrivalAt(originStopIndex, fromTripIndex);
16129
+ for (const toStopIndex of toStopIndices) {
16130
+ const toDepartureTime = toRoute.departureFrom(toStopIndex, toTripIndex);
16131
+ if (toDepartureTime.isAfter(fromArrivalTime)) {
16132
+ const timeDifference = toDepartureTime.toMinutes() - fromArrivalTime.toMinutes();
16133
+ if (timeDifference < bestTimeDifference) {
16134
+ bestTimeDifference = timeDifference;
16135
+ bestFromStopIndex = originStopIndex;
16136
+ bestToStopIndex = toStopIndex;
16137
+ }
16138
+ }
16139
+ }
16140
+ }
16141
+ if (bestFromStopIndex !== undefined && bestToStopIndex !== undefined) {
16142
+ return {
16143
+ fromStopIndex: bestFromStopIndex,
16144
+ toStopIndex: bestToStopIndex,
16145
+ };
16146
+ }
16147
+ return undefined;
16148
+ };
16149
+ /**
16150
+ * Builds trip continuations map from GTFS trip continuation data.
16151
+ *
16152
+ * This function processes GTFS in-seat transfer data and creates a mapping
16153
+ * from trip boarding IDs to continuation boarding information. It disambiguates
16154
+ * stop indices when routes have multiple stops with the same ID by finding
16155
+ * the most coherent transfer timing.
16156
+ *
16157
+ * @param tripsMapping Mapping from GTFS trip IDs to internal trip representations
16158
+ * @param tripContinuations Array of GTFS trip continuation data from transfers.txt
16159
+ * @param timetable The timetable containing route and timing information
16160
+ * @param activeStopIds Set of stop IDs that are active/enabled in the system
16161
+ * @returns A map from trip boarding IDs to arrays of continuation boarding options
16162
+ */
16163
+ const buildTripContinuations = (tripsMapping, tripContinuations, timetable, activeStopIds) => {
16164
+ const continuations = new Map();
16165
+ for (const gtfsContinuation of tripContinuations) {
16166
+ if (!activeStopIds.has(gtfsContinuation.fromStop) ||
16167
+ !activeStopIds.has(gtfsContinuation.toStop)) {
16168
+ continue;
16169
+ }
16170
+ const fromTripMapping = tripsMapping.get(gtfsContinuation.fromTrip);
16171
+ const toTripMapping = tripsMapping.get(gtfsContinuation.toTrip);
16172
+ if (!fromTripMapping || !toTripMapping) {
16173
+ continue;
16174
+ }
16175
+ const fromRoute = timetable.getRoute(fromTripMapping.routeId);
16176
+ const toRoute = timetable.getRoute(toTripMapping.routeId);
16177
+ if (!fromRoute || !toRoute) {
16178
+ continue;
16179
+ }
16180
+ const bestStopIndices = disambiguateTransferStopsIndices(gtfsContinuation.fromStop, fromRoute, fromTripMapping.tripRouteIndex, gtfsContinuation.toStop, toRoute, toTripMapping.tripRouteIndex);
16181
+ if (!bestStopIndices) {
16182
+ // No valid continuation found
16183
+ continue;
16184
+ }
16185
+ const tripBoardingId = encode(bestStopIndices.fromStopIndex, fromTripMapping.routeId, fromTripMapping.tripRouteIndex);
16186
+ const continuationBoarding = {
16187
+ hopOnStopIndex: bestStopIndices.toStopIndex,
16188
+ routeId: toTripMapping.routeId,
16189
+ tripIndex: toTripMapping.tripRouteIndex,
16190
+ };
16191
+ const existingContinuations = continuations.get(tripBoardingId) || [];
16192
+ existingContinuations.push(continuationBoarding);
16193
+ continuations.set(tripBoardingId, existingContinuations);
16194
+ }
16195
+ return continuations;
16196
+ };
16061
16197
  const parseGtfsTransferType = (gtfsTransferType) => {
16062
16198
  switch (gtfsTransferType) {
16063
16199
  case 0:
@@ -16194,7 +16330,7 @@ const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) => __awaiter(void
16194
16330
  }
16195
16331
  return trips;
16196
16332
  });
16197
- const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, transfersMap, tripContinuationsMap, nbStops, activeStops) => {
16333
+ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
16198
16334
  const stopsAdjacency = new Array(nbStops);
16199
16335
  for (let i = 0; i < nbStops; i++) {
16200
16336
  stopsAdjacency[i] = {
@@ -16234,40 +16370,6 @@ const buildStopsAdjacencyStructure = (tripsMapping, serviceRoutes, routes, trans
16234
16370
  }
16235
16371
  }
16236
16372
  }
16237
- for (const [stop, tripContinuations] of tripContinuationsMap) {
16238
- for (let i = 0; i < tripContinuations.length; i++) {
16239
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16240
- const tripContinuation = tripContinuations[i];
16241
- if (activeStops.has(stop) ||
16242
- activeStops.has(tripContinuation.hopOnStop)) {
16243
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16244
- const stopAdj = stopsAdjacency[stop];
16245
- if (!stopAdj.tripContinuations) {
16246
- stopAdj.tripContinuations = new Map();
16247
- }
16248
- const originTrip = tripsMapping.get(tripContinuation.fromTrip);
16249
- const destinationTrip = tripsMapping.get(tripContinuation.toTrip);
16250
- if (destinationTrip === undefined || originTrip === undefined) {
16251
- continue;
16252
- }
16253
- const tripBoarding = {
16254
- hopOnStop: tripContinuation.hopOnStop,
16255
- routeId: destinationTrip.routeId,
16256
- tripIndex: destinationTrip.tripRouteIndex,
16257
- };
16258
- const tripId = encode(originTrip.routeId, originTrip.tripRouteIndex);
16259
- const existingContinuations = stopAdj.tripContinuations.get(tripId);
16260
- if (existingContinuations) {
16261
- existingContinuations.push(tripBoarding);
16262
- }
16263
- else {
16264
- stopAdj.tripContinuations.set(tripId, [tripBoarding]);
16265
- }
16266
- activeStops.add(tripContinuation.hopOnStop);
16267
- activeStops.add(stop);
16268
- }
16269
- }
16270
- }
16271
16373
  return stopsAdjacency;
16272
16374
  };
16273
16375
  /**
@@ -16494,16 +16596,16 @@ class GtfsParser {
16494
16596
  const tripsEnd = performance.now();
16495
16597
  log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
16496
16598
  let transfers = new Map();
16497
- let tripContinuations = new Map();
16599
+ let tripContinuationsMap = [];
16498
16600
  if (entries[TRANSFERS_FILE]) {
16499
16601
  log.info(`Parsing ${TRANSFERS_FILE}`);
16500
16602
  const transfersStart = performance.now();
16501
16603
  const transfersStream = yield zip.stream(TRANSFERS_FILE);
16502
16604
  const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = yield parseTransfers(transfersStream, parsedStops);
16503
16605
  transfers = parsedTransfers;
16504
- tripContinuations = parsedTripContinuations;
16606
+ tripContinuationsMap = parsedTripContinuations;
16505
16607
  const transfersEnd = performance.now();
16506
- log.info(`${transfers.size} valid transfers and ${tripContinuations.size} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
16608
+ log.info(`${transfers.size} valid transfers and ${tripContinuationsMap.length} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
16507
16609
  }
16508
16610
  log.info(`Parsing ${STOP_TIMES_FILE}`);
16509
16611
  const stopTimesStart = performance.now();
@@ -16514,13 +16616,19 @@ class GtfsParser {
16514
16616
  log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
16515
16617
  log.info('Building stops adjacency structure');
16516
16618
  const stopsAdjacencyStart = performance.now();
16517
- const stopsAdjacency = buildStopsAdjacencyStructure(tripsMapping, serviceRoutes, routes, transfers, tripContinuations, parsedStops.size, activeStopIds);
16619
+ const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
16518
16620
  const stopsAdjacencyEnd = performance.now();
16519
16621
  log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
16520
16622
  yield zip.close();
16623
+ // temporary timetable for building continuations
16521
16624
  const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
16625
+ log.info('Building in-seat trip continuations');
16626
+ const tripContinuationsStart = performance.now();
16627
+ const tripContinuations = buildTripContinuations(tripsMapping, tripContinuationsMap, timetable, activeStopIds);
16628
+ const tripContinuationsEnd = performance.now();
16629
+ log.info(`${tripContinuations.size} in-seat trip continuations origins created. (${(tripContinuationsEnd - tripContinuationsStart).toFixed(2)}ms)`);
16522
16630
  log.info('Parsing complete.');
16523
- return timetable;
16631
+ return new Timetable(stopsAdjacency, routes, serviceRoutes, tripContinuations);
16524
16632
  });
16525
16633
  }
16526
16634
  /**