minotor 7.0.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cspell.json +11 -1
- package/CHANGELOG.md +8 -3
- package/README.md +26 -24
- package/dist/cli.mjs +1243 -267
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +13 -4
- package/dist/gtfs/trips.d.ts +12 -7
- package/dist/parser.cjs.js +494 -71
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +494 -71
- 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 +2 -2
- 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__/plotter.test.d.ts +1 -0
- package/dist/routing/plotter.d.ts +42 -3
- package/dist/routing/result.d.ts +23 -7
- package/dist/routing/route.d.ts +2 -0
- package/dist/routing/router.d.ts +78 -19
- package/dist/timetable/__tests__/tripId.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +4 -2
- package/dist/timetable/proto/timetable.d.ts +13 -1
- package/dist/timetable/route.d.ts +41 -8
- package/dist/timetable/timetable.d.ts +18 -3
- package/dist/timetable/tripId.d.ts +15 -0
- package/package.json +1 -1
- package/src/__e2e__/router.test.ts +114 -105
- package/src/__e2e__/timetable/stops.bin +2 -2
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/repl.ts +259 -1
- package/src/gtfs/__tests__/transfers.test.ts +468 -12
- package/src/gtfs/__tests__/trips.test.ts +350 -28
- package/src/gtfs/parser.ts +16 -4
- package/src/gtfs/transfers.ts +61 -18
- package/src/gtfs/trips.ts +97 -22
- package/src/router.ts +2 -2
- package/src/routing/__tests__/plotter.test.ts +230 -0
- package/src/routing/__tests__/result.test.ts +486 -125
- package/src/routing/__tests__/route.test.ts +7 -3
- package/src/routing/__tests__/router.test.ts +378 -172
- package/src/routing/plotter.ts +279 -48
- package/src/routing/result.ts +114 -34
- package/src/routing/route.ts +0 -3
- package/src/routing/router.ts +332 -211
- package/src/timetable/__tests__/io.test.ts +33 -1
- package/src/timetable/__tests__/route.test.ts +10 -3
- package/src/timetable/__tests__/timetable.test.ts +225 -57
- package/src/timetable/__tests__/tripId.test.ts +27 -0
- package/src/timetable/io.ts +71 -10
- package/src/timetable/proto/timetable.proto +14 -2
- package/src/timetable/proto/timetable.ts +218 -20
- package/src/timetable/route.ts +152 -19
- package/src/timetable/timetable.ts +45 -6
- package/src/timetable/tripId.ts +29 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
3
|
|
|
4
|
+
import { Timetable } from '../../router.js';
|
|
4
5
|
import { Stop, StopId } from '../../stops/stops.js';
|
|
5
6
|
import { StopsIndex } from '../../stops/stopsIndex.js';
|
|
7
|
+
import { Duration } from '../../timetable/duration.js';
|
|
8
|
+
import { Route } from '../../timetable/route.js';
|
|
6
9
|
import { Time } from '../../timetable/time.js';
|
|
10
|
+
import { ServiceRoute, StopAdjacency } from '../../timetable/timetable.js';
|
|
7
11
|
import { Query } from '../query.js';
|
|
8
12
|
import { Result } from '../result.js';
|
|
9
|
-
import {
|
|
13
|
+
import { Arrival, RoutingEdge, TransferEdge, VehicleEdge } from '../router.js';
|
|
10
14
|
|
|
11
15
|
describe('Result', () => {
|
|
12
16
|
const stop1: Stop = {
|
|
@@ -86,7 +90,82 @@ describe('Result', () => {
|
|
|
86
90
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
87
91
|
};
|
|
88
92
|
|
|
89
|
-
const
|
|
93
|
+
const stopsAdjacency: StopAdjacency[] = [
|
|
94
|
+
{ routes: [0] },
|
|
95
|
+
{ routes: [0] },
|
|
96
|
+
{ routes: [0, 1] },
|
|
97
|
+
{ routes: [1] },
|
|
98
|
+
{ routes: [1] },
|
|
99
|
+
{ routes: [1] },
|
|
100
|
+
{ routes: [1] },
|
|
101
|
+
];
|
|
102
|
+
const routesAdjacency = [
|
|
103
|
+
Route.of({
|
|
104
|
+
id: 0,
|
|
105
|
+
serviceRouteId: 0,
|
|
106
|
+
trips: [
|
|
107
|
+
{
|
|
108
|
+
stops: [
|
|
109
|
+
{
|
|
110
|
+
id: 0,
|
|
111
|
+
arrivalTime: Time.fromString('08:00:00'),
|
|
112
|
+
departureTime: Time.fromString('08:05:00'),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 1,
|
|
116
|
+
arrivalTime: Time.fromString('08:30:00'),
|
|
117
|
+
departureTime: Time.fromString('08:35:00'),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 2,
|
|
121
|
+
arrivalTime: Time.fromString('09:00:00'),
|
|
122
|
+
departureTime: Time.fromString('09:05:00'),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
}),
|
|
128
|
+
Route.of({
|
|
129
|
+
id: 1,
|
|
130
|
+
serviceRouteId: 1,
|
|
131
|
+
trips: [
|
|
132
|
+
{
|
|
133
|
+
stops: [
|
|
134
|
+
{
|
|
135
|
+
id: 2,
|
|
136
|
+
arrivalTime: Time.fromString('09:10:00'),
|
|
137
|
+
departureTime: Time.fromString('09:15:00'),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 3,
|
|
141
|
+
arrivalTime: Time.fromString('09:45:00'),
|
|
142
|
+
departureTime: Time.fromString('09:50:00'),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 5,
|
|
146
|
+
arrivalTime: Time.fromString('10:10:00'),
|
|
147
|
+
departureTime: Time.fromString('10:15:00'),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
}),
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const routes: ServiceRoute[] = [
|
|
156
|
+
{
|
|
157
|
+
type: 'RAIL',
|
|
158
|
+
name: 'Line 1',
|
|
159
|
+
routes: [0],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: 'RAIL',
|
|
163
|
+
name: 'Line 2',
|
|
164
|
+
routes: [1],
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const mockStopsIndex = new StopsIndex([
|
|
90
169
|
stop1,
|
|
91
170
|
stop2,
|
|
92
171
|
stop3,
|
|
@@ -94,9 +173,8 @@ describe('Result', () => {
|
|
|
94
173
|
parentStop,
|
|
95
174
|
childStop1,
|
|
96
175
|
childStop2,
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const mockStopsIndex = new StopsIndex(stopsArray);
|
|
176
|
+
]);
|
|
177
|
+
const mockTimetable = new Timetable(stopsAdjacency, routesAdjacency, routes);
|
|
100
178
|
|
|
101
179
|
const mockQuery = new Query.Builder()
|
|
102
180
|
.from('stop1')
|
|
@@ -106,14 +184,18 @@ describe('Result', () => {
|
|
|
106
184
|
|
|
107
185
|
describe('bestRoute', () => {
|
|
108
186
|
it('should return undefined when no route exists', () => {
|
|
109
|
-
const earliestArrivals = new Map<StopId,
|
|
110
|
-
const
|
|
187
|
+
const earliestArrivals = new Map<StopId, Arrival>();
|
|
188
|
+
const graph: Map<StopId, RoutingEdge>[] = [];
|
|
111
189
|
|
|
112
190
|
const result = new Result(
|
|
113
191
|
mockQuery,
|
|
114
|
-
|
|
115
|
-
|
|
192
|
+
{
|
|
193
|
+
earliestArrivals,
|
|
194
|
+
graph,
|
|
195
|
+
destinations: [2, 3],
|
|
196
|
+
},
|
|
116
197
|
mockStopsIndex,
|
|
198
|
+
mockTimetable,
|
|
117
199
|
);
|
|
118
200
|
|
|
119
201
|
const route = result.bestRoute();
|
|
@@ -122,15 +204,21 @@ describe('Result', () => {
|
|
|
122
204
|
|
|
123
205
|
it('should return undefined for unreachable destination', () => {
|
|
124
206
|
const earliestArrivals = new Map([
|
|
125
|
-
[1, { arrival: Time.fromHMS(8, 30, 0), legNumber: 0
|
|
207
|
+
[1, { arrival: Time.fromHMS(8, 30, 0), legNumber: 0 }],
|
|
126
208
|
]);
|
|
127
|
-
const
|
|
209
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
210
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
211
|
+
];
|
|
128
212
|
|
|
129
213
|
const result = new Result(
|
|
130
214
|
mockQuery,
|
|
131
|
-
|
|
132
|
-
|
|
215
|
+
{
|
|
216
|
+
earliestArrivals,
|
|
217
|
+
graph,
|
|
218
|
+
destinations: [2, 3],
|
|
219
|
+
},
|
|
133
220
|
mockStopsIndex,
|
|
221
|
+
mockTimetable,
|
|
134
222
|
);
|
|
135
223
|
|
|
136
224
|
const route = result.bestRoute('stop4'); // stop4 not in earliestArrivals
|
|
@@ -139,125 +227,375 @@ describe('Result', () => {
|
|
|
139
227
|
|
|
140
228
|
it('should return route to closest destination when multiple destinations exist', () => {
|
|
141
229
|
const earliestArrivals = new Map([
|
|
142
|
-
[
|
|
143
|
-
[
|
|
230
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
231
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }], // faster destination
|
|
232
|
+
[3, { arrival: Time.fromHMS(9, 30, 0), legNumber: 1 }], // slower destination
|
|
144
233
|
]);
|
|
145
234
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
235
|
+
const vehicleEdge: VehicleEdge = {
|
|
236
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
237
|
+
from: 0,
|
|
238
|
+
to: 2,
|
|
239
|
+
routeId: 0,
|
|
240
|
+
tripIndex: 0,
|
|
152
241
|
};
|
|
153
242
|
|
|
154
|
-
const
|
|
155
|
-
new Map([
|
|
156
|
-
|
|
157
|
-
2,
|
|
158
|
-
{
|
|
159
|
-
arrival: Time.fromHMS(9, 0, 0),
|
|
160
|
-
legNumber: 0,
|
|
161
|
-
origin: 0,
|
|
162
|
-
leg: vehicleLegTo3,
|
|
163
|
-
} as TripLeg,
|
|
164
|
-
],
|
|
165
|
-
]),
|
|
243
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
244
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
245
|
+
new Map<StopId, RoutingEdge>([[2, vehicleEdge]]), // Round 1
|
|
166
246
|
];
|
|
167
247
|
|
|
168
248
|
const result = new Result(
|
|
169
249
|
mockQuery,
|
|
170
|
-
|
|
171
|
-
|
|
250
|
+
{
|
|
251
|
+
earliestArrivals,
|
|
252
|
+
graph,
|
|
253
|
+
destinations: [2, 3],
|
|
254
|
+
},
|
|
172
255
|
mockStopsIndex,
|
|
256
|
+
mockTimetable,
|
|
173
257
|
);
|
|
174
258
|
|
|
175
259
|
const route = result.bestRoute();
|
|
176
260
|
assert(route);
|
|
177
261
|
assert.strictEqual(route.legs.length, 1);
|
|
178
|
-
|
|
262
|
+
const firstLeg = route.legs[0];
|
|
263
|
+
assert(firstLeg);
|
|
264
|
+
assert.strictEqual(firstLeg.from.id, 0);
|
|
265
|
+
assert.strictEqual(firstLeg.to.id, 2);
|
|
179
266
|
});
|
|
180
267
|
|
|
181
268
|
it('should return route to fastest child stop when parent stop is queried', () => {
|
|
182
|
-
const vehicleLegToChild1 = {
|
|
183
|
-
from: stop1,
|
|
184
|
-
to: childStop1,
|
|
185
|
-
route: { type: 'BUS', name: 'Bus 101' },
|
|
186
|
-
departureTime: Time.fromHMS(8, 0, 0),
|
|
187
|
-
arrivalTime: Time.fromHMS(9, 0, 0),
|
|
188
|
-
};
|
|
189
|
-
|
|
190
269
|
const earliestArrivals = new Map([
|
|
191
|
-
[
|
|
192
|
-
[
|
|
270
|
+
[2, { arrival: Time.fromHMS(9, 10, 0), legNumber: 1 }], // intermediate stop
|
|
271
|
+
[5, { arrival: Time.fromHMS(10, 10, 0), legNumber: 2 }], // child1 - faster
|
|
272
|
+
[6, { arrival: Time.fromHMS(10, 30, 0), legNumber: 2 }], // child2 - slower
|
|
193
273
|
]);
|
|
194
274
|
|
|
195
|
-
const
|
|
196
|
-
|
|
275
|
+
const vehicleEdge: VehicleEdge = {
|
|
276
|
+
arrival: Time.fromHMS(10, 10, 0),
|
|
277
|
+
from: 2,
|
|
278
|
+
to: 5,
|
|
279
|
+
routeId: 1,
|
|
280
|
+
tripIndex: 0,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
284
|
+
new Map<StopId, RoutingEdge>([[2, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
285
|
+
new Map<StopId, RoutingEdge>([
|
|
197
286
|
[
|
|
198
|
-
|
|
287
|
+
2,
|
|
199
288
|
{
|
|
200
|
-
arrival: Time.fromHMS(9,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
289
|
+
arrival: Time.fromHMS(9, 10, 0),
|
|
290
|
+
from: 0,
|
|
291
|
+
to: 2,
|
|
292
|
+
routeId: 0,
|
|
293
|
+
tripIndex: 0,
|
|
294
|
+
},
|
|
205
295
|
],
|
|
206
|
-
]),
|
|
296
|
+
]), // Round 1
|
|
297
|
+
new Map<StopId, RoutingEdge>([[5, vehicleEdge]]), // Round 2
|
|
207
298
|
];
|
|
208
299
|
|
|
209
300
|
const result = new Result(
|
|
210
301
|
mockQuery,
|
|
211
|
-
|
|
212
|
-
|
|
302
|
+
{
|
|
303
|
+
earliestArrivals,
|
|
304
|
+
graph,
|
|
305
|
+
destinations: [4], // parent stop
|
|
306
|
+
},
|
|
213
307
|
mockStopsIndex,
|
|
308
|
+
mockTimetable,
|
|
214
309
|
);
|
|
215
310
|
|
|
216
311
|
const route = result.bestRoute('parent');
|
|
217
312
|
assert(route);
|
|
218
|
-
assert.strictEqual(route.legs.length,
|
|
219
|
-
|
|
313
|
+
assert.strictEqual(route.legs.length, 2);
|
|
314
|
+
const lastLeg = route.legs[route.legs.length - 1];
|
|
315
|
+
assert(lastLeg);
|
|
316
|
+
assert.strictEqual(lastLeg.to.id, 5); // should route to faster child
|
|
220
317
|
});
|
|
221
318
|
|
|
222
319
|
it('should handle simple single-leg route reconstruction', () => {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
320
|
+
const earliestArrivals = new Map([
|
|
321
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
322
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }], // destination
|
|
323
|
+
]);
|
|
324
|
+
|
|
325
|
+
const vehicleEdge: VehicleEdge = {
|
|
326
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
327
|
+
from: 0,
|
|
328
|
+
to: 2,
|
|
329
|
+
routeId: 0,
|
|
330
|
+
tripIndex: 0,
|
|
229
331
|
};
|
|
230
332
|
|
|
231
|
-
|
|
333
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
334
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
335
|
+
new Map<StopId, RoutingEdge>([[2, vehicleEdge]]), // Round 1
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
const result = new Result(
|
|
339
|
+
mockQuery,
|
|
340
|
+
{
|
|
341
|
+
earliestArrivals,
|
|
342
|
+
graph,
|
|
343
|
+
destinations: [2],
|
|
344
|
+
},
|
|
345
|
+
mockStopsIndex,
|
|
346
|
+
mockTimetable,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const route = result.bestRoute('stop3');
|
|
350
|
+
assert(route);
|
|
351
|
+
assert.strictEqual(route.legs.length, 1);
|
|
352
|
+
const firstLeg = route.legs[0];
|
|
353
|
+
assert(firstLeg);
|
|
354
|
+
assert.strictEqual(firstLeg.from.id, 0);
|
|
355
|
+
assert.strictEqual(firstLeg.to.id, 2);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should handle multi-leg route with transfer', () => {
|
|
232
359
|
const earliestArrivals = new Map([
|
|
233
|
-
[
|
|
360
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
361
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }], // intermediate stop
|
|
362
|
+
[3, { arrival: Time.fromHMS(9, 45, 0), legNumber: 2 }], // final destination
|
|
234
363
|
]);
|
|
235
364
|
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
365
|
+
const firstVehicleEdge: VehicleEdge = {
|
|
366
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
367
|
+
from: 0,
|
|
368
|
+
to: 2,
|
|
369
|
+
routeId: 0,
|
|
370
|
+
tripIndex: 0,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const secondVehicleEdge: VehicleEdge = {
|
|
374
|
+
arrival: Time.fromHMS(9, 45, 0),
|
|
375
|
+
from: 2,
|
|
376
|
+
to: 3,
|
|
377
|
+
routeId: 1,
|
|
378
|
+
tripIndex: 0,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
382
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
383
|
+
new Map<StopId, RoutingEdge>([[2, firstVehicleEdge]]), // Round 1
|
|
384
|
+
new Map<StopId, RoutingEdge>([[3, secondVehicleEdge]]), // Round 2
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
const result = new Result(
|
|
388
|
+
mockQuery,
|
|
389
|
+
{
|
|
390
|
+
earliestArrivals,
|
|
391
|
+
graph,
|
|
392
|
+
destinations: [3],
|
|
393
|
+
},
|
|
394
|
+
mockStopsIndex,
|
|
395
|
+
mockTimetable,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const route = result.bestRoute('stop4');
|
|
399
|
+
assert(route);
|
|
400
|
+
assert.strictEqual(route.legs.length, 2); // two vehicle legs (transfer is implicit in route change)
|
|
401
|
+
const firstLeg = route.legs[0];
|
|
402
|
+
const secondLeg = route.legs[1];
|
|
403
|
+
assert(firstLeg);
|
|
404
|
+
assert(secondLeg);
|
|
405
|
+
assert.strictEqual(firstLeg.from.id, 0);
|
|
406
|
+
assert.strictEqual(firstLeg.to.id, 2);
|
|
407
|
+
assert.strictEqual(secondLeg.from.id, 2);
|
|
408
|
+
assert.strictEqual(secondLeg.to.id, 3);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('continuous trips', () => {
|
|
413
|
+
it('should handle single continuous trip correctly', () => {
|
|
414
|
+
const earliestArrivals = new Map([
|
|
415
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
416
|
+
[1, { arrival: Time.fromHMS(8, 30, 0), legNumber: 1 }], // intermediate stop
|
|
417
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }], // final destination via continuous trip
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
const firstVehicleEdge: VehicleEdge = {
|
|
421
|
+
arrival: Time.fromHMS(8, 30, 0),
|
|
422
|
+
from: 0,
|
|
423
|
+
to: 1,
|
|
424
|
+
routeId: 0,
|
|
425
|
+
tripIndex: 0,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const continuousVehicleEdge: VehicleEdge = {
|
|
429
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
430
|
+
from: 1,
|
|
431
|
+
to: 2,
|
|
432
|
+
routeId: 0,
|
|
433
|
+
tripIndex: 0,
|
|
434
|
+
continuationOf: firstVehicleEdge,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
438
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
439
|
+
new Map<StopId, RoutingEdge>([
|
|
440
|
+
[1, firstVehicleEdge],
|
|
441
|
+
[2, continuousVehicleEdge],
|
|
442
|
+
]), // Round 1
|
|
248
443
|
];
|
|
249
444
|
|
|
250
445
|
const result = new Result(
|
|
251
446
|
mockQuery,
|
|
252
|
-
|
|
253
|
-
|
|
447
|
+
{
|
|
448
|
+
earliestArrivals,
|
|
449
|
+
graph,
|
|
450
|
+
destinations: [2],
|
|
451
|
+
},
|
|
254
452
|
mockStopsIndex,
|
|
453
|
+
mockTimetable,
|
|
255
454
|
);
|
|
256
455
|
|
|
257
456
|
const route = result.bestRoute('stop3');
|
|
258
457
|
assert(route);
|
|
259
458
|
assert.strictEqual(route.legs.length, 1);
|
|
260
|
-
|
|
459
|
+
const leg = route.legs[0];
|
|
460
|
+
assert(leg);
|
|
461
|
+
assert.strictEqual(leg.from.id, 0);
|
|
462
|
+
assert.strictEqual(leg.to.id, 2);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should handle continuous trips with route change mid-journey', () => {
|
|
466
|
+
const earliestArrivals = new Map([
|
|
467
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
468
|
+
[3, { arrival: Time.fromHMS(9, 45, 0), legNumber: 1 }], // destination via continuous trip to route 1
|
|
469
|
+
]);
|
|
470
|
+
|
|
471
|
+
const firstVehicleEdge: VehicleEdge = {
|
|
472
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
473
|
+
from: 0,
|
|
474
|
+
to: 2,
|
|
475
|
+
routeId: 0,
|
|
476
|
+
tripIndex: 0,
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const continuousVehicleEdge: VehicleEdge = {
|
|
480
|
+
arrival: Time.fromHMS(9, 45, 0),
|
|
481
|
+
from: 2,
|
|
482
|
+
to: 3,
|
|
483
|
+
routeId: 1,
|
|
484
|
+
tripIndex: 0,
|
|
485
|
+
continuationOf: firstVehicleEdge,
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
489
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
490
|
+
new Map<StopId, RoutingEdge>([[3, continuousVehicleEdge]]), // Round 1
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
const result = new Result(
|
|
494
|
+
mockQuery,
|
|
495
|
+
{
|
|
496
|
+
earliestArrivals,
|
|
497
|
+
graph,
|
|
498
|
+
destinations: [3],
|
|
499
|
+
},
|
|
500
|
+
mockStopsIndex,
|
|
501
|
+
mockTimetable,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const route = result.bestRoute('stop4');
|
|
505
|
+
assert(route);
|
|
506
|
+
assert.strictEqual(route.legs.length, 1);
|
|
507
|
+
|
|
508
|
+
const leg = route.legs[0];
|
|
509
|
+
assert(leg);
|
|
510
|
+
assert.strictEqual(leg.from.id, 0);
|
|
511
|
+
assert.strictEqual(leg.to.id, 3);
|
|
512
|
+
});
|
|
513
|
+
it('should handle route reconstruction with actual transfer edges', () => {
|
|
514
|
+
const earliestArrivals = new Map([
|
|
515
|
+
[0, { arrival: Time.fromHMS(8, 0, 0), legNumber: 0 }], // origin
|
|
516
|
+
[1, { arrival: Time.fromHMS(8, 30, 0), legNumber: 1 }], // first vehicle leg destination
|
|
517
|
+
[2, { arrival: Time.fromHMS(8, 35, 0), legNumber: 1 }], // after transfer (same round as transfer doesn't advance round)
|
|
518
|
+
[3, { arrival: Time.fromHMS(9, 15, 0), legNumber: 2 }], // final destination
|
|
519
|
+
]);
|
|
520
|
+
|
|
521
|
+
const firstVehicleEdge: VehicleEdge = {
|
|
522
|
+
arrival: Time.fromHMS(8, 30, 0),
|
|
523
|
+
from: 0,
|
|
524
|
+
to: 1,
|
|
525
|
+
routeId: 0,
|
|
526
|
+
tripIndex: 0,
|
|
527
|
+
};
|
|
528
|
+
const transferEdge: TransferEdge = {
|
|
529
|
+
arrival: Time.fromHMS(8, 35, 0),
|
|
530
|
+
from: 1,
|
|
531
|
+
to: 2,
|
|
532
|
+
type: 'RECOMMENDED',
|
|
533
|
+
minTransferTime: Duration.fromMinutes(5),
|
|
534
|
+
};
|
|
535
|
+
const secondVehicleEdge: VehicleEdge = {
|
|
536
|
+
arrival: Time.fromHMS(9, 15, 0),
|
|
537
|
+
from: 2,
|
|
538
|
+
to: 3,
|
|
539
|
+
routeId: 1,
|
|
540
|
+
tripIndex: 0,
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
544
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
545
|
+
new Map<StopId, RoutingEdge>([
|
|
546
|
+
[1, firstVehicleEdge], // First vehicle leg
|
|
547
|
+
[2, transferEdge], // Transfer happens in same round as vehicle leg
|
|
548
|
+
]), // Round 1
|
|
549
|
+
new Map<StopId, RoutingEdge>([[3, secondVehicleEdge]]), // Round 2 - second vehicle leg
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
const result = new Result(
|
|
553
|
+
mockQuery,
|
|
554
|
+
{
|
|
555
|
+
earliestArrivals,
|
|
556
|
+
graph,
|
|
557
|
+
destinations: [3],
|
|
558
|
+
},
|
|
559
|
+
mockStopsIndex,
|
|
560
|
+
mockTimetable,
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
const route = result.bestRoute('stop4');
|
|
564
|
+
assert(route);
|
|
565
|
+
|
|
566
|
+
assert.strictEqual(
|
|
567
|
+
route.legs.length,
|
|
568
|
+
3,
|
|
569
|
+
'Route should have vehicle + transfer + vehicle legs',
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
const firstLeg = route.legs[0];
|
|
573
|
+
const transferLeg = route.legs[1];
|
|
574
|
+
const thirdLeg = route.legs[2];
|
|
575
|
+
|
|
576
|
+
assert(firstLeg);
|
|
577
|
+
assert(transferLeg);
|
|
578
|
+
assert(thirdLeg);
|
|
579
|
+
|
|
580
|
+
assert.strictEqual(firstLeg.from.id, 0);
|
|
581
|
+
assert.strictEqual(firstLeg.to.id, 1);
|
|
582
|
+
assert('departureTime' in firstLeg);
|
|
583
|
+
assert('route' in firstLeg);
|
|
584
|
+
|
|
585
|
+
assert.strictEqual(transferLeg.from.id, 1);
|
|
586
|
+
assert.strictEqual(transferLeg.to.id, 2);
|
|
587
|
+
assert('type' in transferLeg);
|
|
588
|
+
assert('minTransferTime' in transferLeg);
|
|
589
|
+
assert.strictEqual(transferLeg.type, 'RECOMMENDED');
|
|
590
|
+
|
|
591
|
+
assert.strictEqual(thirdLeg.from.id, 2);
|
|
592
|
+
assert.strictEqual(thirdLeg.to.id, 3);
|
|
593
|
+
assert('departureTime' in thirdLeg);
|
|
594
|
+
assert('route' in thirdLeg);
|
|
595
|
+
|
|
596
|
+
assert.strictEqual(earliestArrivals.get(1)?.legNumber, 1);
|
|
597
|
+
assert.strictEqual(earliestArrivals.get(2)?.legNumber, 1);
|
|
598
|
+
assert.strictEqual(earliestArrivals.get(3)?.legNumber, 2);
|
|
261
599
|
});
|
|
262
600
|
});
|
|
263
601
|
|
|
@@ -266,15 +604,19 @@ describe('Result', () => {
|
|
|
266
604
|
const arrivalTime = {
|
|
267
605
|
arrival: Time.fromHMS(9, 0, 0),
|
|
268
606
|
legNumber: 1,
|
|
269
|
-
origin: 0,
|
|
270
607
|
};
|
|
271
608
|
const earliestArrivals = new Map([[2, arrivalTime]]);
|
|
609
|
+
const graph: Map<StopId, RoutingEdge>[] = [];
|
|
272
610
|
|
|
273
611
|
const result = new Result(
|
|
274
612
|
mockQuery,
|
|
275
|
-
|
|
276
|
-
|
|
613
|
+
{
|
|
614
|
+
earliestArrivals,
|
|
615
|
+
graph,
|
|
616
|
+
destinations: [2],
|
|
617
|
+
},
|
|
277
618
|
mockStopsIndex,
|
|
619
|
+
mockTimetable,
|
|
278
620
|
);
|
|
279
621
|
|
|
280
622
|
const arrival = result.arrivalAt('stop3');
|
|
@@ -283,14 +625,19 @@ describe('Result', () => {
|
|
|
283
625
|
|
|
284
626
|
it('should return undefined for unreachable stop', () => {
|
|
285
627
|
const earliestArrivals = new Map([
|
|
286
|
-
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1
|
|
628
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }],
|
|
287
629
|
]);
|
|
630
|
+
const graph: Map<StopId, RoutingEdge>[] = [];
|
|
288
631
|
|
|
289
632
|
const result = new Result(
|
|
290
633
|
mockQuery,
|
|
291
|
-
|
|
292
|
-
|
|
634
|
+
{
|
|
635
|
+
earliestArrivals,
|
|
636
|
+
graph,
|
|
637
|
+
destinations: [2],
|
|
638
|
+
},
|
|
293
639
|
mockStopsIndex,
|
|
640
|
+
mockTimetable,
|
|
294
641
|
);
|
|
295
642
|
|
|
296
643
|
const arrival = result.arrivalAt('stop4');
|
|
@@ -301,24 +648,27 @@ describe('Result', () => {
|
|
|
301
648
|
const earlierArrival = {
|
|
302
649
|
arrival: Time.fromHMS(9, 0, 0),
|
|
303
650
|
legNumber: 1,
|
|
304
|
-
origin: 0,
|
|
305
651
|
};
|
|
306
652
|
const laterArrival = {
|
|
307
653
|
arrival: Time.fromHMS(9, 30, 0),
|
|
308
654
|
legNumber: 1,
|
|
309
|
-
origin: 0,
|
|
310
655
|
};
|
|
311
656
|
|
|
312
657
|
const earliestArrivals = new Map([
|
|
313
|
-
[
|
|
314
|
-
[
|
|
658
|
+
[5, earlierArrival], // child1 - faster
|
|
659
|
+
[6, laterArrival], // child2 - slower
|
|
315
660
|
]);
|
|
661
|
+
const graph: Map<StopId, RoutingEdge>[] = [];
|
|
316
662
|
|
|
317
663
|
const result = new Result(
|
|
318
664
|
mockQuery,
|
|
319
|
-
|
|
320
|
-
|
|
665
|
+
{
|
|
666
|
+
earliestArrivals,
|
|
667
|
+
graph,
|
|
668
|
+
destinations: [4], // parent stop
|
|
669
|
+
},
|
|
321
670
|
mockStopsIndex,
|
|
671
|
+
mockTimetable,
|
|
322
672
|
);
|
|
323
673
|
|
|
324
674
|
const arrival = result.arrivalAt('parent');
|
|
@@ -326,63 +676,74 @@ describe('Result', () => {
|
|
|
326
676
|
});
|
|
327
677
|
|
|
328
678
|
it('should respect maxTransfers constraint', () => {
|
|
329
|
-
const
|
|
330
|
-
arrival: Time.fromHMS(
|
|
331
|
-
legNumber: 0,
|
|
332
|
-
origin: 0,
|
|
333
|
-
};
|
|
334
|
-
const tripLeg2 = {
|
|
335
|
-
arrival: Time.fromHMS(9, 0, 0),
|
|
679
|
+
const directArrival = {
|
|
680
|
+
arrival: Time.fromHMS(9, 30, 0),
|
|
336
681
|
legNumber: 1,
|
|
337
|
-
origin: 0,
|
|
338
682
|
};
|
|
339
|
-
const
|
|
340
|
-
arrival: Time.fromHMS(9,
|
|
683
|
+
const transferArrival = {
|
|
684
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
341
685
|
legNumber: 2,
|
|
342
|
-
origin: 0,
|
|
343
686
|
};
|
|
344
687
|
|
|
345
688
|
const earliestArrivals = new Map([
|
|
346
|
-
[2,
|
|
689
|
+
[2, transferArrival], // Best overall arrival with transfer
|
|
347
690
|
]);
|
|
348
691
|
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
692
|
+
const vehicleEdge1: VehicleEdge = {
|
|
693
|
+
arrival: Time.fromHMS(9, 30, 0),
|
|
694
|
+
from: 0,
|
|
695
|
+
to: 2,
|
|
696
|
+
routeId: 0,
|
|
697
|
+
tripIndex: 0,
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const vehicleEdge2: VehicleEdge = {
|
|
701
|
+
arrival: Time.fromHMS(9, 0, 0),
|
|
702
|
+
from: 0,
|
|
703
|
+
to: 2,
|
|
704
|
+
routeId: 1,
|
|
705
|
+
tripIndex: 0,
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const graph: Map<StopId, RoutingEdge>[] = [
|
|
709
|
+
new Map<StopId, RoutingEdge>([[0, { arrival: Time.fromHMS(8, 0, 0) }]]), // Round 0 - origins
|
|
710
|
+
new Map<StopId, RoutingEdge>([[2, vehicleEdge1]]), // Round 1 - direct route (no transfers)
|
|
711
|
+
new Map<StopId, RoutingEdge>([[2, vehicleEdge2]]), // Round 2 - route with 1 transfer
|
|
353
712
|
];
|
|
354
713
|
|
|
355
714
|
const result = new Result(
|
|
356
715
|
mockQuery,
|
|
357
|
-
|
|
358
|
-
|
|
716
|
+
{
|
|
717
|
+
earliestArrivals,
|
|
718
|
+
graph,
|
|
719
|
+
destinations: [2],
|
|
720
|
+
},
|
|
359
721
|
mockStopsIndex,
|
|
722
|
+
mockTimetable,
|
|
360
723
|
);
|
|
361
724
|
|
|
362
725
|
const arrivalWithLimit = result.arrivalAt('stop3', 0);
|
|
363
|
-
assert.deepStrictEqual(arrivalWithLimit,
|
|
726
|
+
assert.deepStrictEqual(arrivalWithLimit, directArrival);
|
|
364
727
|
|
|
365
|
-
const arrivalWithoutLimit = result.arrivalAt('stop3'
|
|
366
|
-
assert.deepStrictEqual(arrivalWithoutLimit,
|
|
728
|
+
const arrivalWithoutLimit = result.arrivalAt('stop3');
|
|
729
|
+
assert.deepStrictEqual(arrivalWithoutLimit, transferArrival);
|
|
367
730
|
});
|
|
368
731
|
|
|
369
732
|
it('should handle non-existent stops', () => {
|
|
370
733
|
const earliestArrivals = new Map([
|
|
371
|
-
[
|
|
372
|
-
2,
|
|
373
|
-
{
|
|
374
|
-
arrival: Time.fromHMS(9, 0, 0),
|
|
375
|
-
legNumber: 1,
|
|
376
|
-
origin: 0,
|
|
377
|
-
} as ReachingTime,
|
|
378
|
-
],
|
|
734
|
+
[2, { arrival: Time.fromHMS(9, 0, 0), legNumber: 1 }],
|
|
379
735
|
]);
|
|
736
|
+
const graph: Map<StopId, RoutingEdge>[] = [];
|
|
380
737
|
|
|
381
738
|
const result = new Result(
|
|
382
739
|
mockQuery,
|
|
383
|
-
|
|
384
|
-
|
|
740
|
+
{
|
|
741
|
+
earliestArrivals,
|
|
742
|
+
graph,
|
|
743
|
+
destinations: [2],
|
|
744
|
+
},
|
|
385
745
|
mockStopsIndex,
|
|
746
|
+
mockTimetable,
|
|
386
747
|
);
|
|
387
748
|
|
|
388
749
|
const arrival = result.arrivalAt('nonexistent');
|