minotor 11.1.2 → 11.2.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 +7 -1
- package/CHANGELOG.md +3 -3
- package/README.md +111 -86
- package/dist/cli/perf.d.ts +57 -18
- package/dist/cli.mjs +1371 -342
- package/dist/cli.mjs.map +1 -1
- package/dist/parser.cjs.js +57 -4
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +57 -4
- 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 +5 -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__/access.test.d.ts +1 -0
- package/dist/routing/__tests__/plainRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeResult.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeState.test.d.ts +1 -0
- package/dist/routing/__tests__/raptor.test.d.ts +1 -0
- package/dist/routing/__tests__/state.test.d.ts +1 -0
- package/dist/routing/access.d.ts +55 -0
- package/dist/routing/plainRouter.d.ts +21 -0
- package/dist/routing/plotter.d.ts +9 -0
- package/dist/routing/query.d.ts +132 -13
- package/dist/routing/rangeResult.d.ts +155 -0
- package/dist/routing/rangeRouter.d.ts +24 -0
- package/dist/routing/rangeState.d.ts +83 -0
- package/dist/routing/raptor.d.ts +96 -0
- package/dist/routing/result.d.ts +27 -7
- package/dist/routing/route.d.ts +5 -21
- package/dist/routing/router.d.ts +20 -91
- package/dist/routing/state.d.ts +92 -17
- package/dist/timetable/route.d.ts +8 -0
- package/dist/timetable/timetable.d.ts +17 -1
- package/package.json +1 -1
- package/src/__e2e__/benchmark.json +18 -0
- package/src/__e2e__/router.test.ts +461 -127
- package/src/cli/minotor.ts +39 -3
- package/src/cli/perf.ts +324 -60
- package/src/cli/repl.ts +96 -41
- package/src/router.ts +11 -3
- package/src/routing/__tests__/access.test.ts +294 -0
- package/src/routing/__tests__/plainRouter.test.ts +1633 -0
- package/src/routing/__tests__/plotter.test.ts +8 -8
- package/src/routing/__tests__/rangeResult.test.ts +273 -0
- package/src/routing/__tests__/rangeRouter.test.ts +472 -0
- package/src/routing/__tests__/rangeState.test.ts +246 -0
- package/src/routing/__tests__/raptor.test.ts +366 -0
- package/src/routing/__tests__/result.test.ts +27 -27
- package/src/routing/__tests__/route.test.ts +28 -0
- package/src/routing/__tests__/router.test.ts +75 -1587
- package/src/routing/__tests__/state.test.ts +78 -0
- package/src/routing/access.ts +144 -0
- package/src/routing/plainRouter.ts +60 -0
- package/src/routing/plotter.ts +53 -6
- package/src/routing/query.ts +116 -13
- package/src/routing/rangeResult.ts +292 -0
- package/src/routing/rangeRouter.ts +167 -0
- package/src/routing/rangeState.ts +150 -0
- package/src/routing/raptor.ts +416 -0
- package/src/routing/result.ts +68 -26
- package/src/routing/route.ts +15 -53
- package/src/routing/router.ts +40 -480
- package/src/routing/state.ts +191 -32
- package/src/timetable/__tests__/timetable.test.ts +373 -0
- package/src/timetable/route.ts +16 -4
- package/src/timetable/timetable.ts +54 -1
|
@@ -75,7 +75,7 @@ describe('Plotter', () => {
|
|
|
75
75
|
describe('plotDotGraph', () => {
|
|
76
76
|
it('should generate valid DOT graph structure', () => {
|
|
77
77
|
const result = new Result(
|
|
78
|
-
mockQuery,
|
|
78
|
+
mockQuery.to,
|
|
79
79
|
RoutingState.fromTestData({ nbStops: NB_STOPS }),
|
|
80
80
|
mockStopsIndex,
|
|
81
81
|
mockTimetable,
|
|
@@ -92,12 +92,12 @@ describe('Plotter', () => {
|
|
|
92
92
|
|
|
93
93
|
it('should include station nodes', () => {
|
|
94
94
|
const result = new Result(
|
|
95
|
-
mockQuery,
|
|
95
|
+
mockQuery.to,
|
|
96
96
|
RoutingState.fromTestData({
|
|
97
97
|
nbStops: NB_STOPS,
|
|
98
98
|
origins: [0],
|
|
99
99
|
destinations: [0],
|
|
100
|
-
graph: [[[0, { arrival: timeFromHMS(8, 0, 0) }]]],
|
|
100
|
+
graph: [[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]]],
|
|
101
101
|
}),
|
|
102
102
|
mockStopsIndex,
|
|
103
103
|
mockTimetable,
|
|
@@ -113,7 +113,7 @@ describe('Plotter', () => {
|
|
|
113
113
|
|
|
114
114
|
it('should handle empty graph gracefully', () => {
|
|
115
115
|
const result = new Result(
|
|
116
|
-
mockQuery,
|
|
116
|
+
mockQuery.to,
|
|
117
117
|
RoutingState.fromTestData({ nbStops: NB_STOPS }),
|
|
118
118
|
mockStopsIndex,
|
|
119
119
|
mockTimetable,
|
|
@@ -141,12 +141,12 @@ describe('Plotter', () => {
|
|
|
141
141
|
const specialStopsIndex = new StopsIndex([stop1, stop2, specialStop]);
|
|
142
142
|
|
|
143
143
|
const result = new Result(
|
|
144
|
-
mockQuery,
|
|
144
|
+
mockQuery.to,
|
|
145
145
|
RoutingState.fromTestData({
|
|
146
146
|
nbStops: NB_STOPS,
|
|
147
147
|
origins: [2],
|
|
148
148
|
destinations: [2],
|
|
149
|
-
graph: [[[2, { arrival: timeFromHMS(8, 0, 0) }]]],
|
|
149
|
+
graph: [[[2, { stopId: 2, arrival: timeFromHMS(8, 0, 0) }]]],
|
|
150
150
|
}),
|
|
151
151
|
specialStopsIndex,
|
|
152
152
|
mockTimetable,
|
|
@@ -166,13 +166,13 @@ describe('Plotter', () => {
|
|
|
166
166
|
|
|
167
167
|
it('should use correct colors', () => {
|
|
168
168
|
const result = new Result(
|
|
169
|
-
mockQuery,
|
|
169
|
+
mockQuery.to,
|
|
170
170
|
RoutingState.fromTestData({
|
|
171
171
|
nbStops: NB_STOPS,
|
|
172
172
|
origins: [0],
|
|
173
173
|
destinations: [1],
|
|
174
174
|
graph: [
|
|
175
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
175
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
176
176
|
[
|
|
177
177
|
[
|
|
178
178
|
1,
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { Timetable } from '../../router.js';
|
|
5
|
+
import { Stop } from '../../stops/stops.js';
|
|
6
|
+
import { StopsIndex } from '../../stops/stopsIndex.js';
|
|
7
|
+
import { Route } from '../../timetable/route.js';
|
|
8
|
+
import { timeFromHM } from '../../timetable/time.js';
|
|
9
|
+
import { ServiceRoute, StopAdjacency } from '../../timetable/timetable.js';
|
|
10
|
+
import { ParetoRun, RangeResult } from '../rangeResult.js';
|
|
11
|
+
import { Result } from '../result.js';
|
|
12
|
+
import { RoutingState, VehicleEdge } from '../router.js';
|
|
13
|
+
|
|
14
|
+
// Two-stop timetable with two trips on a single route:
|
|
15
|
+
// trip 0: stop 0 departs 09:00, stop 1 arrives 09:30
|
|
16
|
+
// trip 1: stop 0 departs 08:30, stop 1 arrives 09:10
|
|
17
|
+
const stops: Stop[] = [
|
|
18
|
+
{
|
|
19
|
+
id: 0,
|
|
20
|
+
sourceStopId: 'A',
|
|
21
|
+
name: 'Stop A',
|
|
22
|
+
lat: 0,
|
|
23
|
+
lon: 0,
|
|
24
|
+
children: [],
|
|
25
|
+
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 1,
|
|
29
|
+
sourceStopId: 'B',
|
|
30
|
+
name: 'Stop B',
|
|
31
|
+
lat: 0,
|
|
32
|
+
lon: 0,
|
|
33
|
+
children: [],
|
|
34
|
+
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const stopsIndex = new StopsIndex(stops);
|
|
38
|
+
|
|
39
|
+
const stopsAdjacency: StopAdjacency[] = [{ routes: [0] }, { routes: [0] }];
|
|
40
|
+
const routesAdjacency = [
|
|
41
|
+
Route.of({
|
|
42
|
+
id: 0,
|
|
43
|
+
serviceRouteId: 0,
|
|
44
|
+
trips: [
|
|
45
|
+
{
|
|
46
|
+
stops: [
|
|
47
|
+
{
|
|
48
|
+
id: 0,
|
|
49
|
+
arrivalTime: timeFromHM(9, 0),
|
|
50
|
+
departureTime: timeFromHM(9, 0),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 1,
|
|
54
|
+
arrivalTime: timeFromHM(9, 30),
|
|
55
|
+
departureTime: timeFromHM(9, 30),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
stops: [
|
|
61
|
+
{
|
|
62
|
+
id: 0,
|
|
63
|
+
arrivalTime: timeFromHM(8, 30),
|
|
64
|
+
departureTime: timeFromHM(8, 30),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 1,
|
|
68
|
+
arrivalTime: timeFromHM(9, 10),
|
|
69
|
+
departureTime: timeFromHM(9, 10),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
];
|
|
76
|
+
const serviceRoutes: ServiceRoute[] = [
|
|
77
|
+
{ type: 'BUS', name: 'Line 1', routes: [0] },
|
|
78
|
+
];
|
|
79
|
+
const timetable = new Timetable(stopsAdjacency, routesAdjacency, serviceRoutes);
|
|
80
|
+
|
|
81
|
+
const DEST = 1;
|
|
82
|
+
const DESTINATIONS = new Set([DEST]);
|
|
83
|
+
|
|
84
|
+
// Run A — later departure (09:00→09:30, 30-minute duration)
|
|
85
|
+
const edgeA: VehicleEdge = {
|
|
86
|
+
arrival: timeFromHM(9, 30),
|
|
87
|
+
stopIndex: 0,
|
|
88
|
+
hopOffStopIndex: 1,
|
|
89
|
+
routeId: 0,
|
|
90
|
+
tripIndex: 0,
|
|
91
|
+
};
|
|
92
|
+
const runA: ParetoRun = {
|
|
93
|
+
departureTime: timeFromHM(9, 0),
|
|
94
|
+
result: new Result(
|
|
95
|
+
DESTINATIONS,
|
|
96
|
+
RoutingState.fromTestData({
|
|
97
|
+
nbStops: 2,
|
|
98
|
+
origins: [0],
|
|
99
|
+
destinations: [DEST],
|
|
100
|
+
arrivals: [
|
|
101
|
+
[0, timeFromHM(9, 0), 0],
|
|
102
|
+
[DEST, timeFromHM(9, 30), 1],
|
|
103
|
+
],
|
|
104
|
+
graph: [[[0, { stopId: 0, arrival: timeFromHM(9, 0) }]], [[DEST, edgeA]]],
|
|
105
|
+
}),
|
|
106
|
+
stopsIndex,
|
|
107
|
+
timetable,
|
|
108
|
+
),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Run B — earlier departure (08:30→09:10, 40-minute duration)
|
|
112
|
+
const edgeB: VehicleEdge = {
|
|
113
|
+
arrival: timeFromHM(9, 10),
|
|
114
|
+
stopIndex: 0,
|
|
115
|
+
hopOffStopIndex: 1,
|
|
116
|
+
routeId: 0,
|
|
117
|
+
tripIndex: 1,
|
|
118
|
+
};
|
|
119
|
+
const runB: ParetoRun = {
|
|
120
|
+
departureTime: timeFromHM(8, 30),
|
|
121
|
+
result: new Result(
|
|
122
|
+
DESTINATIONS,
|
|
123
|
+
RoutingState.fromTestData({
|
|
124
|
+
nbStops: 2,
|
|
125
|
+
origins: [0],
|
|
126
|
+
destinations: [DEST],
|
|
127
|
+
arrivals: [
|
|
128
|
+
[0, timeFromHM(8, 30), 0],
|
|
129
|
+
[DEST, timeFromHM(9, 10), 1],
|
|
130
|
+
],
|
|
131
|
+
graph: [
|
|
132
|
+
[[0, { stopId: 0, arrival: timeFromHM(8, 30) }]],
|
|
133
|
+
[[DEST, edgeB]],
|
|
134
|
+
],
|
|
135
|
+
}),
|
|
136
|
+
stopsIndex,
|
|
137
|
+
timetable,
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Runs are stored latest-departure-first: [runA, runB]
|
|
142
|
+
const rangeResult = new RangeResult([runA, runB], DESTINATIONS);
|
|
143
|
+
|
|
144
|
+
describe('RangeResult', () => {
|
|
145
|
+
describe('size and destinations', () => {
|
|
146
|
+
it('size reflects the number of Pareto runs', () => {
|
|
147
|
+
assert.strictEqual(rangeResult.size, 2);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('destinations returns the resolved destination stop set', () => {
|
|
151
|
+
assert.deepStrictEqual(rangeResult.destinations, DESTINATIONS);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('[Symbol.iterator]', () => {
|
|
156
|
+
it('iterates runs in latest-departure-first order', () => {
|
|
157
|
+
const runs = [...rangeResult];
|
|
158
|
+
assert.strictEqual(runs.length, 2);
|
|
159
|
+
assert.strictEqual(runs[0]?.departureTime, timeFromHM(9, 0));
|
|
160
|
+
assert.strictEqual(runs[1]?.departureTime, timeFromHM(8, 30));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('bestRoute', () => {
|
|
165
|
+
it('returns the route that arrives earliest at the destination', () => {
|
|
166
|
+
const route = rangeResult.bestRoute();
|
|
167
|
+
assert(route);
|
|
168
|
+
assert.strictEqual(route.arrivalTime(), timeFromHM(9, 10));
|
|
169
|
+
assert.strictEqual(route.departureTime(), timeFromHM(8, 30));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('accepts a specific stop ID', () => {
|
|
173
|
+
const route = rangeResult.bestRoute(DEST);
|
|
174
|
+
assert(route);
|
|
175
|
+
assert.strictEqual(route.arrivalTime(), timeFromHM(9, 10));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('returns undefined for an unreachable stop', () => {
|
|
179
|
+
assert.strictEqual(rangeResult.bestRoute(99), undefined);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('latestDepartureRoute', () => {
|
|
184
|
+
it('returns the route with the latest possible departure', () => {
|
|
185
|
+
const route = rangeResult.latestDepartureRoute();
|
|
186
|
+
assert(route);
|
|
187
|
+
assert.strictEqual(route.departureTime(), timeFromHM(9, 0));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('returns undefined for an unreachable stop', () => {
|
|
191
|
+
assert.strictEqual(rangeResult.latestDepartureRoute(99), undefined);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('fastestRoute', () => {
|
|
196
|
+
it('returns the route with the shortest travel duration', () => {
|
|
197
|
+
// runA: 30 min, runB: 40 min
|
|
198
|
+
const route = rangeResult.fastestRoute();
|
|
199
|
+
assert(route);
|
|
200
|
+
assert.strictEqual(route.departureTime(), timeFromHM(9, 0));
|
|
201
|
+
assert.strictEqual(route.totalDuration(), 30);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('returns undefined for an unreachable stop', () => {
|
|
205
|
+
assert.strictEqual(rangeResult.fastestRoute(99), undefined);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('getRoutes', () => {
|
|
210
|
+
it('returns all routes ordered earliest-departure-first', () => {
|
|
211
|
+
const routes = rangeResult.getRoutes();
|
|
212
|
+
assert.strictEqual(routes.length, 2);
|
|
213
|
+
assert.strictEqual(routes[0]?.departureTime(), timeFromHM(8, 30));
|
|
214
|
+
assert.strictEqual(routes[1]?.departureTime(), timeFromHM(9, 0));
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('earliestArrivalAt', () => {
|
|
219
|
+
it('returns the earliest arrival across all Pareto runs', () => {
|
|
220
|
+
const arrival = rangeResult.earliestArrivalAt(DEST);
|
|
221
|
+
assert(arrival);
|
|
222
|
+
assert.strictEqual(arrival.arrival, timeFromHM(9, 10));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns undefined for a stop that was never reached', () => {
|
|
226
|
+
assert.strictEqual(rangeResult.earliestArrivalAt(99), undefined);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('shortestDurationTo', () => {
|
|
231
|
+
it('returns the run with the minimum travel duration', () => {
|
|
232
|
+
const result = rangeResult.shortestDurationTo(DEST);
|
|
233
|
+
assert(result);
|
|
234
|
+
assert.strictEqual(result.duration, 30);
|
|
235
|
+
assert.strictEqual(result.arrival, timeFromHM(9, 30));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('returns undefined for a stop that was never reached', () => {
|
|
239
|
+
assert.strictEqual(rangeResult.shortestDurationTo(99), undefined);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('allShortestDurations', () => {
|
|
244
|
+
it('maps every reached stop to the shortest duration across all runs', () => {
|
|
245
|
+
const durations = rangeResult.allShortestDurations();
|
|
246
|
+
assert.strictEqual(durations.get(0)?.duration, 0);
|
|
247
|
+
assert.strictEqual(durations.get(DEST)?.duration, 30);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('allEarliestArrivals', () => {
|
|
252
|
+
it('maps every reached stop to the earliest arrival across all runs', () => {
|
|
253
|
+
const arrivals = rangeResult.allEarliestArrivals();
|
|
254
|
+
assert.strictEqual(arrivals.get(0)?.arrival, timeFromHM(8, 30));
|
|
255
|
+
assert.strictEqual(arrivals.get(DEST)?.arrival, timeFromHM(9, 10));
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('empty result', () => {
|
|
260
|
+
it('handles an empty Pareto set gracefully', () => {
|
|
261
|
+
const empty = new RangeResult([], DESTINATIONS);
|
|
262
|
+
assert.strictEqual(empty.size, 0);
|
|
263
|
+
assert.strictEqual(empty.bestRoute(), undefined);
|
|
264
|
+
assert.strictEqual(empty.latestDepartureRoute(), undefined);
|
|
265
|
+
assert.strictEqual(empty.fastestRoute(), undefined);
|
|
266
|
+
assert.deepStrictEqual(empty.getRoutes(), []);
|
|
267
|
+
assert.strictEqual(empty.earliestArrivalAt(DEST), undefined);
|
|
268
|
+
assert.strictEqual(empty.shortestDurationTo(DEST), undefined);
|
|
269
|
+
assert.deepStrictEqual(empty.allShortestDurations(), new Map());
|
|
270
|
+
assert.deepStrictEqual(empty.allEarliestArrivals(), new Map());
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|