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
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MUST_COORDINATE_WITH_DRIVER,
|
|
6
|
+
MUST_PHONE_AGENCY,
|
|
7
|
+
NOT_AVAILABLE,
|
|
8
|
+
REGULAR,
|
|
9
|
+
Route,
|
|
10
|
+
} from '../route.js';
|
|
11
|
+
import { Time } from '../time.js';
|
|
12
|
+
|
|
13
|
+
describe('Route', () => {
|
|
14
|
+
const stopTimes = new Uint16Array([
|
|
15
|
+
// Trip 0: Stop 1 -> Stop 2
|
|
16
|
+
Time.fromHMS(8, 0, 0).toMinutes(),
|
|
17
|
+
Time.fromHMS(8, 1, 0).toMinutes(),
|
|
18
|
+
Time.fromHMS(8, 30, 0).toMinutes(),
|
|
19
|
+
Time.fromHMS(8, 31, 0).toMinutes(),
|
|
20
|
+
// Trip 1: Stop 1 -> Stop 2
|
|
21
|
+
Time.fromHMS(9, 0, 0).toMinutes(),
|
|
22
|
+
Time.fromHMS(9, 1, 0).toMinutes(),
|
|
23
|
+
Time.fromHMS(9, 30, 0).toMinutes(),
|
|
24
|
+
Time.fromHMS(9, 31, 0).toMinutes(),
|
|
25
|
+
// Trip 2: Stop 1 -> Stop 2
|
|
26
|
+
Time.fromHMS(10, 0, 0).toMinutes(),
|
|
27
|
+
Time.fromHMS(10, 1, 0).toMinutes(),
|
|
28
|
+
Time.fromHMS(10, 30, 0).toMinutes(),
|
|
29
|
+
Time.fromHMS(10, 31, 0).toMinutes(),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const pickUpDropOffTypes = new Uint8Array([
|
|
33
|
+
// Trip 0
|
|
34
|
+
REGULAR,
|
|
35
|
+
REGULAR,
|
|
36
|
+
NOT_AVAILABLE,
|
|
37
|
+
REGULAR,
|
|
38
|
+
// Trip 1
|
|
39
|
+
REGULAR,
|
|
40
|
+
REGULAR,
|
|
41
|
+
REGULAR,
|
|
42
|
+
REGULAR,
|
|
43
|
+
// Trip 2
|
|
44
|
+
MUST_PHONE_AGENCY,
|
|
45
|
+
REGULAR,
|
|
46
|
+
MUST_COORDINATE_WITH_DRIVER,
|
|
47
|
+
REGULAR,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const stops = new Uint32Array([1001, 1002]);
|
|
51
|
+
const serviceRouteId = 'test-route-1';
|
|
52
|
+
|
|
53
|
+
const route = new Route(stopTimes, pickUpDropOffTypes, stops, serviceRouteId);
|
|
54
|
+
|
|
55
|
+
describe('constructor', () => {
|
|
56
|
+
it('should create a route with correct properties', () => {
|
|
57
|
+
assert.strictEqual(route.getNbStops(), 2);
|
|
58
|
+
assert.strictEqual(route.serviceRoute(), serviceRouteId);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should handle empty route', () => {
|
|
62
|
+
const emptyRoute = new Route(
|
|
63
|
+
new Uint16Array([]),
|
|
64
|
+
new Uint8Array([]),
|
|
65
|
+
new Uint32Array([]),
|
|
66
|
+
'empty-route',
|
|
67
|
+
);
|
|
68
|
+
assert.strictEqual(emptyRoute.getNbStops(), 0);
|
|
69
|
+
assert.strictEqual(emptyRoute.serviceRoute(), 'empty-route');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('serialize', () => {
|
|
74
|
+
it('should serialize route data correctly', () => {
|
|
75
|
+
const serialized = route.serialize();
|
|
76
|
+
assert.deepStrictEqual(serialized.stopTimes, stopTimes);
|
|
77
|
+
assert.deepStrictEqual(serialized.pickUpDropOffTypes, pickUpDropOffTypes);
|
|
78
|
+
assert.deepStrictEqual(serialized.stops, stops);
|
|
79
|
+
assert.strictEqual(serialized.serviceRouteId, serviceRouteId);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('isBefore', () => {
|
|
84
|
+
it('should return true when stopA is before stopB', () => {
|
|
85
|
+
assert.strictEqual(route.isBefore(1001, 1002), true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return false when stopA is after stopB', () => {
|
|
89
|
+
assert.strictEqual(route.isBefore(1002, 1001), false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return false when stopA equals stopB', () => {
|
|
93
|
+
assert.strictEqual(route.isBefore(1001, 1001), false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should throw error when stopA is not found', () => {
|
|
97
|
+
assert.throws(
|
|
98
|
+
() => route.isBefore(9999, 1002),
|
|
99
|
+
/Stop index undefined not found in route test-route-1/,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should throw error when stopB is not found', () => {
|
|
104
|
+
assert.throws(
|
|
105
|
+
() => route.isBefore(1001, 9999),
|
|
106
|
+
/Stop index undefined not found in route test-route-1/,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('getNbStops', () => {
|
|
112
|
+
it('should return correct number of stops', () => {
|
|
113
|
+
assert.strictEqual(route.getNbStops(), 2);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('serviceRoute', () => {
|
|
118
|
+
it('should return correct service route ID', () => {
|
|
119
|
+
assert.strictEqual(route.serviceRoute(), serviceRouteId);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('arrivalAt', () => {
|
|
124
|
+
it('should return correct arrival time for trip 0 at stop 1001', () => {
|
|
125
|
+
const arrival = route.arrivalAt(1001, 0);
|
|
126
|
+
assert.strictEqual(
|
|
127
|
+
arrival.toMinutes(),
|
|
128
|
+
Time.fromHMS(8, 0, 0).toMinutes(),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return correct arrival time for trip 1 at stop 1002', () => {
|
|
133
|
+
const arrival = route.arrivalAt(1002, 1);
|
|
134
|
+
assert.strictEqual(
|
|
135
|
+
arrival.toMinutes(),
|
|
136
|
+
Time.fromHMS(9, 30, 0).toMinutes(),
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should throw error for invalid stop ID', () => {
|
|
141
|
+
assert.throws(
|
|
142
|
+
() => route.arrivalAt(9999, 0),
|
|
143
|
+
/Stop index for 9999 not found in route test-route-1/,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should throw error for invalid trip index', () => {
|
|
148
|
+
assert.throws(
|
|
149
|
+
() => route.arrivalAt(1001, 999),
|
|
150
|
+
/Arrival time not found for stop 1001 at trip index 999/,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('departureFrom', () => {
|
|
156
|
+
it('should return correct departure time for trip 0 at stop 1001', () => {
|
|
157
|
+
const departure = route.departureFrom(1001, 0);
|
|
158
|
+
assert.strictEqual(
|
|
159
|
+
departure.toMinutes(),
|
|
160
|
+
Time.fromHMS(8, 1, 0).toMinutes(),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should return correct departure time for trip 2 at stop 1002', () => {
|
|
165
|
+
const departure = route.departureFrom(1002, 2);
|
|
166
|
+
assert.strictEqual(
|
|
167
|
+
departure.toMinutes(),
|
|
168
|
+
Time.fromHMS(10, 31, 0).toMinutes(),
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should throw error for invalid stop ID', () => {
|
|
173
|
+
assert.throws(
|
|
174
|
+
() => route.departureFrom(9999, 0),
|
|
175
|
+
/Stop index for 9999 not found in route test-route-1/,
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should throw error for invalid trip index', () => {
|
|
180
|
+
assert.throws(
|
|
181
|
+
() => route.departureFrom(1001, 999),
|
|
182
|
+
/Departure time not found for stop 1001 at trip index 999/,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('pickUpTypeFrom', () => {
|
|
188
|
+
it('should return REGULAR pickup type for trip 0 at stop 1001', () => {
|
|
189
|
+
const pickUpType = route.pickUpTypeFrom(1001, 0);
|
|
190
|
+
assert.strictEqual(pickUpType, 'REGULAR');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return NOT_AVAILABLE pickup type for trip 0 at stop 1002', () => {
|
|
194
|
+
const pickUpType = route.pickUpTypeFrom(1002, 0);
|
|
195
|
+
assert.strictEqual(pickUpType, 'NOT_AVAILABLE');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return MUST_PHONE_AGENCY pickup type for trip 2 at stop 1001', () => {
|
|
199
|
+
const pickUpType = route.pickUpTypeFrom(1001, 2);
|
|
200
|
+
assert.strictEqual(pickUpType, 'MUST_PHONE_AGENCY');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should throw error for invalid stop ID', () => {
|
|
204
|
+
assert.throws(
|
|
205
|
+
() => route.pickUpTypeFrom(9999, 0),
|
|
206
|
+
/Stop index for 9999 not found in route test-route-1/,
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should throw error for invalid trip index', () => {
|
|
211
|
+
assert.throws(
|
|
212
|
+
() => route.pickUpTypeFrom(1001, 999),
|
|
213
|
+
/Pick up type not found for stop 1001 at trip index 999/,
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('dropOffTypeAt', () => {
|
|
219
|
+
it('should return REGULAR drop off type for trip 0 at stop 1001', () => {
|
|
220
|
+
const dropOffType = route.dropOffTypeAt(1001, 0);
|
|
221
|
+
assert.strictEqual(dropOffType, 'REGULAR');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return REGULAR drop off type for trip 1 at stop 1002', () => {
|
|
225
|
+
const dropOffType = route.dropOffTypeAt(1002, 1);
|
|
226
|
+
assert.strictEqual(dropOffType, 'REGULAR');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should throw error for invalid stop ID', () => {
|
|
230
|
+
assert.throws(
|
|
231
|
+
() => route.dropOffTypeAt(9999, 0),
|
|
232
|
+
/Stop index for 9999 not found in route test-route-1/,
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should throw error for invalid trip index', () => {
|
|
237
|
+
assert.throws(
|
|
238
|
+
() => route.dropOffTypeAt(1001, 999),
|
|
239
|
+
/Drop off type not found for stop 1001 at trip index 999/,
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('stopsIterator', () => {
|
|
245
|
+
it('should iterate over all stops when no start stop is provided', () => {
|
|
246
|
+
const stopsList = Array.from(route.stopsIterator());
|
|
247
|
+
assert.deepStrictEqual(stopsList, [1001, 1002]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should iterate from specified start stop', () => {
|
|
251
|
+
const stopsList = Array.from(route.stopsIterator(1002));
|
|
252
|
+
assert.deepStrictEqual(stopsList, [1002]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should throw error for invalid start stop ID', () => {
|
|
256
|
+
assert.throws(
|
|
257
|
+
() => Array.from(route.stopsIterator(9999)),
|
|
258
|
+
/Start stop 9999 not found in route test-route-1/,
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('findEarliestTrip', () => {
|
|
264
|
+
it('should find earliest trip without time constraint', () => {
|
|
265
|
+
const tripIndex = route.findEarliestTrip(1001);
|
|
266
|
+
assert.strictEqual(tripIndex, 0);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should find earliest trip after specified time', () => {
|
|
270
|
+
const afterTime = Time.fromHMS(8, 30, 0);
|
|
271
|
+
const tripIndex = route.findEarliestTrip(1001, afterTime);
|
|
272
|
+
assert.strictEqual(tripIndex, 1);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should find earliest trip with exact match time', () => {
|
|
276
|
+
const afterTime = Time.fromHMS(9, 1, 0);
|
|
277
|
+
const tripIndex = route.findEarliestTrip(1001, afterTime);
|
|
278
|
+
assert.strictEqual(tripIndex, 1);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should return undefined when no trip is available after specified time', () => {
|
|
282
|
+
const afterTime = Time.fromHMS(23, 0, 0);
|
|
283
|
+
const tripIndex = route.findEarliestTrip(1001, afterTime);
|
|
284
|
+
assert.strictEqual(tripIndex, undefined);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should skip trips where pickup is not available', () => {
|
|
288
|
+
const tripIndex = route.findEarliestTrip(1002);
|
|
289
|
+
// Trip 0 has NOT_AVAILABLE pickup at stop 1002, so should return trip 1
|
|
290
|
+
assert.strictEqual(tripIndex, 1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should respect beforeTrip constraint', () => {
|
|
294
|
+
const tripIndex = route.findEarliestTrip(1001, Time.fromHMS(8, 2, 0), 1);
|
|
295
|
+
assert.strictEqual(tripIndex, undefined);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should return undefined when beforeTrip is 0', () => {
|
|
299
|
+
const tripIndex = route.findEarliestTrip(1001, Time.origin(), 0);
|
|
300
|
+
assert.strictEqual(tripIndex, undefined);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should handle MUST_PHONE_AGENCY pickup type', () => {
|
|
304
|
+
const afterTime = Time.fromHMS(9, 30, 0);
|
|
305
|
+
const tripIndex = route.findEarliestTrip(1001, afterTime);
|
|
306
|
+
// Should find trip 2 even though it requires phone agency
|
|
307
|
+
assert.strictEqual(tripIndex, 2);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should throw error for invalid stop ID', () => {
|
|
311
|
+
assert.throws(
|
|
312
|
+
() => route.findEarliestTrip(9999),
|
|
313
|
+
/Stop index for 9999 not found in route test-route-1/,
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|