minotor 3.0.0 → 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.
- package/.cspell.json +14 -1
- package/.gitattributes +3 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +3 -0
- package/.github/workflows/minotor.yml +17 -1
- package/CHANGELOG.md +3 -9
- package/README.md +47 -17
- package/dist/__e2e__/router.test.d.ts +1 -0
- package/dist/cli/perf.d.ts +28 -0
- package/dist/cli/utils.d.ts +6 -2
- package/dist/cli.mjs +1967 -823
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/trips.d.ts +1 -0
- package/dist/gtfs/utils.d.ts +1 -1
- package/dist/parser.cjs.js +1030 -627
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.d.ts +4 -2
- package/dist/parser.esm.js +1030 -627
- package/dist/parser.esm.js.map +1 -1
- package/dist/router.cjs.js +1 -1
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +10 -5
- package/dist/router.esm.js +1 -1
- package/dist/router.esm.js.map +1 -1
- package/dist/router.umd.js +1 -1
- package/dist/router.umd.js.map +1 -1
- package/dist/routing/__tests__/result.test.d.ts +1 -0
- package/dist/routing/query.d.ts +27 -6
- package/dist/routing/result.d.ts +1 -1
- package/dist/routing/route.d.ts +47 -2
- package/dist/routing/router.d.ts +15 -1
- package/dist/stops/stopsIndex.d.ts +3 -3
- package/dist/timetable/__tests__/route.test.d.ts +1 -0
- package/dist/timetable/__tests__/time.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +7 -1
- package/dist/timetable/proto/timetable.d.ts +1 -1
- package/dist/timetable/route.d.ts +155 -0
- package/dist/timetable/time.d.ts +21 -0
- package/dist/timetable/timetable.d.ts +41 -61
- package/package.json +36 -35
- package/src/__e2e__/benchmark.json +22 -0
- package/src/__e2e__/router.test.ts +209 -0
- package/src/__e2e__/timetable/stops.bin +3 -0
- package/src/__e2e__/timetable/timetable.bin +3 -0
- package/src/cli/minotor.ts +51 -1
- package/src/cli/perf.ts +136 -0
- package/src/cli/repl.ts +26 -13
- package/src/cli/utils.ts +6 -28
- package/src/gtfs/__tests__/parser.test.ts +12 -15
- package/src/gtfs/__tests__/services.test.ts +1 -0
- package/src/gtfs/__tests__/transfers.test.ts +0 -1
- package/src/gtfs/__tests__/trips.test.ts +67 -74
- package/src/gtfs/profiles/ch.ts +1 -1
- package/src/gtfs/routes.ts +4 -4
- package/src/gtfs/services.ts +15 -2
- package/src/gtfs/stops.ts +7 -3
- package/src/gtfs/transfers.ts +6 -3
- package/src/gtfs/trips.ts +33 -16
- package/src/gtfs/utils.ts +13 -2
- package/src/parser.ts +4 -2
- package/src/router.ts +17 -11
- package/src/routing/__tests__/result.test.ts +392 -0
- package/src/routing/__tests__/router.test.ts +94 -137
- package/src/routing/query.ts +28 -7
- package/src/routing/result.ts +10 -5
- package/src/routing/route.ts +95 -9
- package/src/routing/router.ts +82 -66
- package/src/stops/__tests__/io.test.ts +1 -1
- package/src/stops/__tests__/stopFinder.test.ts +1 -1
- package/src/stops/proto/stops.ts +4 -4
- package/src/stops/stopsIndex.ts +3 -3
- package/src/timetable/__tests__/io.test.ts +16 -23
- package/src/timetable/__tests__/route.test.ts +317 -0
- package/src/timetable/__tests__/time.test.ts +494 -0
- package/src/timetable/__tests__/timetable.test.ts +64 -75
- package/src/timetable/io.ts +32 -26
- package/src/timetable/proto/timetable.proto +1 -1
- package/src/timetable/proto/timetable.ts +13 -13
- package/src/timetable/route.ts +347 -0
- package/src/timetable/time.ts +40 -8
- package/src/timetable/timetable.ts +74 -165
- 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 {
|
|
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
|
-
|
|
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
|
|
96
|
+
* Serializes the Timetable into a binary array.
|
|
153
97
|
*
|
|
154
|
-
* @returns
|
|
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
|
|
172
|
-
* @returns
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
219
|
-
): Map<
|
|
220
|
-
const reachableRoutes = new Map<
|
|
221
|
-
for (const
|
|
222
|
-
const validRoutes = this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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(
|
|
219
|
+
reachableRoutes.set(route, originStop);
|
|
242
220
|
}
|
|
243
221
|
} else {
|
|
244
|
-
reachableRoutes.set(
|
|
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
|
}
|
package/tsconfig.build.json
CHANGED