minotor 3.0.1 → 3.0.2

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 (81) hide show
  1. package/.cspell.json +12 -1
  2. package/.gitattributes +3 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +3 -0
  4. package/.github/workflows/minotor.yml +17 -1
  5. package/CHANGELOG.md +2 -2
  6. package/README.md +34 -14
  7. package/dist/__e2e__/router.test.d.ts +1 -0
  8. package/dist/cli/perf.d.ts +28 -0
  9. package/dist/cli/utils.d.ts +6 -2
  10. package/dist/cli.mjs +1967 -823
  11. package/dist/cli.mjs.map +1 -1
  12. package/dist/gtfs/trips.d.ts +1 -0
  13. package/dist/gtfs/utils.d.ts +1 -1
  14. package/dist/parser.cjs.js +1030 -627
  15. package/dist/parser.cjs.js.map +1 -1
  16. package/dist/parser.d.ts +4 -2
  17. package/dist/parser.esm.js +1030 -627
  18. package/dist/parser.esm.js.map +1 -1
  19. package/dist/router.cjs.js +1 -1
  20. package/dist/router.cjs.js.map +1 -1
  21. package/dist/router.d.ts +10 -5
  22. package/dist/router.esm.js +1 -1
  23. package/dist/router.esm.js.map +1 -1
  24. package/dist/router.umd.js +1 -1
  25. package/dist/router.umd.js.map +1 -1
  26. package/dist/routing/__tests__/result.test.d.ts +1 -0
  27. package/dist/routing/query.d.ts +27 -6
  28. package/dist/routing/result.d.ts +1 -1
  29. package/dist/routing/route.d.ts +47 -2
  30. package/dist/routing/router.d.ts +15 -1
  31. package/dist/stops/stopsIndex.d.ts +3 -3
  32. package/dist/timetable/__tests__/route.test.d.ts +1 -0
  33. package/dist/timetable/__tests__/time.test.d.ts +1 -0
  34. package/dist/timetable/io.d.ts +7 -1
  35. package/dist/timetable/proto/timetable.d.ts +1 -1
  36. package/dist/timetable/route.d.ts +155 -0
  37. package/dist/timetable/time.d.ts +21 -0
  38. package/dist/timetable/timetable.d.ts +41 -61
  39. package/package.json +36 -34
  40. package/src/__e2e__/benchmark.json +22 -0
  41. package/src/__e2e__/router.test.ts +209 -0
  42. package/src/__e2e__/timetable/stops.bin +3 -0
  43. package/src/__e2e__/timetable/timetable.bin +3 -0
  44. package/src/cli/minotor.ts +51 -1
  45. package/src/cli/perf.ts +136 -0
  46. package/src/cli/repl.ts +26 -13
  47. package/src/cli/utils.ts +6 -28
  48. package/src/gtfs/__tests__/parser.test.ts +12 -15
  49. package/src/gtfs/__tests__/services.test.ts +1 -0
  50. package/src/gtfs/__tests__/transfers.test.ts +0 -1
  51. package/src/gtfs/__tests__/trips.test.ts +67 -74
  52. package/src/gtfs/profiles/ch.ts +1 -1
  53. package/src/gtfs/routes.ts +4 -4
  54. package/src/gtfs/services.ts +15 -2
  55. package/src/gtfs/stops.ts +7 -3
  56. package/src/gtfs/transfers.ts +6 -3
  57. package/src/gtfs/trips.ts +33 -16
  58. package/src/gtfs/utils.ts +13 -2
  59. package/src/parser.ts +4 -2
  60. package/src/router.ts +17 -11
  61. package/src/routing/__tests__/result.test.ts +392 -0
  62. package/src/routing/__tests__/router.test.ts +94 -137
  63. package/src/routing/query.ts +28 -7
  64. package/src/routing/result.ts +10 -5
  65. package/src/routing/route.ts +95 -9
  66. package/src/routing/router.ts +82 -66
  67. package/src/stops/__tests__/io.test.ts +1 -1
  68. package/src/stops/__tests__/stopFinder.test.ts +1 -1
  69. package/src/stops/proto/stops.ts +4 -4
  70. package/src/stops/stopsIndex.ts +3 -3
  71. package/src/timetable/__tests__/io.test.ts +16 -23
  72. package/src/timetable/__tests__/route.test.ts +317 -0
  73. package/src/timetable/__tests__/time.test.ts +494 -0
  74. package/src/timetable/__tests__/timetable.test.ts +64 -75
  75. package/src/timetable/io.ts +32 -26
  76. package/src/timetable/proto/timetable.proto +1 -1
  77. package/src/timetable/proto/timetable.ts +13 -13
  78. package/src/timetable/route.ts +347 -0
  79. package/src/timetable/time.ts +40 -8
  80. package/src/timetable/timetable.ts +74 -165
  81. package/tsconfig.build.json +1 -1
@@ -11,58 +11,8 @@ import {
11
11
  serializeStopsAdjacency,
12
12
  } from './io.js';
13
13
  import { Timetable as ProtoTimetable } from './proto/timetable.js';
14
- import { Time } from './time.js';
14
+ import { Route, RouteId } from './route.js';
15
15
 
16
- // Identifies all trips of a given service route sharing the same list of stops.
17
- export type RouteId = string;
18
-
19
- export const REGULAR = 0;
20
- export const NOT_AVAILABLE = 1;
21
- export const MUST_PHONE_AGENCY = 2;
22
- export const MUST_COORDINATE_WITH_DRIVER = 3;
23
-
24
- export type PickUpDropOffType =
25
- | 0 // REGULAR
26
- | 1 // NOT_AVAILABLE
27
- | 2 // MUST_PHONE_AGENCY
28
- | 3; // MUST_COORDINATE_WITH_DRIVER
29
-
30
- export type Route = {
31
- /**
32
- * Arrivals and departures encoded as minutes from midnight.
33
- * Format: [arrival1, departure1, arrival2, departure2, etc.]
34
- */
35
- stopTimes: Uint16Array;
36
- /**
37
- * PickUp and DropOff types represented as a binary Uint8Array.
38
- * Values:
39
- * 0: REGULAR
40
- * 1: NOT_AVAILABLE
41
- * 2: MUST_PHONE_AGENCY
42
- * 3: MUST_COORDINATE_WITH_DRIVER
43
- * Format: [pickupTypeStop1, dropOffTypeStop1, pickupTypeStop2, dropOffTypeStop2, etc.]
44
- */
45
- pickUpDropOffTypes: Uint8Array;
46
- /**
47
- * A binary array of stopIds in the route.
48
- * [stop1, stop2, stop3,...]
49
- */
50
- stops: Uint32Array;
51
- /**
52
- * A reverse mapping of each stop with their index in the route:
53
- * {
54
- * 4: 0,
55
- * 5: 1,
56
- * ...
57
- * }
58
- */
59
- stopIndices: Map<StopId, number>;
60
- /**
61
- * The identifier of the route as a service shown to users.
62
- */
63
- serviceRouteId: ServiceRouteId;
64
- // TODO Add tripIds for real-time support
65
- };
66
16
  export type RoutesAdjacency = Map<RouteId, Route>;
67
17
 
68
18
  export type TransferType =
@@ -109,12 +59,7 @@ export type ServiceRoute = {
109
59
  // Service is here a synonym for route in the GTFS sense.
110
60
  export type ServiceRoutesMap = Map<ServiceRouteId, ServiceRoute>;
111
61
 
112
- // a trip index corresponds to the index of the
113
- // first stop time in the trip modulo the number of stops
114
- // in the given route
115
- type TripIndex = number;
116
-
117
- export const ALL_TRANSPORT_MODES: RouteType[] = [
62
+ export const ALL_TRANSPORT_MODES: Set<RouteType> = new Set([
118
63
  'TRAM',
119
64
  'SUBWAY',
120
65
  'RAIL',
@@ -125,13 +70,12 @@ export const ALL_TRANSPORT_MODES: RouteType[] = [
125
70
  'FUNICULAR',
126
71
  'TROLLEYBUS',
127
72
  'MONORAIL',
128
- ];
73
+ ]);
129
74
 
130
75
  export const CURRENT_VERSION = '0.0.3';
131
76
 
132
77
  /**
133
- * The internal transit timetable format
134
- * reuses some GTFS concepts for the sake of simplicity for now.
78
+ * The internal transit timetable format.
135
79
  */
136
80
  export class Timetable {
137
81
  private readonly stopsAdjacency: StopsAdjacency;
@@ -149,9 +93,9 @@ export class Timetable {
149
93
  }
150
94
 
151
95
  /**
152
- * Serializes the Timetable into a binary protobuf.
96
+ * Serializes the Timetable into a binary array.
153
97
  *
154
- * @returns {Uint8Array} - The serialized binary data.
98
+ * @returns The serialized binary data.
155
99
  */
156
100
  serialize(): Uint8Array {
157
101
  const protoTimetable = {
@@ -168,8 +112,8 @@ export class Timetable {
168
112
  /**
169
113
  * Deserializes a binary protobuf into a Timetable object.
170
114
  *
171
- * @param {Uint8Array} data - The binary data to deserialize.
172
- * @returns {Timetable} - The deserialized Timetable object.
115
+ * @param data - The binary data to deserialize.
116
+ * @returns The deserialized Timetable object.
173
117
  */
174
118
  static fromData(data: Uint8Array): Timetable {
175
119
  const reader = new BinaryReader(data);
@@ -191,129 +135,94 @@ export class Timetable {
191
135
  );
192
136
  }
193
137
 
194
- getRoutesThroughStop(stopId: StopId): RouteId[] {
195
- const stopAdjacency = this.stopsAdjacency.get(stopId);
196
- if (!stopAdjacency) {
197
- return [];
198
- }
199
- return stopAdjacency.routes;
200
- }
201
-
138
+ /**
139
+ * Retrieves the route associated with the given route ID.
140
+ *
141
+ * @param routeId - The ID of the route to be retrieved.
142
+ * @returns The route corresponding to the provided ID,
143
+ * or undefined if no such route exists.
144
+ */
202
145
  getRoute(routeId: RouteId): Route | undefined {
203
146
  return this.routesAdjacency.get(routeId);
204
147
  }
205
148
 
149
+ /**
150
+ * Retrieves all transfer options available at the specified stop.
151
+ *
152
+ * @param stopId - The ID of the stop to get transfers for.
153
+ * @returns An array of transfer options available at the stop.
154
+ */
206
155
  getTransfers(stopId: StopId): Transfer[] {
207
156
  return this.stopsAdjacency.get(stopId)?.transfers ?? [];
208
157
  }
209
158
 
159
+ /**
160
+ * Retrieves the service route associated with the given route.
161
+ * A service route refers to a collection of trips that are displayed
162
+ * to riders as a single service.
163
+ *
164
+ * @param route - The route for which the service route is to be retrieved.
165
+ * @returns The service route corresponding to the provided route.
166
+ */
167
+ getServiceRoute(route: Route): ServiceRoute {
168
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
169
+ return this.routes.get(route.serviceRoute())!;
170
+ }
171
+
172
+ /**
173
+ * Finds all routes passing through a stop.
174
+ *
175
+ * @param stopId - The ID of the stop to find routes for.
176
+ * @returns An array of routes passing through the specified stop.
177
+ */
178
+ routesPassingThrough(stopId: StopId): Route[] {
179
+ const stopData = this.stopsAdjacency.get(stopId);
180
+ if (!stopData) {
181
+ return [];
182
+ }
183
+ const routes: Route[] = [];
184
+ for (const routeId of stopData.routes) {
185
+ const route = this.routesAdjacency.get(routeId);
186
+ if (route) {
187
+ routes.push(route);
188
+ }
189
+ }
190
+ return routes;
191
+ }
192
+
210
193
  /**
211
194
  * Finds routes that are reachable from a set of stop IDs.
212
195
  * Also identifies the first stop available to hop on each route among
213
196
  * the input stops.
197
+ *
198
+ * @param fromStops - The set of stop IDs to find reachable routes from.
199
+ * @param transportModes - The set of transport modes to consider for reachable routes.
200
+ * @returns A map of reachable routes to the first stop available to hop on each route.
214
201
  */
215
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
216
202
  findReachableRoutes(
217
203
  fromStops: Set<StopId>,
218
- transportModes: RouteType[] = ALL_TRANSPORT_MODES,
219
- ): Map<RouteId, StopId> {
220
- const reachableRoutes = new Map<RouteId, StopId>();
221
- for (const stop of fromStops) {
222
- const validRoutes = this.stopsAdjacency
223
- .get(stop)
224
- ?.routes.filter((routeId) => {
225
- const serviceRoute = this.getServiceRouteFromRouteId(routeId);
226
- if (!serviceRoute) {
227
- return false;
228
- }
229
- return transportModes.includes(serviceRoute.type);
230
- });
231
- for (const routeId of validRoutes || []) {
232
- const hopOnStop = reachableRoutes.get(routeId);
204
+ transportModes: Set<RouteType> = ALL_TRANSPORT_MODES,
205
+ ): Map<Route, StopId> {
206
+ const reachableRoutes = new Map<Route, StopId>();
207
+ for (const originStop of fromStops) {
208
+ const validRoutes = this.routesPassingThrough(originStop).filter(
209
+ (route) => {
210
+ const serviceRoute = this.getServiceRoute(route);
211
+ return transportModes.has(serviceRoute.type);
212
+ },
213
+ );
214
+ for (const route of validRoutes) {
215
+ const hopOnStop = reachableRoutes.get(route);
233
216
  if (hopOnStop) {
234
- // Checks if the existing hop on stop is before the current stop
235
- const routeStopIndices =
236
- this.routesAdjacency.get(routeId)!.stopIndices;
237
- const stopIndex = routeStopIndices.get(stop)!;
238
- const hopOnStopIndex = routeStopIndices.get(hopOnStop)!;
239
- if (stopIndex < hopOnStopIndex) {
217
+ if (route.isBefore(originStop, hopOnStop)) {
240
218
  // if the current stop is before the existing hop on stop, replace it
241
- reachableRoutes.set(routeId, stop);
219
+ reachableRoutes.set(route, originStop);
242
220
  }
243
221
  } else {
244
- reachableRoutes.set(routeId, stop);
222
+ reachableRoutes.set(route, originStop);
245
223
  }
246
224
  }
247
225
  }
248
226
  return reachableRoutes;
249
227
  }
250
-
251
- getServiceRouteFromRouteId(routeId: RouteId): ServiceRoute | undefined {
252
- const route = this.routesAdjacency.get(routeId);
253
- if (!route) {
254
- console.warn(`Route ${routeId} not found.`);
255
- return undefined;
256
- }
257
- return this.routes.get(route.serviceRouteId);
258
- }
259
-
260
- getServiceRoute(serviceRouteId: ServiceRouteId): ServiceRoute | undefined {
261
- return this.routes.get(serviceRouteId);
262
- }
263
-
264
- /**
265
- * Finds the earliest trip that can be taken from a specific stop on a given route,
266
- * optionally constrained by a latest trip index and a time before which the trip
267
- * should not depart.
268
- */
269
- findEarliestTrip(
270
- route: Route,
271
- stopId: StopId,
272
- beforeTrip?: TripIndex,
273
- after: Time = Time.origin(),
274
- ): TripIndex | undefined {
275
- const stopIndex = route.stopIndices.get(stopId)!;
276
-
277
- const stopsNumber = route.stops.length;
278
-
279
- if (beforeTrip === undefined) {
280
- for (
281
- let tripIndex = 0;
282
- tripIndex < route.stopTimes.length / stopsNumber;
283
- tripIndex++
284
- ) {
285
- const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
286
- const departure = route.stopTimes[stopTimeIndex * 2 + 1]!;
287
- const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2]!;
288
- if (departure >= after.toMinutes() && pickUpType !== NOT_AVAILABLE) {
289
- return tripIndex;
290
- }
291
- }
292
- return undefined;
293
- } else {
294
- let earliestTripIndex: TripIndex | undefined;
295
- let earliestDeparture: Time | undefined;
296
- for (
297
- let tripIndex = beforeTrip; // ?? route.stopTimes.length / stopsNumber - 1;
298
- tripIndex >= 0;
299
- tripIndex--
300
- ) {
301
- const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
302
- const departure = route.stopTimes[stopTimeIndex * 2 + 1]!;
303
- const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2]!;
304
- if (departure < after.toMinutes()) {
305
- break;
306
- }
307
- if (
308
- pickUpType !== NOT_AVAILABLE &&
309
- (earliestDeparture === undefined ||
310
- departure < earliestDeparture.toMinutes())
311
- ) {
312
- earliestTripIndex = tripIndex;
313
- earliestDeparture = Time.fromMinutes(departure);
314
- }
315
- }
316
- return earliestTripIndex;
317
- }
318
- }
319
228
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
- "exclude": ["./src/**/__tests__"]
3
+ "exclude": ["./src/**/__tests__", "./src/**/__e2e_tests__"]
4
4
  }