minotor 7.0.2 → 9.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 +1786 -791
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +29 -5
- package/dist/gtfs/trips.d.ts +10 -5
- package/dist/parser.cjs.js +972 -525
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +972 -525
- 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__/tripBoardingId.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +4 -2
- package/dist/timetable/proto/timetable.d.ts +15 -1
- package/dist/timetable/route.d.ts +48 -23
- package/dist/timetable/timetable.d.ts +24 -7
- package/dist/timetable/tripBoardingId.d.ts +34 -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 +245 -1
- package/src/gtfs/__tests__/parser.test.ts +19 -4
- package/src/gtfs/__tests__/transfers.test.ts +773 -37
- package/src/gtfs/__tests__/trips.test.ts +308 -27
- package/src/gtfs/parser.ts +36 -6
- package/src/gtfs/transfers.ts +193 -19
- package/src/gtfs/trips.ts +58 -21
- 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 +380 -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 +344 -211
- package/src/timetable/__tests__/io.test.ts +34 -1
- package/src/timetable/__tests__/route.test.ts +74 -81
- package/src/timetable/__tests__/timetable.test.ts +232 -61
- package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
- package/src/timetable/io.ts +72 -10
- package/src/timetable/proto/timetable.proto +16 -2
- package/src/timetable/proto/timetable.ts +256 -22
- package/src/timetable/route.ts +174 -58
- package/src/timetable/timetable.ts +66 -16
- package/src/timetable/tripBoardingId.ts +94 -0
- package/tsconfig.json +2 -2
|
@@ -58,7 +58,13 @@ describe('Route', () => {
|
|
|
58
58
|
const stops = new Uint32Array([1001, 1002]);
|
|
59
59
|
const serviceRouteId = 0;
|
|
60
60
|
|
|
61
|
-
const route = new Route(
|
|
61
|
+
const route = new Route(
|
|
62
|
+
0,
|
|
63
|
+
stopTimes,
|
|
64
|
+
pickUpDropOffTypes,
|
|
65
|
+
stops,
|
|
66
|
+
serviceRouteId,
|
|
67
|
+
);
|
|
62
68
|
|
|
63
69
|
describe('constructor', () => {
|
|
64
70
|
it('should create a route with correct properties', () => {
|
|
@@ -68,6 +74,7 @@ describe('Route', () => {
|
|
|
68
74
|
|
|
69
75
|
it('should handle empty route', () => {
|
|
70
76
|
const emptyRoute = new Route(
|
|
77
|
+
0,
|
|
71
78
|
new Uint16Array([]),
|
|
72
79
|
new Uint8Array([]),
|
|
73
80
|
new Uint32Array([]),
|
|
@@ -88,34 +95,6 @@ describe('Route', () => {
|
|
|
88
95
|
});
|
|
89
96
|
});
|
|
90
97
|
|
|
91
|
-
describe('isBefore', () => {
|
|
92
|
-
it('should return true when stopA is before stopB', () => {
|
|
93
|
-
assert.strictEqual(route.isBefore(1001, 1002), true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should return false when stopA is after stopB', () => {
|
|
97
|
-
assert.strictEqual(route.isBefore(1002, 1001), false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should return false when stopA equals stopB', () => {
|
|
101
|
-
assert.strictEqual(route.isBefore(1001, 1001), false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should throw error when stopA is not found', () => {
|
|
105
|
-
assert.throws(
|
|
106
|
-
() => route.isBefore(9999, 1002),
|
|
107
|
-
/Stop index undefined not found in route 0/,
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should throw error when stopB is not found', () => {
|
|
112
|
-
assert.throws(
|
|
113
|
-
() => route.isBefore(1001, 9999),
|
|
114
|
-
/Stop index undefined not found in route 0/,
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
98
|
describe('getNbStops', () => {
|
|
120
99
|
it('should return correct number of stops', () => {
|
|
121
100
|
assert.strictEqual(route.getNbStops(), 2);
|
|
@@ -129,178 +108,192 @@ describe('Route', () => {
|
|
|
129
108
|
});
|
|
130
109
|
|
|
131
110
|
describe('arrivalAt', () => {
|
|
132
|
-
it('should return correct arrival time for trip 0 at stop
|
|
133
|
-
const arrival = route.arrivalAt(
|
|
111
|
+
it('should return correct arrival time for trip 0 at stop index 0', () => {
|
|
112
|
+
const arrival = route.arrivalAt(0, 0);
|
|
134
113
|
assert.strictEqual(
|
|
135
114
|
arrival.toMinutes(),
|
|
136
115
|
Time.fromHMS(8, 0, 0).toMinutes(),
|
|
137
116
|
);
|
|
138
117
|
});
|
|
139
118
|
|
|
140
|
-
it('should return correct arrival time for trip 1 at stop
|
|
141
|
-
const arrival = route.arrivalAt(
|
|
119
|
+
it('should return correct arrival time for trip 1 at stop index 1', () => {
|
|
120
|
+
const arrival = route.arrivalAt(1, 1);
|
|
142
121
|
assert.strictEqual(
|
|
143
122
|
arrival.toMinutes(),
|
|
144
123
|
Time.fromHMS(9, 30, 0).toMinutes(),
|
|
145
124
|
);
|
|
146
125
|
});
|
|
147
126
|
|
|
148
|
-
it('should throw error for invalid stop
|
|
127
|
+
it('should throw error for invalid stop index', () => {
|
|
149
128
|
assert.throws(
|
|
150
|
-
() => route.arrivalAt(
|
|
151
|
-
/
|
|
129
|
+
() => route.arrivalAt(999, 0),
|
|
130
|
+
/StopId for stop at index 999 not found/,
|
|
152
131
|
);
|
|
153
132
|
});
|
|
154
133
|
|
|
155
134
|
it('should throw error for invalid trip index', () => {
|
|
156
|
-
assert.throws(
|
|
157
|
-
() => route.arrivalAt(1001, 999),
|
|
158
|
-
/Arrival time not found for stop 1001 at trip index 999/,
|
|
159
|
-
);
|
|
135
|
+
assert.throws(() => route.arrivalAt(0, 999), /Arrival time not found/);
|
|
160
136
|
});
|
|
161
137
|
});
|
|
162
138
|
|
|
163
139
|
describe('departureFrom', () => {
|
|
164
|
-
it('should return correct departure time for trip 0 at stop
|
|
165
|
-
const departure = route.departureFrom(
|
|
140
|
+
it('should return correct departure time for trip 0 at stop index 0', () => {
|
|
141
|
+
const departure = route.departureFrom(0, 0);
|
|
166
142
|
assert.strictEqual(
|
|
167
143
|
departure.toMinutes(),
|
|
168
144
|
Time.fromHMS(8, 1, 0).toMinutes(),
|
|
169
145
|
);
|
|
170
146
|
});
|
|
171
147
|
|
|
172
|
-
it('should return correct departure time for trip 2 at stop
|
|
173
|
-
const departure = route.departureFrom(
|
|
148
|
+
it('should return correct departure time for trip 2 at stop index 1', () => {
|
|
149
|
+
const departure = route.departureFrom(1, 2);
|
|
174
150
|
assert.strictEqual(
|
|
175
151
|
departure.toMinutes(),
|
|
176
152
|
Time.fromHMS(10, 31, 0).toMinutes(),
|
|
177
153
|
);
|
|
178
154
|
});
|
|
179
155
|
|
|
180
|
-
it('should throw error for invalid stop
|
|
156
|
+
it('should throw error for invalid stop index', () => {
|
|
181
157
|
assert.throws(
|
|
182
|
-
() => route.departureFrom(
|
|
183
|
-
/
|
|
158
|
+
() => route.departureFrom(999, 0),
|
|
159
|
+
/StopId for stop at index 999 not found/,
|
|
184
160
|
);
|
|
185
161
|
});
|
|
186
162
|
|
|
187
163
|
it('should throw error for invalid trip index', () => {
|
|
188
164
|
assert.throws(
|
|
189
|
-
() => route.departureFrom(
|
|
190
|
-
/Departure time not found
|
|
165
|
+
() => route.departureFrom(0, 999),
|
|
166
|
+
/Departure time not found/,
|
|
191
167
|
);
|
|
192
168
|
});
|
|
193
169
|
});
|
|
194
170
|
|
|
195
171
|
describe('pickUpTypeFrom', () => {
|
|
196
|
-
it('should return REGULAR pickup type for trip 0 at stop
|
|
197
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
172
|
+
it('should return REGULAR pickup type for trip 0 at stop index 0', () => {
|
|
173
|
+
const pickUpType = route.pickUpTypeFrom(0, 0);
|
|
198
174
|
assert.strictEqual(pickUpType, 'REGULAR');
|
|
199
175
|
});
|
|
200
176
|
|
|
201
|
-
it('should return NOT_AVAILABLE pickup type for trip 0 at stop
|
|
202
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
177
|
+
it('should return NOT_AVAILABLE pickup type for trip 0 at stop index 1', () => {
|
|
178
|
+
const pickUpType = route.pickUpTypeFrom(1, 0);
|
|
203
179
|
assert.strictEqual(pickUpType, 'NOT_AVAILABLE');
|
|
204
180
|
});
|
|
205
181
|
|
|
206
|
-
it('should return MUST_PHONE_AGENCY pickup type for trip 2 at stop
|
|
207
|
-
const pickUpType = route.pickUpTypeFrom(
|
|
182
|
+
it('should return MUST_PHONE_AGENCY pickup type for trip 2 at stop index 0', () => {
|
|
183
|
+
const pickUpType = route.pickUpTypeFrom(0, 2);
|
|
208
184
|
assert.strictEqual(pickUpType, 'MUST_PHONE_AGENCY');
|
|
209
185
|
});
|
|
210
186
|
|
|
211
|
-
it('should throw error for invalid stop
|
|
187
|
+
it('should throw error for invalid stop index', () => {
|
|
212
188
|
assert.throws(
|
|
213
|
-
() => route.pickUpTypeFrom(
|
|
214
|
-
/
|
|
189
|
+
() => route.pickUpTypeFrom(999, 0),
|
|
190
|
+
/StopId for stop at index 999 not found/,
|
|
215
191
|
);
|
|
216
192
|
});
|
|
217
193
|
|
|
218
194
|
it('should throw error for invalid trip index', () => {
|
|
219
195
|
assert.throws(
|
|
220
|
-
() => route.pickUpTypeFrom(
|
|
221
|
-
/Pick up type not found
|
|
196
|
+
() => route.pickUpTypeFrom(0, 999),
|
|
197
|
+
/Pick up type not found/,
|
|
222
198
|
);
|
|
223
199
|
});
|
|
224
200
|
});
|
|
225
201
|
|
|
226
202
|
describe('dropOffTypeAt', () => {
|
|
227
|
-
it('should return REGULAR drop off type for trip 0 at stop
|
|
228
|
-
const dropOffType = route.dropOffTypeAt(
|
|
203
|
+
it('should return REGULAR drop off type for trip 0 at stop index 0', () => {
|
|
204
|
+
const dropOffType = route.dropOffTypeAt(0, 0);
|
|
229
205
|
assert.strictEqual(dropOffType, 'REGULAR');
|
|
230
206
|
});
|
|
231
207
|
|
|
232
|
-
it('should return REGULAR drop off type for trip 1 at stop
|
|
233
|
-
const dropOffType = route.dropOffTypeAt(
|
|
208
|
+
it('should return REGULAR drop off type for trip 1 at stop index 1', () => {
|
|
209
|
+
const dropOffType = route.dropOffTypeAt(1, 1);
|
|
234
210
|
assert.strictEqual(dropOffType, 'REGULAR');
|
|
235
211
|
});
|
|
236
212
|
|
|
237
|
-
it('should throw error for invalid stop
|
|
213
|
+
it('should throw error for invalid stop index', () => {
|
|
238
214
|
assert.throws(
|
|
239
|
-
() => route.dropOffTypeAt(
|
|
240
|
-
/
|
|
215
|
+
() => route.dropOffTypeAt(999, 0),
|
|
216
|
+
/StopId for stop at index 999 not found/,
|
|
241
217
|
);
|
|
242
218
|
});
|
|
243
219
|
|
|
244
220
|
it('should throw error for invalid trip index', () => {
|
|
245
221
|
assert.throws(
|
|
246
|
-
() => route.dropOffTypeAt(
|
|
247
|
-
/Drop off type not found
|
|
222
|
+
() => route.dropOffTypeAt(0, 999),
|
|
223
|
+
/Drop off type not found/,
|
|
248
224
|
);
|
|
249
225
|
});
|
|
250
226
|
});
|
|
251
227
|
|
|
252
228
|
describe('findEarliestTrip', () => {
|
|
253
229
|
it('should find earliest trip without time constraint', () => {
|
|
254
|
-
const tripIndex = route.findEarliestTrip(
|
|
230
|
+
const tripIndex = route.findEarliestTrip(0);
|
|
255
231
|
assert.strictEqual(tripIndex, 0);
|
|
256
232
|
});
|
|
257
233
|
|
|
258
234
|
it('should find earliest trip after specified time', () => {
|
|
259
235
|
const afterTime = Time.fromHMS(8, 30, 0);
|
|
260
|
-
const tripIndex = route.findEarliestTrip(
|
|
236
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
261
237
|
assert.strictEqual(tripIndex, 1);
|
|
262
238
|
});
|
|
263
239
|
|
|
264
240
|
it('should find earliest trip with exact match time', () => {
|
|
265
241
|
const afterTime = Time.fromHMS(9, 1, 0);
|
|
266
|
-
const tripIndex = route.findEarliestTrip(
|
|
242
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
267
243
|
assert.strictEqual(tripIndex, 1);
|
|
268
244
|
});
|
|
269
245
|
|
|
270
246
|
it('should return undefined when no trip is available after specified time', () => {
|
|
271
247
|
const afterTime = Time.fromHMS(23, 0, 0);
|
|
272
|
-
const tripIndex = route.findEarliestTrip(
|
|
248
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
273
249
|
assert.strictEqual(tripIndex, undefined);
|
|
274
250
|
});
|
|
275
251
|
|
|
276
252
|
it('should skip trips where pickup is not available', () => {
|
|
277
|
-
const tripIndex = route.findEarliestTrip(
|
|
278
|
-
// Trip 0 has NOT_AVAILABLE pickup at stop
|
|
253
|
+
const tripIndex = route.findEarliestTrip(1);
|
|
254
|
+
// Trip 0 has NOT_AVAILABLE pickup at stop index 1, so should return trip 1
|
|
279
255
|
assert.strictEqual(tripIndex, 1);
|
|
280
256
|
});
|
|
281
257
|
|
|
282
258
|
it('should respect beforeTrip constraint', () => {
|
|
283
|
-
const tripIndex = route.findEarliestTrip(
|
|
259
|
+
const tripIndex = route.findEarliestTrip(0, Time.fromHMS(8, 2, 0), 1);
|
|
284
260
|
assert.strictEqual(tripIndex, undefined);
|
|
285
261
|
});
|
|
286
262
|
|
|
287
263
|
it('should return undefined when beforeTrip is 0', () => {
|
|
288
|
-
const tripIndex = route.findEarliestTrip(
|
|
264
|
+
const tripIndex = route.findEarliestTrip(0, Time.origin(), 0);
|
|
289
265
|
assert.strictEqual(tripIndex, undefined);
|
|
290
266
|
});
|
|
291
267
|
|
|
292
268
|
it('should handle MUST_PHONE_AGENCY pickup type', () => {
|
|
293
269
|
const afterTime = Time.fromHMS(9, 30, 0);
|
|
294
|
-
const tripIndex = route.findEarliestTrip(
|
|
270
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
295
271
|
// Should find trip 2 even though it requires phone agency
|
|
296
272
|
assert.strictEqual(tripIndex, 2);
|
|
297
273
|
});
|
|
298
274
|
|
|
299
|
-
it('should throw error for invalid stop
|
|
275
|
+
it('should throw error for invalid stop index', () => {
|
|
300
276
|
assert.throws(
|
|
301
|
-
() => route.findEarliestTrip(
|
|
302
|
-
/
|
|
277
|
+
() => route.findEarliestTrip(999),
|
|
278
|
+
/StopId for stop at index 999 not found/,
|
|
303
279
|
);
|
|
304
280
|
});
|
|
305
281
|
});
|
|
282
|
+
|
|
283
|
+
describe('stopRouteIndices', () => {
|
|
284
|
+
it('should return correct stop route indices for existing stop', () => {
|
|
285
|
+
const indices = route.stopRouteIndices(1001);
|
|
286
|
+
assert.deepStrictEqual(indices, [0]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return correct stop route indices for second stop', () => {
|
|
290
|
+
const indices = route.stopRouteIndices(1002);
|
|
291
|
+
assert.deepStrictEqual(indices, [1]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should return empty array for non-existent stop', () => {
|
|
295
|
+
const indices = route.stopRouteIndices(9999);
|
|
296
|
+
assert.deepStrictEqual(indices, []);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
306
299
|
});
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
3
|
|
|
4
|
-
import { encodePickUpDropOffTypes } from '../../gtfs/trips.js';
|
|
5
4
|
import { Duration } from '../duration.js';
|
|
6
|
-
import { NOT_AVAILABLE,
|
|
5
|
+
import { NOT_AVAILABLE, Route } from '../route.js';
|
|
7
6
|
import { Time } from '../time.js';
|
|
8
7
|
import {
|
|
9
8
|
RouteType,
|
|
10
9
|
ServiceRoute,
|
|
11
10
|
StopAdjacency,
|
|
12
11
|
Timetable,
|
|
12
|
+
TripBoarding,
|
|
13
13
|
} from '../timetable.js';
|
|
14
|
+
import { encode } from '../tripBoardingId.js';
|
|
14
15
|
|
|
15
16
|
describe('Timetable', () => {
|
|
16
17
|
const stopsAdjacency: StopAdjacency[] = [
|
|
17
|
-
{
|
|
18
|
+
{ routes: [] },
|
|
18
19
|
{
|
|
19
20
|
transfers: [{ destination: 2, type: 'RECOMMENDED' }],
|
|
20
21
|
routes: [0, 1],
|
|
@@ -30,40 +31,65 @@ describe('Timetable', () => {
|
|
|
30
31
|
routes: [1, 0],
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
|
-
transfers: [],
|
|
34
34
|
routes: [],
|
|
35
35
|
},
|
|
36
36
|
];
|
|
37
37
|
|
|
38
|
-
const route1 =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
const route1 = Route.of({
|
|
39
|
+
id: 0,
|
|
40
|
+
serviceRouteId: 0,
|
|
41
|
+
trips: [
|
|
42
|
+
{
|
|
43
|
+
stops: [
|
|
44
|
+
{
|
|
45
|
+
id: 1,
|
|
46
|
+
arrivalTime: Time.fromHMS(16, 40, 0),
|
|
47
|
+
departureTime: Time.fromHMS(16, 50, 0),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 2,
|
|
51
|
+
arrivalTime: Time.fromHMS(17, 20, 0),
|
|
52
|
+
departureTime: Time.fromHMS(17, 30, 0),
|
|
53
|
+
pickUpType: NOT_AVAILABLE,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
stops: [
|
|
59
|
+
{
|
|
60
|
+
id: 1,
|
|
61
|
+
arrivalTime: Time.fromHMS(18, 0, 0),
|
|
62
|
+
departureTime: Time.fromHMS(18, 10, 0),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 2,
|
|
66
|
+
arrivalTime: Time.fromHMS(19, 0, 0),
|
|
67
|
+
departureTime: Time.fromHMS(19, 10, 0),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
const route2 = Route.of({
|
|
74
|
+
id: 1,
|
|
75
|
+
serviceRouteId: 1,
|
|
76
|
+
trips: [
|
|
77
|
+
{
|
|
78
|
+
stops: [
|
|
79
|
+
{
|
|
80
|
+
id: 2,
|
|
81
|
+
arrivalTime: Time.fromHMS(18, 20, 0),
|
|
82
|
+
departureTime: Time.fromHMS(18, 30, 0),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 1,
|
|
86
|
+
arrivalTime: Time.fromHMS(19, 0, 0),
|
|
87
|
+
departureTime: Time.fromHMS(19, 10, 0),
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
67
93
|
const routesAdjacency = [route1, route2];
|
|
68
94
|
const routes: ServiceRoute[] = [
|
|
69
95
|
{ type: 'RAIL', name: 'Route 1', routes: [0] },
|
|
@@ -74,6 +100,7 @@ describe('Timetable', () => {
|
|
|
74
100
|
stopsAdjacency,
|
|
75
101
|
routesAdjacency,
|
|
76
102
|
routes,
|
|
103
|
+
new Map(),
|
|
77
104
|
);
|
|
78
105
|
|
|
79
106
|
it('should serialize a timetable to a Uint8Array', () => {
|
|
@@ -90,7 +117,7 @@ describe('Timetable', () => {
|
|
|
90
117
|
it('should find the earliest trip for stop1 on route1', () => {
|
|
91
118
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
92
119
|
const route = sampleTimetable.getRoute(0)!;
|
|
93
|
-
const tripIndex = route.findEarliestTrip(
|
|
120
|
+
const tripIndex = route.findEarliestTrip(0);
|
|
94
121
|
assert.strictEqual(tripIndex, 0);
|
|
95
122
|
});
|
|
96
123
|
|
|
@@ -98,7 +125,7 @@ describe('Timetable', () => {
|
|
|
98
125
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
99
126
|
const route = sampleTimetable.getRoute(0)!;
|
|
100
127
|
const afterTime = Time.fromHMS(17, 0, 0);
|
|
101
|
-
const tripIndex = route.findEarliestTrip(
|
|
128
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
102
129
|
assert.strictEqual(tripIndex, 1);
|
|
103
130
|
});
|
|
104
131
|
|
|
@@ -106,40 +133,184 @@ describe('Timetable', () => {
|
|
|
106
133
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
107
134
|
const route = sampleTimetable.getRoute(0)!;
|
|
108
135
|
const afterTime = Time.fromHMS(23, 40, 0);
|
|
109
|
-
const tripIndex = route.findEarliestTrip(
|
|
136
|
+
const tripIndex = route.findEarliestTrip(0, afterTime);
|
|
110
137
|
assert.strictEqual(tripIndex, undefined);
|
|
111
138
|
});
|
|
112
139
|
it('should return undefined if the stop on a trip has pick up not available', () => {
|
|
113
140
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
114
141
|
const route = sampleTimetable.getRoute(0)!;
|
|
115
|
-
const tripIndex = route.findEarliestTrip(
|
|
142
|
+
const tripIndex = route.findEarliestTrip(1);
|
|
116
143
|
assert.strictEqual(tripIndex, 1);
|
|
117
144
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
[
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
describe('findReachableRoutes', () => {
|
|
146
|
+
it('should find reachable routes from a single stop', () => {
|
|
147
|
+
const fromStops = new Set([1]);
|
|
148
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
149
|
+
assert.strictEqual(reachableRoutes.size, 2);
|
|
150
|
+
assert.deepStrictEqual(
|
|
151
|
+
reachableRoutes,
|
|
152
|
+
new Map([
|
|
153
|
+
[route1, 0],
|
|
154
|
+
[route2, 1],
|
|
155
|
+
]),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
it('should find reachable routes from multiple stops', () => {
|
|
160
|
+
const fromStops = new Set([1, 2]);
|
|
161
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
162
|
+
assert.strictEqual(reachableRoutes.size, 2);
|
|
163
|
+
|
|
164
|
+
assert.deepStrictEqual(
|
|
165
|
+
reachableRoutes,
|
|
166
|
+
new Map([
|
|
167
|
+
[route1, 0],
|
|
168
|
+
[route2, 0],
|
|
169
|
+
]),
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should find no reachable routes from stops with no routes', () => {
|
|
174
|
+
const fromStops = new Set([3]); // Stop 3 has no routes in sample timetable
|
|
175
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
176
|
+
assert.strictEqual(reachableRoutes.size, 0);
|
|
177
|
+
assert.deepStrictEqual(reachableRoutes, new Map());
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should find no reachable routes from empty stop set', () => {
|
|
181
|
+
const fromStops = new Set<number>();
|
|
182
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
183
|
+
assert.strictEqual(reachableRoutes.size, 0);
|
|
184
|
+
assert.deepStrictEqual(reachableRoutes, new Map());
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should find no reachable routes from non-existent stops', () => {
|
|
188
|
+
const fromStops = new Set([999, 1000]); // Non-existent stops
|
|
189
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
190
|
+
assert.strictEqual(reachableRoutes.size, 0);
|
|
191
|
+
assert.deepStrictEqual(reachableRoutes, new Map());
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should filter routes by transport modes correctly', () => {
|
|
195
|
+
const fromStops = new Set([1]);
|
|
196
|
+
|
|
197
|
+
const railRoutes = sampleTimetable.findReachableRoutes(
|
|
198
|
+
fromStops,
|
|
199
|
+
new Set<RouteType>(['RAIL']),
|
|
200
|
+
);
|
|
201
|
+
assert.strictEqual(railRoutes.size, 2);
|
|
202
|
+
assert.deepStrictEqual(
|
|
203
|
+
railRoutes,
|
|
204
|
+
new Map([
|
|
205
|
+
[route1, 0],
|
|
206
|
+
[route2, 1],
|
|
207
|
+
]),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const busRoutes = sampleTimetable.findReachableRoutes(
|
|
211
|
+
fromStops,
|
|
212
|
+
new Set<RouteType>(['BUS']),
|
|
213
|
+
);
|
|
214
|
+
assert.strictEqual(busRoutes.size, 0);
|
|
215
|
+
assert.deepStrictEqual(busRoutes, new Map());
|
|
216
|
+
|
|
217
|
+
const multiModeRoutes = sampleTimetable.findReachableRoutes(
|
|
218
|
+
fromStops,
|
|
219
|
+
new Set<RouteType>(['RAIL', 'BUS', 'SUBWAY']),
|
|
220
|
+
);
|
|
221
|
+
assert.strictEqual(multiModeRoutes.size, 2);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return earliest hop-on stop when route is accessible from multiple stops', () => {
|
|
225
|
+
// Create scenario where same route is accessible from multiple stops in the query
|
|
226
|
+
// route1 has stops [1, 2] in that order, so we need to test with those actual stops
|
|
227
|
+
const fromStops = new Set([1, 2]); // Both stops are on route1
|
|
228
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
229
|
+
|
|
230
|
+
// route1 should use stop index 0 (stop 1 comes before stop 2 on route1)
|
|
231
|
+
// route2 should use stop index 0 (stop 2 comes before stop 1 on route2)
|
|
232
|
+
assert.strictEqual(reachableRoutes.size, 2);
|
|
233
|
+
assert.deepStrictEqual(
|
|
234
|
+
reachableRoutes,
|
|
235
|
+
new Map([
|
|
236
|
+
[route1, 0], // Stop index 0 (stop 1) comes before stop index 1 (stop 2) on route1
|
|
237
|
+
[route2, 0], // Stop index 0 (stop 2) comes before stop index 1 (stop 1) on route2
|
|
238
|
+
]),
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('getContinuousTrips', () => {
|
|
243
|
+
it('should return empty array when stop has no trip continuations', () => {
|
|
244
|
+
const continuousTrips = sampleTimetable.getContinuousTrips(0, 0, 0);
|
|
245
|
+
assert.deepStrictEqual(continuousTrips, []);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should return empty array when stop has trip continuations but not for the specified trip', () => {
|
|
249
|
+
// Create a timetable with trip continuations that don't match the query
|
|
250
|
+
const tripContinuationsMap = new Map([
|
|
251
|
+
[encode(0, 0, 1), [{ hopOnStopIndex: 0, routeId: 1, tripIndex: 0 }]], // Different trip index
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
const stopsWithContinuations: StopAdjacency[] = [
|
|
255
|
+
{ routes: [] },
|
|
256
|
+
{
|
|
257
|
+
routes: [0, 1],
|
|
258
|
+
},
|
|
259
|
+
{ routes: [1] },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
const timetableWithContinuations = new Timetable(
|
|
263
|
+
stopsWithContinuations,
|
|
264
|
+
routesAdjacency,
|
|
265
|
+
routes,
|
|
266
|
+
tripContinuationsMap,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const continuousTrips = timetableWithContinuations.getContinuousTrips(
|
|
270
|
+
0,
|
|
271
|
+
0,
|
|
272
|
+
0,
|
|
273
|
+
); // Query trip index 0, but continuations are for trip index 1
|
|
274
|
+
assert.deepStrictEqual(continuousTrips, []);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should return trip continuations when they exist for the specified trip', () => {
|
|
278
|
+
const expectedContinuations: TripBoarding[] = [
|
|
279
|
+
{ hopOnStopIndex: 0, routeId: 1, tripIndex: 0 },
|
|
280
|
+
{ hopOnStopIndex: 0, routeId: 1, tripIndex: 1 },
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const tripContinuationsMap = new Map([
|
|
284
|
+
[encode(0, 0, 0), expectedContinuations],
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
const stopsWithContinuations: StopAdjacency[] = [
|
|
288
|
+
{ routes: [] },
|
|
289
|
+
{
|
|
290
|
+
routes: [0, 1],
|
|
291
|
+
},
|
|
292
|
+
{ routes: [1] },
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
const timetableWithContinuations = new Timetable(
|
|
296
|
+
stopsWithContinuations,
|
|
297
|
+
routesAdjacency,
|
|
298
|
+
routes,
|
|
299
|
+
tripContinuationsMap,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const continuousTrips = timetableWithContinuations.getContinuousTrips(
|
|
303
|
+
0,
|
|
304
|
+
0,
|
|
305
|
+
0,
|
|
306
|
+
);
|
|
307
|
+
assert.deepStrictEqual(continuousTrips, expectedContinuations);
|
|
308
|
+
});
|
|
136
309
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
143
|
-
assert.strictEqual(reachableRoutes.size, 0);
|
|
310
|
+
it('should return empty array when querying with non-matching parameters', () => {
|
|
311
|
+
const continuousTrips = sampleTimetable.getContinuousTrips(999, 0, 0);
|
|
312
|
+
assert.deepStrictEqual(continuousTrips, []);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
144
315
|
});
|
|
145
316
|
});
|