minotor 8.0.0 → 9.0.1
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/.github/workflows/minotor.yml +1 -1
- package/CHANGELOG.md +3 -8
- package/README.md +1 -1
- package/dist/cli.mjs +352 -256
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +21 -6
- package/dist/gtfs/trips.d.ts +2 -2
- package/dist/parser.cjs.js +296 -188
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +296 -188
- 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.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/router.d.ts +4 -4
- package/dist/timetable/io.d.ts +3 -3
- package/dist/timetable/proto/timetable.d.ts +6 -4
- package/dist/timetable/route.d.ts +13 -21
- package/dist/timetable/timetable.d.ts +13 -11
- package/dist/timetable/tripBoardingId.d.ts +34 -0
- package/package.json +1 -1
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/repl.ts +53 -67
- package/src/gtfs/__tests__/parser.test.ts +19 -4
- package/src/gtfs/__tests__/transfers.test.ts +598 -318
- package/src/gtfs/__tests__/trips.test.ts +3 -44
- package/src/gtfs/parser.ts +26 -8
- package/src/gtfs/transfers.ts +151 -20
- package/src/gtfs/trips.ts +1 -39
- package/src/routing/__tests__/result.test.ts +10 -10
- package/src/routing/__tests__/router.test.ts +11 -9
- package/src/routing/result.ts +2 -2
- package/src/routing/router.ts +34 -22
- package/src/timetable/__tests__/io.test.ts +8 -7
- package/src/timetable/__tests__/route.test.ts +66 -80
- package/src/timetable/__tests__/timetable.test.ts +32 -29
- package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
- package/src/timetable/io.ts +21 -20
- package/src/timetable/proto/timetable.proto +6 -4
- package/src/timetable/proto/timetable.ts +84 -48
- package/src/timetable/route.ts +39 -56
- package/src/timetable/timetable.ts +37 -26
- package/src/timetable/tripBoardingId.ts +94 -0
- package/dist/timetable/tripId.d.ts +0 -15
- package/src/timetable/__tests__/tripId.test.ts +0 -27
- package/src/timetable/tripId.ts +0 -29
- /package/dist/timetable/__tests__/{tripId.test.d.ts → tripBoardingId.test.d.ts} +0 -0
|
@@ -3,8 +3,17 @@ import { Readable } from 'node:stream';
|
|
|
3
3
|
import { describe, it } from 'node:test';
|
|
4
4
|
|
|
5
5
|
import { Duration } from '../../timetable/duration.js';
|
|
6
|
+
import { Route } from '../../timetable/route.js';
|
|
7
|
+
import { Time } from '../../timetable/time.js';
|
|
8
|
+
import { Timetable } from '../../timetable/timetable.js';
|
|
9
|
+
import { encode } from '../../timetable/tripBoardingId.js';
|
|
6
10
|
import { GtfsStopsMap } from '../stops.js';
|
|
7
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
buildTripContinuations,
|
|
13
|
+
GtfsTripContinuation,
|
|
14
|
+
parseTransfers,
|
|
15
|
+
} from '../transfers.js';
|
|
16
|
+
import { TripsMapping } from '../trips.js';
|
|
8
17
|
|
|
9
18
|
describe('GTFS transfers parser', () => {
|
|
10
19
|
it('should correctly parse valid transfers', async () => {
|
|
@@ -13,7 +22,7 @@ describe('GTFS transfers parser', () => {
|
|
|
13
22
|
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
14
23
|
);
|
|
15
24
|
mockedStream.push('"1100084","8014440:0:1","2","180"\n');
|
|
16
|
-
mockedStream.push('"1100097","8014447","
|
|
25
|
+
mockedStream.push('"1100097","8014447","0","240"\n');
|
|
17
26
|
mockedStream.push(null);
|
|
18
27
|
|
|
19
28
|
const stopsMap: GtfsStopsMap = new Map([
|
|
@@ -60,6 +69,7 @@ describe('GTFS transfers parser', () => {
|
|
|
60
69
|
]);
|
|
61
70
|
|
|
62
71
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
72
|
+
|
|
63
73
|
const expectedTransfers = new Map([
|
|
64
74
|
[
|
|
65
75
|
0, // Internal ID for stop '1100084'
|
|
@@ -76,7 +86,7 @@ describe('GTFS transfers parser', () => {
|
|
|
76
86
|
[
|
|
77
87
|
{
|
|
78
88
|
destination: 3, // Internal ID for stop '8014447'
|
|
79
|
-
type: '
|
|
89
|
+
type: 'RECOMMENDED',
|
|
80
90
|
minTransferTime: Duration.fromSeconds(240),
|
|
81
91
|
},
|
|
82
92
|
],
|
|
@@ -84,10 +94,10 @@ describe('GTFS transfers parser', () => {
|
|
|
84
94
|
]);
|
|
85
95
|
|
|
86
96
|
assert.deepEqual(result.transfers, expectedTransfers);
|
|
87
|
-
assert.deepEqual(result.tripContinuations,
|
|
97
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
88
98
|
});
|
|
89
99
|
|
|
90
|
-
it('should ignore impossible transfer types', async () => {
|
|
100
|
+
it('should ignore impossible transfer types (3 and 5)', async () => {
|
|
91
101
|
const mockedStream = new Readable();
|
|
92
102
|
mockedStream.push(
|
|
93
103
|
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
@@ -140,35 +150,37 @@ describe('GTFS transfers parser', () => {
|
|
|
140
150
|
]);
|
|
141
151
|
|
|
142
152
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
153
|
+
|
|
143
154
|
assert.deepEqual(result.transfers, new Map());
|
|
144
|
-
assert.deepEqual(result.tripContinuations,
|
|
155
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
145
156
|
});
|
|
146
157
|
|
|
147
|
-
it('should ignore
|
|
158
|
+
it('should ignore transfers with missing stop IDs', async () => {
|
|
148
159
|
const mockedStream = new Readable();
|
|
149
160
|
mockedStream.push(
|
|
150
|
-
'
|
|
161
|
+
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
151
162
|
);
|
|
152
|
-
mockedStream.push('
|
|
163
|
+
mockedStream.push(',"8014440:0:1","2","180"\n');
|
|
164
|
+
mockedStream.push('"1100097",,"0","240"\n');
|
|
153
165
|
mockedStream.push(null);
|
|
154
166
|
|
|
155
167
|
const stopsMap: GtfsStopsMap = new Map([
|
|
156
168
|
[
|
|
157
|
-
'
|
|
169
|
+
'8014440:0:1',
|
|
158
170
|
{
|
|
159
|
-
id:
|
|
160
|
-
sourceStopId: '
|
|
161
|
-
name: 'Test Stop
|
|
171
|
+
id: 1,
|
|
172
|
+
sourceStopId: '8014440:0:1',
|
|
173
|
+
name: 'Test Stop 2',
|
|
162
174
|
children: [],
|
|
163
175
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
164
176
|
},
|
|
165
177
|
],
|
|
166
178
|
[
|
|
167
|
-
'
|
|
179
|
+
'1100097',
|
|
168
180
|
{
|
|
169
|
-
id:
|
|
170
|
-
sourceStopId: '
|
|
171
|
-
name: 'Test Stop
|
|
181
|
+
id: 2,
|
|
182
|
+
sourceStopId: '1100097',
|
|
183
|
+
name: 'Test Stop 3',
|
|
172
184
|
children: [],
|
|
173
185
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
174
186
|
},
|
|
@@ -176,16 +188,18 @@ describe('GTFS transfers parser', () => {
|
|
|
176
188
|
]);
|
|
177
189
|
|
|
178
190
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
191
|
+
|
|
179
192
|
assert.deepEqual(result.transfers, new Map());
|
|
180
|
-
assert.deepEqual(result.tripContinuations,
|
|
193
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
181
194
|
});
|
|
182
195
|
|
|
183
|
-
it('should
|
|
196
|
+
it('should correctly parse in-seat transfers (type 4)', async () => {
|
|
184
197
|
const mockedStream = new Readable();
|
|
185
198
|
mockedStream.push(
|
|
186
|
-
'from_trip_id,to_trip_id,transfer_type,min_transfer_time\n',
|
|
199
|
+
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type,min_transfer_time\n',
|
|
187
200
|
);
|
|
188
|
-
mockedStream.push('"1100084","8014440","
|
|
201
|
+
mockedStream.push('"1100084","8014440:0:1","trip1","trip2","4","0"\n');
|
|
202
|
+
mockedStream.push('"1100097","8014447","trip3","trip4","4","0"\n');
|
|
189
203
|
mockedStream.push(null);
|
|
190
204
|
|
|
191
205
|
const stopsMap: GtfsStopsMap = new Map([
|
|
@@ -200,130 +214,106 @@ describe('GTFS transfers parser', () => {
|
|
|
200
214
|
},
|
|
201
215
|
],
|
|
202
216
|
[
|
|
203
|
-
'8014440',
|
|
217
|
+
'8014440:0:1',
|
|
204
218
|
{
|
|
205
219
|
id: 1,
|
|
206
|
-
sourceStopId: '8014440',
|
|
220
|
+
sourceStopId: '8014440:0:1',
|
|
207
221
|
name: 'Test Stop 2',
|
|
208
222
|
children: [],
|
|
209
223
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
210
224
|
},
|
|
211
225
|
],
|
|
212
|
-
]);
|
|
213
|
-
|
|
214
|
-
const result = await parseTransfers(mockedStream, stopsMap);
|
|
215
|
-
assert.deepEqual(result.transfers, new Map());
|
|
216
|
-
assert.deepEqual(result.tripContinuations, new Map());
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('should allow missing minimum transfer time', async () => {
|
|
220
|
-
const mockedStream = new Readable();
|
|
221
|
-
mockedStream.push(
|
|
222
|
-
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
223
|
-
);
|
|
224
|
-
mockedStream.push('"1100084","8014440:0:1","2"\n');
|
|
225
|
-
mockedStream.push(null);
|
|
226
|
-
|
|
227
|
-
const stopsMap: GtfsStopsMap = new Map([
|
|
228
226
|
[
|
|
229
|
-
'
|
|
227
|
+
'1100097',
|
|
230
228
|
{
|
|
231
|
-
id:
|
|
232
|
-
sourceStopId: '
|
|
233
|
-
name: 'Test Stop
|
|
229
|
+
id: 2,
|
|
230
|
+
sourceStopId: '1100097',
|
|
231
|
+
name: 'Test Stop 3',
|
|
234
232
|
children: [],
|
|
235
233
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
236
234
|
},
|
|
237
235
|
],
|
|
238
236
|
[
|
|
239
|
-
'
|
|
237
|
+
'8014447',
|
|
240
238
|
{
|
|
241
|
-
id:
|
|
242
|
-
sourceStopId: '
|
|
243
|
-
name: 'Test Stop
|
|
239
|
+
id: 3,
|
|
240
|
+
sourceStopId: '8014447',
|
|
241
|
+
name: 'Test Stop 4',
|
|
244
242
|
children: [],
|
|
245
243
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
246
244
|
},
|
|
247
245
|
],
|
|
248
246
|
]);
|
|
247
|
+
|
|
249
248
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
250
|
-
assert.deepEqual(
|
|
251
|
-
result.transfers,
|
|
252
|
-
new Map([
|
|
253
|
-
[
|
|
254
|
-
0, // Internal ID for stop '1100084'
|
|
255
|
-
[
|
|
256
|
-
{
|
|
257
|
-
destination: 1, // Internal ID for stop '8014440:0:1'
|
|
258
|
-
type: 'REQUIRES_MINIMAL_TIME',
|
|
259
|
-
},
|
|
260
|
-
],
|
|
261
|
-
],
|
|
262
|
-
]),
|
|
263
|
-
);
|
|
264
|
-
assert.deepEqual(result.tripContinuations, new Map());
|
|
265
|
-
});
|
|
266
249
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
250
|
+
const expectedTripContinuations = [
|
|
251
|
+
{
|
|
252
|
+
fromStop: 0,
|
|
253
|
+
fromTrip: 'trip1',
|
|
254
|
+
toStop: 1,
|
|
255
|
+
toTrip: 'trip2',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
fromStop: 2,
|
|
259
|
+
fromTrip: 'trip3',
|
|
260
|
+
toStop: 3,
|
|
261
|
+
toTrip: 'trip4',
|
|
262
|
+
},
|
|
263
|
+
];
|
|
273
264
|
|
|
274
|
-
const stopsMap: GtfsStopsMap = new Map();
|
|
275
|
-
|
|
276
|
-
const result = await parseTransfers(mockedStream, stopsMap);
|
|
277
265
|
assert.deepEqual(result.transfers, new Map());
|
|
278
|
-
assert.deepEqual(result.tripContinuations,
|
|
266
|
+
assert.deepEqual(result.tripContinuations, expectedTripContinuations);
|
|
279
267
|
});
|
|
280
268
|
|
|
281
|
-
it('should
|
|
269
|
+
it('should ignore in-seat transfers with missing trip IDs', async () => {
|
|
282
270
|
const mockedStream = new Readable();
|
|
283
271
|
mockedStream.push(
|
|
284
|
-
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type\n',
|
|
272
|
+
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type,min_transfer_time\n',
|
|
285
273
|
);
|
|
286
|
-
mockedStream.push('"
|
|
287
|
-
mockedStream.push('"
|
|
274
|
+
mockedStream.push('"1100084","8014440:0:1",,"trip2","4","0"\n');
|
|
275
|
+
mockedStream.push('"1100097","8014447","trip3",,"4","0"\n');
|
|
276
|
+
mockedStream.push('"1100098","8014448","","trip5","4","0"\n');
|
|
277
|
+
mockedStream.push('"1100099","8014449","trip6","","4","0"\n');
|
|
288
278
|
mockedStream.push(null);
|
|
289
279
|
|
|
290
280
|
const stopsMap: GtfsStopsMap = new Map([
|
|
291
281
|
[
|
|
292
|
-
'
|
|
282
|
+
'1100084',
|
|
293
283
|
{
|
|
294
284
|
id: 0,
|
|
295
|
-
sourceStopId: '
|
|
296
|
-
name: 'Stop 1',
|
|
285
|
+
sourceStopId: '1100084',
|
|
286
|
+
name: 'Test Stop 1',
|
|
297
287
|
children: [],
|
|
298
288
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
299
289
|
},
|
|
300
290
|
],
|
|
301
291
|
[
|
|
302
|
-
'
|
|
292
|
+
'8014440:0:1',
|
|
303
293
|
{
|
|
304
294
|
id: 1,
|
|
305
|
-
sourceStopId: '
|
|
306
|
-
name: 'Stop 2',
|
|
295
|
+
sourceStopId: '8014440:0:1',
|
|
296
|
+
name: 'Test Stop 2',
|
|
307
297
|
children: [],
|
|
308
298
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
309
299
|
},
|
|
310
300
|
],
|
|
311
301
|
[
|
|
312
|
-
'
|
|
302
|
+
'1100097',
|
|
313
303
|
{
|
|
314
304
|
id: 2,
|
|
315
|
-
sourceStopId: '
|
|
316
|
-
name: 'Stop 3',
|
|
305
|
+
sourceStopId: '1100097',
|
|
306
|
+
name: 'Test Stop 3',
|
|
317
307
|
children: [],
|
|
318
308
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
319
309
|
},
|
|
320
310
|
],
|
|
321
311
|
[
|
|
322
|
-
'
|
|
312
|
+
'8014447',
|
|
323
313
|
{
|
|
324
314
|
id: 3,
|
|
325
|
-
sourceStopId: '
|
|
326
|
-
name: 'Stop 4',
|
|
315
|
+
sourceStopId: '8014447',
|
|
316
|
+
name: 'Test Stop 4',
|
|
327
317
|
children: [],
|
|
328
318
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
329
319
|
},
|
|
@@ -332,136 +322,130 @@ describe('GTFS transfers parser', () => {
|
|
|
332
322
|
|
|
333
323
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
334
324
|
|
|
335
|
-
const expectedTripContinuations = new Map([
|
|
336
|
-
[
|
|
337
|
-
0, // from_stop_id 'stop1' -> internal ID 0
|
|
338
|
-
[
|
|
339
|
-
{
|
|
340
|
-
fromTrip: 'trip1',
|
|
341
|
-
toTrip: 'trip2',
|
|
342
|
-
hopOnStop: 1, // to_stop_id 'stop2' -> internal ID 1
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
],
|
|
346
|
-
[
|
|
347
|
-
2, // from_stop_id 'stop3' -> internal ID 2
|
|
348
|
-
[
|
|
349
|
-
{
|
|
350
|
-
fromTrip: 'trip3',
|
|
351
|
-
toTrip: 'trip4',
|
|
352
|
-
hopOnStop: 3, // to_stop_id 'stop4' -> internal ID 3
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
],
|
|
356
|
-
]);
|
|
357
|
-
|
|
358
325
|
assert.deepEqual(result.transfers, new Map());
|
|
359
|
-
assert.deepEqual(result.tripContinuations,
|
|
326
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
360
327
|
});
|
|
361
328
|
|
|
362
|
-
it('should
|
|
329
|
+
it('should ignore unsupported transfer types between trips', async () => {
|
|
363
330
|
const mockedStream = new Readable();
|
|
364
331
|
mockedStream.push(
|
|
365
|
-
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type\n',
|
|
332
|
+
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type,min_transfer_time\n',
|
|
366
333
|
);
|
|
367
|
-
mockedStream.push('"
|
|
368
|
-
mockedStream.push('"stop1","stop3","trip1","trip3","4"\n');
|
|
334
|
+
mockedStream.push('"1100084","8014440:0:1","trip1","trip2","1","0"\n');
|
|
369
335
|
mockedStream.push(null);
|
|
370
336
|
|
|
371
337
|
const stopsMap: GtfsStopsMap = new Map([
|
|
372
338
|
[
|
|
373
|
-
'
|
|
339
|
+
'1100084',
|
|
374
340
|
{
|
|
375
341
|
id: 0,
|
|
376
|
-
sourceStopId: '
|
|
377
|
-
name: 'Stop 1',
|
|
342
|
+
sourceStopId: '1100084',
|
|
343
|
+
name: 'Test Stop 1',
|
|
378
344
|
children: [],
|
|
379
345
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
380
346
|
},
|
|
381
347
|
],
|
|
382
348
|
[
|
|
383
|
-
'
|
|
349
|
+
'8014440:0:1',
|
|
384
350
|
{
|
|
385
351
|
id: 1,
|
|
386
|
-
sourceStopId: '
|
|
387
|
-
name: 'Stop 2',
|
|
352
|
+
sourceStopId: '8014440:0:1',
|
|
353
|
+
name: 'Test Stop 2',
|
|
388
354
|
children: [],
|
|
389
355
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
390
356
|
},
|
|
391
357
|
],
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
const result = await parseTransfers(mockedStream, stopsMap);
|
|
361
|
+
|
|
362
|
+
assert.deepEqual(result.transfers, new Map());
|
|
363
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should ignore unsupported transfer types between routes', async () => {
|
|
367
|
+
const mockedStream = new Readable();
|
|
368
|
+
mockedStream.push(
|
|
369
|
+
'from_stop_id,to_stop_id,from_route_id,to_route_id,transfer_type,min_transfer_time\n',
|
|
370
|
+
);
|
|
371
|
+
mockedStream.push('"1100084","8014440:0:1","route1","route2","1","0"\n');
|
|
372
|
+
mockedStream.push(null);
|
|
373
|
+
|
|
374
|
+
const stopsMap: GtfsStopsMap = new Map([
|
|
392
375
|
[
|
|
393
|
-
'
|
|
376
|
+
'1100084',
|
|
394
377
|
{
|
|
395
|
-
id:
|
|
396
|
-
sourceStopId: '
|
|
397
|
-
name: 'Stop
|
|
378
|
+
id: 0,
|
|
379
|
+
sourceStopId: '1100084',
|
|
380
|
+
name: 'Test Stop 1',
|
|
398
381
|
children: [],
|
|
399
382
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
400
383
|
},
|
|
401
384
|
],
|
|
402
|
-
]);
|
|
403
|
-
|
|
404
|
-
const result = await parseTransfers(mockedStream, stopsMap);
|
|
405
|
-
|
|
406
|
-
const expectedTripContinuations = new Map([
|
|
407
385
|
[
|
|
408
|
-
0,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
fromTrip: 'trip1',
|
|
417
|
-
toTrip: 'trip3',
|
|
418
|
-
hopOnStop: 2, // to_stop_id 'stop3' -> internal ID 2
|
|
419
|
-
},
|
|
420
|
-
],
|
|
386
|
+
'8014440:0:1',
|
|
387
|
+
{
|
|
388
|
+
id: 1,
|
|
389
|
+
sourceStopId: '8014440:0:1',
|
|
390
|
+
name: 'Test Stop 2',
|
|
391
|
+
children: [],
|
|
392
|
+
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
393
|
+
},
|
|
421
394
|
],
|
|
422
395
|
]);
|
|
423
396
|
|
|
397
|
+
const result = await parseTransfers(mockedStream, stopsMap);
|
|
398
|
+
|
|
424
399
|
assert.deepEqual(result.transfers, new Map());
|
|
425
|
-
assert.deepEqual(result.tripContinuations,
|
|
400
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
426
401
|
});
|
|
427
402
|
|
|
428
|
-
it('should
|
|
403
|
+
it('should handle transfers without minimum transfer time', async () => {
|
|
429
404
|
const mockedStream = new Readable();
|
|
430
405
|
mockedStream.push(
|
|
431
|
-
'from_stop_id,to_stop_id,
|
|
406
|
+
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
432
407
|
);
|
|
433
|
-
mockedStream.push('"
|
|
434
|
-
mockedStream.push('"
|
|
435
|
-
mockedStream.push('"stop2","stop3","","","0",""\n'); // Regular transfer
|
|
408
|
+
mockedStream.push('"1100084","8014440:0:1","2"\n');
|
|
409
|
+
mockedStream.push('"1100097","8014447","1","0"\n');
|
|
436
410
|
mockedStream.push(null);
|
|
437
411
|
|
|
438
412
|
const stopsMap: GtfsStopsMap = new Map([
|
|
439
413
|
[
|
|
440
|
-
'
|
|
414
|
+
'1100084',
|
|
441
415
|
{
|
|
442
416
|
id: 0,
|
|
443
|
-
sourceStopId: '
|
|
444
|
-
name: 'Stop 1',
|
|
417
|
+
sourceStopId: '1100084',
|
|
418
|
+
name: 'Test Stop 1',
|
|
445
419
|
children: [],
|
|
446
420
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
447
421
|
},
|
|
448
422
|
],
|
|
449
423
|
[
|
|
450
|
-
'
|
|
424
|
+
'8014440:0:1',
|
|
451
425
|
{
|
|
452
426
|
id: 1,
|
|
453
|
-
sourceStopId: '
|
|
454
|
-
name: 'Stop 2',
|
|
427
|
+
sourceStopId: '8014440:0:1',
|
|
428
|
+
name: 'Test Stop 2',
|
|
455
429
|
children: [],
|
|
456
430
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
457
431
|
},
|
|
458
432
|
],
|
|
459
433
|
[
|
|
460
|
-
'
|
|
434
|
+
'1100097',
|
|
461
435
|
{
|
|
462
436
|
id: 2,
|
|
463
|
-
sourceStopId: '
|
|
464
|
-
name: 'Stop 3',
|
|
437
|
+
sourceStopId: '1100097',
|
|
438
|
+
name: 'Test Stop 3',
|
|
439
|
+
children: [],
|
|
440
|
+
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
[
|
|
444
|
+
'8014447',
|
|
445
|
+
{
|
|
446
|
+
id: 3,
|
|
447
|
+
sourceStopId: '8014447',
|
|
448
|
+
name: 'Test Stop 4',
|
|
465
449
|
children: [],
|
|
466
450
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
467
451
|
},
|
|
@@ -472,215 +456,152 @@ describe('GTFS transfers parser', () => {
|
|
|
472
456
|
|
|
473
457
|
const expectedTransfers = new Map([
|
|
474
458
|
[
|
|
475
|
-
0,
|
|
459
|
+
0,
|
|
476
460
|
[
|
|
477
461
|
{
|
|
478
|
-
destination: 1,
|
|
462
|
+
destination: 1,
|
|
479
463
|
type: 'REQUIRES_MINIMAL_TIME',
|
|
480
|
-
minTransferTime: Duration.fromSeconds(120),
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
],
|
|
484
|
-
[
|
|
485
|
-
1, // from_stop_id 'stop2' -> internal ID 1
|
|
486
|
-
[
|
|
487
|
-
{
|
|
488
|
-
destination: 2, // to_stop_id 'stop3' -> internal ID 2
|
|
489
|
-
type: 'RECOMMENDED',
|
|
490
464
|
},
|
|
491
465
|
],
|
|
492
466
|
],
|
|
493
|
-
]);
|
|
494
|
-
|
|
495
|
-
const expectedTripContinuations = new Map([
|
|
496
467
|
[
|
|
497
|
-
|
|
468
|
+
2,
|
|
498
469
|
[
|
|
499
470
|
{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
471
|
+
destination: 3,
|
|
472
|
+
type: 'GUARANTEED',
|
|
473
|
+
minTransferTime: Duration.fromSeconds(0),
|
|
503
474
|
},
|
|
504
475
|
],
|
|
505
476
|
],
|
|
506
477
|
]);
|
|
507
478
|
|
|
508
479
|
assert.deepEqual(result.transfers, expectedTransfers);
|
|
509
|
-
assert.deepEqual(result.tripContinuations,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
it('should ignore trip continuations with undefined trip IDs', async () => {
|
|
513
|
-
const mockedStream = new Readable();
|
|
514
|
-
mockedStream.push('from_stop_id,to_stop_id,transfer_type\n');
|
|
515
|
-
mockedStream.push('"stop1","stop2","4"\n');
|
|
516
|
-
mockedStream.push(null);
|
|
517
|
-
|
|
518
|
-
const stopsMap: GtfsStopsMap = new Map([
|
|
519
|
-
[
|
|
520
|
-
'stop1',
|
|
521
|
-
{
|
|
522
|
-
id: 0,
|
|
523
|
-
sourceStopId: 'stop1',
|
|
524
|
-
name: 'Stop 1',
|
|
525
|
-
children: [],
|
|
526
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
527
|
-
},
|
|
528
|
-
],
|
|
529
|
-
[
|
|
530
|
-
'stop2',
|
|
531
|
-
{
|
|
532
|
-
id: 1,
|
|
533
|
-
sourceStopId: 'stop2',
|
|
534
|
-
name: 'Stop 2',
|
|
535
|
-
children: [],
|
|
536
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
537
|
-
},
|
|
538
|
-
],
|
|
539
|
-
]);
|
|
540
|
-
|
|
541
|
-
const result = await parseTransfers(mockedStream, stopsMap);
|
|
542
|
-
|
|
543
|
-
assert.deepEqual(result.transfers, new Map());
|
|
544
|
-
assert.deepEqual(result.tripContinuations, new Map());
|
|
480
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
545
481
|
});
|
|
546
482
|
|
|
547
|
-
it('should
|
|
483
|
+
it('should handle mixed transfers and trip continuations', async () => {
|
|
548
484
|
const mockedStream = new Readable();
|
|
549
485
|
mockedStream.push(
|
|
550
|
-
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type\n',
|
|
486
|
+
'from_stop_id,to_stop_id,from_trip_id,to_trip_id,transfer_type,min_transfer_time\n',
|
|
551
487
|
);
|
|
552
|
-
mockedStream.push('"
|
|
553
|
-
mockedStream.push('"
|
|
554
|
-
mockedStream.push('"stop5","stop6","","","4"\n');
|
|
488
|
+
mockedStream.push('"1100084","8014440:0:1","","","1","120"\n');
|
|
489
|
+
mockedStream.push('"1100097","8014447","trip1","trip2","4","0"\n');
|
|
555
490
|
mockedStream.push(null);
|
|
556
491
|
|
|
557
492
|
const stopsMap: GtfsStopsMap = new Map([
|
|
558
493
|
[
|
|
559
|
-
'
|
|
494
|
+
'1100084',
|
|
560
495
|
{
|
|
561
496
|
id: 0,
|
|
562
|
-
sourceStopId: '
|
|
563
|
-
name: 'Stop 1',
|
|
497
|
+
sourceStopId: '1100084',
|
|
498
|
+
name: 'Test Stop 1',
|
|
564
499
|
children: [],
|
|
565
500
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
566
501
|
},
|
|
567
502
|
],
|
|
568
503
|
[
|
|
569
|
-
'
|
|
504
|
+
'8014440:0:1',
|
|
570
505
|
{
|
|
571
506
|
id: 1,
|
|
572
|
-
sourceStopId: '
|
|
573
|
-
name: 'Stop 2',
|
|
507
|
+
sourceStopId: '8014440:0:1',
|
|
508
|
+
name: 'Test Stop 2',
|
|
574
509
|
children: [],
|
|
575
510
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
576
511
|
},
|
|
577
512
|
],
|
|
578
513
|
[
|
|
579
|
-
'
|
|
514
|
+
'1100097',
|
|
580
515
|
{
|
|
581
516
|
id: 2,
|
|
582
|
-
sourceStopId: '
|
|
583
|
-
name: 'Stop 3',
|
|
517
|
+
sourceStopId: '1100097',
|
|
518
|
+
name: 'Test Stop 3',
|
|
584
519
|
children: [],
|
|
585
520
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
586
521
|
},
|
|
587
522
|
],
|
|
588
523
|
[
|
|
589
|
-
'
|
|
524
|
+
'8014447',
|
|
590
525
|
{
|
|
591
526
|
id: 3,
|
|
592
|
-
sourceStopId: '
|
|
593
|
-
name: 'Stop 4',
|
|
594
|
-
children: [],
|
|
595
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
596
|
-
},
|
|
597
|
-
],
|
|
598
|
-
[
|
|
599
|
-
'stop5',
|
|
600
|
-
{
|
|
601
|
-
id: 4,
|
|
602
|
-
sourceStopId: 'stop5',
|
|
603
|
-
name: 'Stop 5',
|
|
527
|
+
sourceStopId: '8014447',
|
|
528
|
+
name: 'Test Stop 4',
|
|
604
529
|
children: [],
|
|
605
530
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
606
531
|
},
|
|
607
532
|
],
|
|
533
|
+
]);
|
|
534
|
+
|
|
535
|
+
const result = await parseTransfers(mockedStream, stopsMap);
|
|
536
|
+
|
|
537
|
+
const expectedTransfers = new Map([
|
|
608
538
|
[
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
539
|
+
0,
|
|
540
|
+
[
|
|
541
|
+
{
|
|
542
|
+
destination: 1,
|
|
543
|
+
type: 'GUARANTEED',
|
|
544
|
+
minTransferTime: Duration.fromSeconds(120),
|
|
545
|
+
},
|
|
546
|
+
],
|
|
617
547
|
],
|
|
618
548
|
]);
|
|
619
549
|
|
|
550
|
+
const expectedTripContinuations = [
|
|
551
|
+
{
|
|
552
|
+
fromStop: 2,
|
|
553
|
+
fromTrip: 'trip1',
|
|
554
|
+
toStop: 3,
|
|
555
|
+
toTrip: 'trip2',
|
|
556
|
+
},
|
|
557
|
+
];
|
|
558
|
+
|
|
559
|
+
assert.deepEqual(result.transfers, expectedTransfers);
|
|
560
|
+
assert.deepEqual(result.tripContinuations, expectedTripContinuations);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should handle empty transfers file', async () => {
|
|
564
|
+
const mockedStream = new Readable();
|
|
565
|
+
mockedStream.push(
|
|
566
|
+
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
567
|
+
);
|
|
568
|
+
mockedStream.push(null);
|
|
569
|
+
|
|
570
|
+
const stopsMap: GtfsStopsMap = new Map();
|
|
571
|
+
|
|
620
572
|
const result = await parseTransfers(mockedStream, stopsMap);
|
|
621
573
|
|
|
622
574
|
assert.deepEqual(result.transfers, new Map());
|
|
623
|
-
assert.deepEqual(result.tripContinuations,
|
|
575
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
624
576
|
});
|
|
625
577
|
|
|
626
|
-
it('should
|
|
578
|
+
it('should ignore transfers with non-existent stops', async () => {
|
|
627
579
|
const mockedStream = new Readable();
|
|
628
580
|
mockedStream.push(
|
|
629
|
-
'from_stop_id,to_stop_id,
|
|
581
|
+
'from_stop_id,to_stop_id,transfer_type,min_transfer_time\n',
|
|
630
582
|
);
|
|
631
|
-
mockedStream.push('"
|
|
632
|
-
mockedStream.push('"
|
|
633
|
-
mockedStream.push('"
|
|
634
|
-
mockedStream.push('"stop1","stop5","trip3","trip4","4",""\n'); // Another trip continuation to stop5
|
|
583
|
+
mockedStream.push('"unknown_stop","8014440:0:1","0","120"\n');
|
|
584
|
+
mockedStream.push('"1100084","unknown_stop","1","60"\n');
|
|
585
|
+
mockedStream.push('"1100084","8014440:0:1","2","180"\n');
|
|
635
586
|
mockedStream.push(null);
|
|
636
587
|
|
|
637
588
|
const stopsMap: GtfsStopsMap = new Map([
|
|
638
589
|
[
|
|
639
|
-
'
|
|
590
|
+
'1100084',
|
|
640
591
|
{
|
|
641
592
|
id: 0,
|
|
642
|
-
sourceStopId: '
|
|
643
|
-
name: 'Stop 1',
|
|
593
|
+
sourceStopId: '1100084',
|
|
594
|
+
name: 'Test Stop 1',
|
|
644
595
|
children: [],
|
|
645
596
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
646
597
|
},
|
|
647
598
|
],
|
|
648
599
|
[
|
|
649
|
-
'
|
|
600
|
+
'8014440:0:1',
|
|
650
601
|
{
|
|
651
602
|
id: 1,
|
|
652
|
-
sourceStopId: '
|
|
653
|
-
name: 'Stop 2',
|
|
654
|
-
children: [],
|
|
655
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
[
|
|
659
|
-
'stop3',
|
|
660
|
-
{
|
|
661
|
-
id: 2,
|
|
662
|
-
sourceStopId: 'stop3',
|
|
663
|
-
name: 'Stop 3',
|
|
664
|
-
children: [],
|
|
665
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
666
|
-
},
|
|
667
|
-
],
|
|
668
|
-
[
|
|
669
|
-
'stop4',
|
|
670
|
-
{
|
|
671
|
-
id: 3,
|
|
672
|
-
sourceStopId: 'stop4',
|
|
673
|
-
name: 'Stop 4',
|
|
674
|
-
children: [],
|
|
675
|
-
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
676
|
-
},
|
|
677
|
-
],
|
|
678
|
-
[
|
|
679
|
-
'stop5',
|
|
680
|
-
{
|
|
681
|
-
id: 4,
|
|
682
|
-
sourceStopId: 'stop5',
|
|
683
|
-
name: 'Stop 5',
|
|
603
|
+
sourceStopId: '8014440:0:1',
|
|
604
|
+
name: 'Test Stop 2',
|
|
684
605
|
children: [],
|
|
685
606
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
686
607
|
},
|
|
@@ -691,40 +612,399 @@ describe('GTFS transfers parser', () => {
|
|
|
691
612
|
|
|
692
613
|
const expectedTransfers = new Map([
|
|
693
614
|
[
|
|
694
|
-
0,
|
|
615
|
+
0,
|
|
695
616
|
[
|
|
696
617
|
{
|
|
697
|
-
destination: 1,
|
|
618
|
+
destination: 1,
|
|
698
619
|
type: 'REQUIRES_MINIMAL_TIME',
|
|
699
|
-
minTransferTime: Duration.fromSeconds(
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
destination: 3, // to_stop_id 'stop4' -> internal ID 3
|
|
703
|
-
type: 'RECOMMENDED',
|
|
620
|
+
minTransferTime: Duration.fromSeconds(180),
|
|
704
621
|
},
|
|
705
622
|
],
|
|
706
623
|
],
|
|
707
624
|
]);
|
|
708
625
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
fromTrip: 'trip3',
|
|
720
|
-
toTrip: 'trip4',
|
|
721
|
-
hopOnStop: 4, // to_stop_id 'stop5' -> internal ID 4
|
|
722
|
-
},
|
|
723
|
-
],
|
|
724
|
-
],
|
|
626
|
+
assert.deepEqual(result.transfers, expectedTransfers);
|
|
627
|
+
assert.deepEqual(result.tripContinuations, []);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe('buildTripContinuations', () => {
|
|
632
|
+
it('should build trip continuations for valid data', () => {
|
|
633
|
+
const tripsMapping: TripsMapping = new Map([
|
|
634
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
635
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
725
636
|
]);
|
|
726
637
|
|
|
727
|
-
|
|
728
|
-
|
|
638
|
+
const tripContinuations = [
|
|
639
|
+
{
|
|
640
|
+
fromStop: 100,
|
|
641
|
+
fromTrip: 'trip1',
|
|
642
|
+
toStop: 200,
|
|
643
|
+
toTrip: 'trip2',
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
|
|
647
|
+
// Mock route with simple stops and timing
|
|
648
|
+
const mockFromRoute = {
|
|
649
|
+
stopRouteIndices: () => [0],
|
|
650
|
+
arrivalAt: () => Time.fromMinutes(60), // 1:00
|
|
651
|
+
} as unknown as Route;
|
|
652
|
+
|
|
653
|
+
const mockToRoute = {
|
|
654
|
+
stopRouteIndices: () => [1],
|
|
655
|
+
departureFrom: () => Time.fromMinutes(75), // 1:15
|
|
656
|
+
} as unknown as Route;
|
|
657
|
+
|
|
658
|
+
const mockTimetable = {
|
|
659
|
+
getRoute: (routeId: number) =>
|
|
660
|
+
routeId === 0 ? mockFromRoute : mockToRoute,
|
|
661
|
+
} as unknown as Timetable;
|
|
662
|
+
|
|
663
|
+
const activeStopIds = new Set([100, 200]);
|
|
664
|
+
|
|
665
|
+
const result = buildTripContinuations(
|
|
666
|
+
tripsMapping,
|
|
667
|
+
tripContinuations,
|
|
668
|
+
mockTimetable,
|
|
669
|
+
activeStopIds,
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
const expectedTripBoardingId = encode(0, 0, 0);
|
|
673
|
+
const continuations = result.get(expectedTripBoardingId);
|
|
674
|
+
|
|
675
|
+
assert(continuations);
|
|
676
|
+
assert.strictEqual(continuations.length, 1);
|
|
677
|
+
assert.deepEqual(continuations[0], {
|
|
678
|
+
hopOnStopIndex: 1,
|
|
679
|
+
routeId: 1,
|
|
680
|
+
tripIndex: 0,
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('should ignore trip continuations with inactive stops', () => {
|
|
685
|
+
const tripsMapping: TripsMapping = new Map([
|
|
686
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
687
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
688
|
+
]);
|
|
689
|
+
|
|
690
|
+
const tripContinuations = [
|
|
691
|
+
{
|
|
692
|
+
fromStop: 100, // inactive stop
|
|
693
|
+
fromTrip: 'trip1',
|
|
694
|
+
toStop: 200,
|
|
695
|
+
toTrip: 'trip2',
|
|
696
|
+
},
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
const mockTimetable = {} as unknown as Timetable;
|
|
700
|
+
const activeStopIds = new Set([200]); // only toStop is active
|
|
701
|
+
|
|
702
|
+
const result = buildTripContinuations(
|
|
703
|
+
tripsMapping,
|
|
704
|
+
tripContinuations,
|
|
705
|
+
mockTimetable,
|
|
706
|
+
activeStopIds,
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
assert.strictEqual(result.size, 0);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it('should ignore trip continuations with unknown trip IDs', () => {
|
|
713
|
+
const tripsMapping: TripsMapping = new Map([
|
|
714
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
715
|
+
]);
|
|
716
|
+
|
|
717
|
+
const tripContinuations = [
|
|
718
|
+
{
|
|
719
|
+
fromStop: 100,
|
|
720
|
+
fromTrip: 'unknown_trip', // not in tripsMapping
|
|
721
|
+
toStop: 200,
|
|
722
|
+
toTrip: 'trip1',
|
|
723
|
+
},
|
|
724
|
+
];
|
|
725
|
+
|
|
726
|
+
const mockTimetable = {} as unknown as Timetable;
|
|
727
|
+
const activeStopIds = new Set([100, 200]);
|
|
728
|
+
|
|
729
|
+
const result = buildTripContinuations(
|
|
730
|
+
tripsMapping,
|
|
731
|
+
tripContinuations,
|
|
732
|
+
mockTimetable,
|
|
733
|
+
activeStopIds,
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
assert.strictEqual(result.size, 0);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should ignore trip continuations with unknown routes', () => {
|
|
740
|
+
const tripsMapping: TripsMapping = new Map([
|
|
741
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
742
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
743
|
+
]);
|
|
744
|
+
|
|
745
|
+
const tripContinuations = [
|
|
746
|
+
{
|
|
747
|
+
fromStop: 100,
|
|
748
|
+
fromTrip: 'trip1',
|
|
749
|
+
toStop: 200,
|
|
750
|
+
toTrip: 'trip2',
|
|
751
|
+
},
|
|
752
|
+
];
|
|
753
|
+
|
|
754
|
+
const mockTimetable = {
|
|
755
|
+
getRoute: () => undefined, // no routes found
|
|
756
|
+
} as unknown as Timetable;
|
|
757
|
+
|
|
758
|
+
const activeStopIds = new Set([100, 200]);
|
|
759
|
+
|
|
760
|
+
const result = buildTripContinuations(
|
|
761
|
+
tripsMapping,
|
|
762
|
+
tripContinuations,
|
|
763
|
+
mockTimetable,
|
|
764
|
+
activeStopIds,
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
assert.strictEqual(result.size, 0);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it('should ignore trip continuations with no valid timing', () => {
|
|
771
|
+
const tripsMapping: TripsMapping = new Map([
|
|
772
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
773
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
774
|
+
]);
|
|
775
|
+
|
|
776
|
+
const tripContinuations = [
|
|
777
|
+
{
|
|
778
|
+
fromStop: 100,
|
|
779
|
+
fromTrip: 'trip1',
|
|
780
|
+
toStop: 200,
|
|
781
|
+
toTrip: 'trip2',
|
|
782
|
+
},
|
|
783
|
+
];
|
|
784
|
+
|
|
785
|
+
const mockFromRoute = {
|
|
786
|
+
stopRouteIndices: () => [0],
|
|
787
|
+
arrivalAt: () => Time.fromMinutes(75), // 1:15 - arrives AFTER departure
|
|
788
|
+
} as unknown as Route;
|
|
789
|
+
|
|
790
|
+
const mockToRoute = {
|
|
791
|
+
stopRouteIndices: () => [1],
|
|
792
|
+
departureFrom: () => Time.fromMinutes(60), // 1:00 - departs BEFORE arrival
|
|
793
|
+
} as unknown as Route;
|
|
794
|
+
|
|
795
|
+
const mockTimetable = {
|
|
796
|
+
getRoute: (routeId: number) =>
|
|
797
|
+
routeId === 0 ? mockFromRoute : mockToRoute,
|
|
798
|
+
} as unknown as Timetable;
|
|
799
|
+
|
|
800
|
+
const activeStopIds = new Set([100, 200]);
|
|
801
|
+
|
|
802
|
+
const result = buildTripContinuations(
|
|
803
|
+
tripsMapping,
|
|
804
|
+
tripContinuations,
|
|
805
|
+
mockTimetable,
|
|
806
|
+
activeStopIds,
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
assert.strictEqual(result.size, 0);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it('should handle multiple continuations from same trip boarding', () => {
|
|
813
|
+
const tripsMapping: TripsMapping = new Map([
|
|
814
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
815
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
816
|
+
['trip3', { routeId: 2, tripRouteIndex: 0 }],
|
|
817
|
+
]);
|
|
818
|
+
|
|
819
|
+
const tripContinuations = [
|
|
820
|
+
{
|
|
821
|
+
fromStop: 100,
|
|
822
|
+
fromTrip: 'trip1',
|
|
823
|
+
toStop: 200,
|
|
824
|
+
toTrip: 'trip2',
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
fromStop: 100,
|
|
828
|
+
fromTrip: 'trip1',
|
|
829
|
+
toStop: 300,
|
|
830
|
+
toTrip: 'trip3',
|
|
831
|
+
},
|
|
832
|
+
];
|
|
833
|
+
|
|
834
|
+
const mockFromRoute = {
|
|
835
|
+
stopRouteIndices: () => [0],
|
|
836
|
+
arrivalAt: () => Time.fromMinutes(60),
|
|
837
|
+
} as unknown as Route;
|
|
838
|
+
|
|
839
|
+
const mockToRoute1 = {
|
|
840
|
+
stopRouteIndices: () => [1],
|
|
841
|
+
departureFrom: () => Time.fromMinutes(70),
|
|
842
|
+
} as unknown as Route;
|
|
843
|
+
|
|
844
|
+
const mockToRoute2 = {
|
|
845
|
+
stopRouteIndices: () => [2],
|
|
846
|
+
departureFrom: () => Time.fromMinutes(80),
|
|
847
|
+
} as unknown as Route;
|
|
848
|
+
|
|
849
|
+
const mockTimetable = {
|
|
850
|
+
getRoute: (routeId: number) => {
|
|
851
|
+
if (routeId === 0) return mockFromRoute;
|
|
852
|
+
if (routeId === 1) return mockToRoute1;
|
|
853
|
+
if (routeId === 2) return mockToRoute2;
|
|
854
|
+
return undefined;
|
|
855
|
+
},
|
|
856
|
+
} as unknown as Timetable;
|
|
857
|
+
|
|
858
|
+
const activeStopIds = new Set([100, 200, 300]);
|
|
859
|
+
|
|
860
|
+
const result = buildTripContinuations(
|
|
861
|
+
tripsMapping,
|
|
862
|
+
tripContinuations,
|
|
863
|
+
mockTimetable,
|
|
864
|
+
activeStopIds,
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
const expectedTripBoardingId = encode(0, 0, 0);
|
|
868
|
+
const continuations = result.get(expectedTripBoardingId);
|
|
869
|
+
|
|
870
|
+
assert(continuations);
|
|
871
|
+
assert.strictEqual(continuations.length, 2);
|
|
872
|
+
assert.deepEqual(continuations[0], {
|
|
873
|
+
hopOnStopIndex: 1,
|
|
874
|
+
routeId: 1,
|
|
875
|
+
tripIndex: 0,
|
|
876
|
+
});
|
|
877
|
+
assert.deepEqual(continuations[1], {
|
|
878
|
+
hopOnStopIndex: 2,
|
|
879
|
+
routeId: 2,
|
|
880
|
+
tripIndex: 0,
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('should handle empty input gracefully', () => {
|
|
885
|
+
const tripsMapping: TripsMapping = new Map();
|
|
886
|
+
const tripContinuations: GtfsTripContinuation[] = [];
|
|
887
|
+
const mockTimetable = {} as unknown as Timetable;
|
|
888
|
+
const activeStopIds = new Set<number>();
|
|
889
|
+
|
|
890
|
+
const result = buildTripContinuations(
|
|
891
|
+
tripsMapping,
|
|
892
|
+
tripContinuations,
|
|
893
|
+
mockTimetable,
|
|
894
|
+
activeStopIds,
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
assert.strictEqual(result.size, 0);
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('should disambiguate transfers when routes visit same stop multiple times', () => {
|
|
901
|
+
const tripsMapping: TripsMapping = new Map([
|
|
902
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
903
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
904
|
+
]);
|
|
905
|
+
|
|
906
|
+
const tripContinuations = [
|
|
907
|
+
{
|
|
908
|
+
fromStop: 100, // This stop appears multiple times in the route
|
|
909
|
+
fromTrip: 'trip1',
|
|
910
|
+
toStop: 200, // This stop also appears multiple times
|
|
911
|
+
toTrip: 'trip2',
|
|
912
|
+
},
|
|
913
|
+
];
|
|
914
|
+
|
|
915
|
+
// Mock route that visits stop 100 at indices 0 and 3 (circular route)
|
|
916
|
+
const mockFromRoute = {
|
|
917
|
+
stopRouteIndices: () => [0, 3], // Stop 100 appears twice
|
|
918
|
+
arrivalAt: (stopIndex: number) => {
|
|
919
|
+
// First visit at 1:00, second visit at 2:00
|
|
920
|
+
return stopIndex === 0 ? Time.fromMinutes(60) : Time.fromMinutes(120);
|
|
921
|
+
},
|
|
922
|
+
} as unknown as Route;
|
|
923
|
+
|
|
924
|
+
// Mock route that visits stop 200 at indices 1 and 4
|
|
925
|
+
const mockToRoute = {
|
|
926
|
+
stopRouteIndices: () => [1, 4], // Stop 200 appears twice
|
|
927
|
+
departureFrom: (stopIndex: number) => {
|
|
928
|
+
// First departure at 1:10, second departure at 2:30
|
|
929
|
+
return stopIndex === 1 ? Time.fromMinutes(70) : Time.fromMinutes(150);
|
|
930
|
+
},
|
|
931
|
+
} as unknown as Route;
|
|
932
|
+
|
|
933
|
+
const mockTimetable = {
|
|
934
|
+
getRoute: (routeId: number) =>
|
|
935
|
+
routeId === 0 ? mockFromRoute : mockToRoute,
|
|
936
|
+
} as unknown as Timetable;
|
|
937
|
+
|
|
938
|
+
const activeStopIds = new Set([100, 200]);
|
|
939
|
+
|
|
940
|
+
const result = buildTripContinuations(
|
|
941
|
+
tripsMapping,
|
|
942
|
+
tripContinuations,
|
|
943
|
+
mockTimetable,
|
|
944
|
+
activeStopIds,
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
// Should pick the best timing: arrive at stop 0 (1:00) -> depart from stop 1 (1:10)
|
|
948
|
+
// This is better than arrive at stop 3 (2:00) -> depart from stop 4 (2:30)
|
|
949
|
+
const expectedTripBoardingId = encode(0, 0, 0); // stopIndex=0, routeId=0, tripIndex=0
|
|
950
|
+
const continuations = result.get(expectedTripBoardingId);
|
|
951
|
+
|
|
952
|
+
assert(continuations);
|
|
953
|
+
assert.strictEqual(continuations.length, 1);
|
|
954
|
+
assert.deepEqual(continuations[0], {
|
|
955
|
+
hopOnStopIndex: 1, // Best to-stop index
|
|
956
|
+
routeId: 1,
|
|
957
|
+
tripIndex: 0,
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should handle case where no valid transfer timing exists between duplicate stops', () => {
|
|
962
|
+
const tripsMapping: TripsMapping = new Map([
|
|
963
|
+
['trip1', { routeId: 0, tripRouteIndex: 0 }],
|
|
964
|
+
['trip2', { routeId: 1, tripRouteIndex: 0 }],
|
|
965
|
+
]);
|
|
966
|
+
|
|
967
|
+
const tripContinuations = [
|
|
968
|
+
{
|
|
969
|
+
fromStop: 100,
|
|
970
|
+
fromTrip: 'trip1',
|
|
971
|
+
toStop: 200,
|
|
972
|
+
toTrip: 'trip2',
|
|
973
|
+
},
|
|
974
|
+
];
|
|
975
|
+
|
|
976
|
+
// Mock route where all arrivals are AFTER all departures (impossible transfer)
|
|
977
|
+
const mockFromRoute = {
|
|
978
|
+
stopRouteIndices: () => [0, 3], // Stop 100 appears twice
|
|
979
|
+
arrivalAt: (stopIndex: number) => {
|
|
980
|
+
// Both arrivals are late: 2:00 and 3:00
|
|
981
|
+
return stopIndex === 0 ? Time.fromMinutes(120) : Time.fromMinutes(180);
|
|
982
|
+
},
|
|
983
|
+
} as unknown as Route;
|
|
984
|
+
|
|
985
|
+
const mockToRoute = {
|
|
986
|
+
stopRouteIndices: () => [1, 4], // Stop 200 appears twice
|
|
987
|
+
departureFrom: (stopIndex: number) => {
|
|
988
|
+
// Both departures are early: 1:00 and 1:30
|
|
989
|
+
return stopIndex === 1 ? Time.fromMinutes(60) : Time.fromMinutes(90);
|
|
990
|
+
},
|
|
991
|
+
} as unknown as Route;
|
|
992
|
+
|
|
993
|
+
const mockTimetable = {
|
|
994
|
+
getRoute: (routeId: number) =>
|
|
995
|
+
routeId === 0 ? mockFromRoute : mockToRoute,
|
|
996
|
+
} as unknown as Timetable;
|
|
997
|
+
|
|
998
|
+
const activeStopIds = new Set([100, 200]);
|
|
999
|
+
|
|
1000
|
+
const result = buildTripContinuations(
|
|
1001
|
+
tripsMapping,
|
|
1002
|
+
tripContinuations,
|
|
1003
|
+
mockTimetable,
|
|
1004
|
+
activeStopIds,
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
// Should find no valid continuations since all departures are before arrivals
|
|
1008
|
+
assert.strictEqual(result.size, 0);
|
|
729
1009
|
});
|
|
730
1010
|
});
|