minotor 1.0.7 → 2.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +3 -2
  3. package/dist/cli.mjs +604 -531
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/gtfs/stops.d.ts +19 -5
  6. package/dist/gtfs/transfers.d.ts +5 -4
  7. package/dist/gtfs/trips.d.ts +7 -5
  8. package/dist/gtfs/utils.d.ts +7 -8
  9. package/dist/parser.cjs.js +569 -501
  10. package/dist/parser.cjs.js.map +1 -1
  11. package/dist/parser.esm.js +569 -501
  12. package/dist/parser.esm.js.map +1 -1
  13. package/dist/router.cjs.js +1 -1
  14. package/dist/router.cjs.js.map +1 -1
  15. package/dist/router.d.ts +3 -3
  16. package/dist/router.esm.js +1 -1
  17. package/dist/router.esm.js.map +1 -1
  18. package/dist/router.umd.js +1 -1
  19. package/dist/router.umd.js.map +1 -1
  20. package/dist/routing/__tests__/route.test.d.ts +1 -0
  21. package/dist/routing/query.d.ts +7 -7
  22. package/dist/routing/result.d.ts +3 -3
  23. package/dist/routing/route.d.ts +1 -0
  24. package/dist/stops/proto/stops.d.ts +5 -4
  25. package/dist/stops/stops.d.ts +10 -1
  26. package/dist/stops/stopsIndex.d.ts +21 -4
  27. package/dist/timetable/proto/timetable.d.ts +21 -18
  28. package/dist/timetable/timetable.d.ts +38 -14
  29. package/package.json +4 -3
  30. package/src/cli/repl.ts +13 -10
  31. package/src/gtfs/__tests__/parser.test.ts +50 -579
  32. package/src/gtfs/__tests__/stops.test.ts +181 -112
  33. package/src/gtfs/__tests__/transfers.test.ts +170 -12
  34. package/src/gtfs/__tests__/trips.test.ts +212 -141
  35. package/src/gtfs/__tests__/utils.test.ts +4 -4
  36. package/src/gtfs/parser.ts +22 -13
  37. package/src/gtfs/stops.ts +63 -28
  38. package/src/gtfs/transfers.ts +14 -6
  39. package/src/gtfs/trips.ts +110 -47
  40. package/src/gtfs/utils.ts +11 -11
  41. package/src/router.ts +2 -3
  42. package/src/routing/__tests__/route.test.ts +112 -0
  43. package/src/routing/__tests__/router.test.ts +234 -244
  44. package/src/routing/query.ts +7 -7
  45. package/src/routing/result.ts +9 -6
  46. package/src/routing/route.ts +11 -0
  47. package/src/routing/router.ts +26 -24
  48. package/src/stops/__tests__/io.test.ts +9 -8
  49. package/src/stops/__tests__/stopFinder.test.ts +45 -36
  50. package/src/stops/io.ts +8 -5
  51. package/src/stops/proto/stops.proto +8 -7
  52. package/src/stops/proto/stops.ts +68 -38
  53. package/src/stops/stops.ts +13 -1
  54. package/src/stops/stopsIndex.ts +50 -7
  55. package/src/timetable/__tests__/io.test.ts +40 -49
  56. package/src/timetable/__tests__/timetable.test.ts +50 -58
  57. package/src/timetable/io.ts +69 -56
  58. package/src/timetable/proto/timetable.proto +22 -17
  59. package/src/timetable/proto/timetable.ts +94 -184
  60. package/src/timetable/timetable.ts +62 -29
@@ -2,13 +2,14 @@ import assert from 'node:assert';
2
2
  import { Readable } from 'node:stream';
3
3
  import { describe, it } from 'node:test';
4
4
 
5
+ import { StopId } from '../../stops/stops.js';
5
6
  import { Time } from '../../timetable/time.js';
6
7
  import {
7
8
  RoutesAdjacency,
8
9
  ServiceRoutesMap,
9
10
  } from '../../timetable/timetable.js';
10
11
  import { ServiceIds } from '../services.js';
11
- import { StopIds } from '../stops.js';
12
+ import { ParsedStopsMap } from '../stops.js';
12
13
  import { TransfersMap } from '../transfers.js';
13
14
  import {
14
15
  buildStopsAdjacencyStructure,
@@ -19,23 +20,24 @@ import {
19
20
 
20
21
  describe('buildStopsAdjacencyStructure', () => {
21
22
  it('should correctly build stops adjacency for valid routes and transfers', () => {
22
- const validStops: StopIds = new Set(['stop1']);
23
+ const validStops: Set<StopId> = new Set([0]);
23
24
  const routesAdjacency: RoutesAdjacency = new Map([
24
25
  [
25
26
  'routeA',
26
27
  {
27
28
  serviceRouteId: 'service1',
28
- stops: ['stop1', 'stop2'],
29
+ stops: new Uint32Array([0, 1]),
29
30
  stopIndices: new Map([
30
- ['stop1', 0],
31
- ['stop2', 1],
31
+ [0, 0],
32
+ [1, 1],
32
33
  ]),
33
- stopTimes: [],
34
+ stopTimes: new Uint32Array(),
35
+ pickUpDropOffTypes: new Uint8Array(),
34
36
  },
35
37
  ],
36
38
  ]);
37
39
  const transfersMap: TransfersMap = new Map([
38
- ['stop1', [{ destination: 'stop2', type: 'RECOMMENDED' }]],
40
+ [0, [{ destination: 1, type: 'RECOMMENDED' }]],
39
41
  ]);
40
42
 
41
43
  const stopsAdjacency = buildStopsAdjacencyStructure(
@@ -46,7 +48,7 @@ describe('buildStopsAdjacencyStructure', () => {
46
48
 
47
49
  assert.deepEqual(Array.from(stopsAdjacency.entries()), [
48
50
  [
49
- 'stop1',
51
+ 0,
50
52
  {
51
53
  routes: ['routeA'],
52
54
  transfers: [],
@@ -56,23 +58,24 @@ describe('buildStopsAdjacencyStructure', () => {
56
58
  });
57
59
 
58
60
  it('should ignore transfers to invalid stops', () => {
59
- const validStops: StopIds = new Set(['stop1', 'stop2']);
61
+ const validStops: Set<StopId> = new Set([0, 1]);
60
62
  const routesAdjacency: RoutesAdjacency = new Map([
61
63
  [
62
64
  'routeA',
63
65
  {
64
66
  serviceRouteId: 'service1',
65
- stops: ['stop1', 'stop2'],
67
+ stops: new Uint32Array([0, 1]),
66
68
  stopIndices: new Map([
67
- ['stop1', 0],
68
- ['stop2', 1],
69
+ [0, 0],
70
+ [1, 1],
69
71
  ]),
70
- stopTimes: [],
72
+ stopTimes: new Uint32Array(),
73
+ pickUpDropOffTypes: new Uint8Array(),
71
74
  },
72
75
  ],
73
76
  ]);
74
77
  const transfersMap: TransfersMap = new Map([
75
- ['stop1', [{ destination: 'stop3', type: 'RECOMMENDED' }]],
78
+ [0, [{ destination: 2, type: 'RECOMMENDED' }]],
76
79
  ]);
77
80
 
78
81
  const stopsAdjacency = buildStopsAdjacencyStructure(
@@ -83,14 +86,14 @@ describe('buildStopsAdjacencyStructure', () => {
83
86
 
84
87
  assert.deepEqual(Array.from(stopsAdjacency.entries()), [
85
88
  [
86
- 'stop1',
89
+ 0,
87
90
  {
88
91
  routes: ['routeA'],
89
92
  transfers: [],
90
93
  },
91
94
  ],
92
95
  [
93
- 'stop2',
96
+ 1,
94
97
  {
95
98
  routes: ['routeA'],
96
99
  transfers: [],
@@ -181,10 +184,36 @@ describe('GTFS stop times parser', () => {
181
184
  mockedStream.push(null);
182
185
 
183
186
  const validTripIds: TripIdsMap = new Map([['tripA', 'routeA']]);
184
- const validStopIds: StopIds = new Set(['stop1', 'stop2']);
185
-
187
+ const validStopIds: Set<StopId> = new Set([0, 1]);
188
+ const stopsMap: ParsedStopsMap = new Map([
189
+ [
190
+ 'stop1',
191
+ {
192
+ id: 0,
193
+ sourceStopId: 'stop1',
194
+ name: 'Stop 1',
195
+ children: [],
196
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
197
+ lat: 36.425288,
198
+ lon: -117.133162,
199
+ },
200
+ ],
201
+ [
202
+ 'stop2',
203
+ {
204
+ id: 1,
205
+ sourceStopId: 'stop2',
206
+ name: 'Stop 2',
207
+ children: [],
208
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
209
+ lat: 36.868446,
210
+ lon: -116.784582,
211
+ },
212
+ ],
213
+ ]);
186
214
  const routes = await parseStopTimes(
187
215
  mockedStream,
216
+ stopsMap,
188
217
  validTripIds,
189
218
  validStopIds,
190
219
  );
@@ -192,28 +221,21 @@ describe('GTFS stop times parser', () => {
192
221
  routes,
193
222
  new Map([
194
223
  [
195
- 'routeA_1e0e4u3',
224
+ 'routeA_1',
196
225
  {
197
226
  serviceRouteId: 'routeA',
198
- stops: ['stop1', 'stop2'],
227
+ stops: new Uint32Array([0, 1]),
199
228
  stopIndices: new Map([
200
- ['stop1', 0],
201
- ['stop2', 1],
229
+ [0, 0],
230
+ [1, 1],
202
231
  ]),
203
- stopTimes: [
204
- {
205
- arrival: Time.fromHMS(8, 0, 0),
206
- departure: Time.fromHMS(8, 5, 0),
207
- pickUpType: 'REGULAR',
208
- dropOffType: 'REGULAR',
209
- },
210
- {
211
- arrival: Time.fromHMS(8, 10, 0),
212
- departure: Time.fromHMS(8, 15, 0),
213
- pickUpType: 'REGULAR',
214
- dropOffType: 'REGULAR',
215
- },
216
- ],
232
+ stopTimes: new Uint32Array([
233
+ Time.fromHMS(8, 0, 0).toSeconds(),
234
+ Time.fromHMS(8, 5, 0).toSeconds(),
235
+ Time.fromHMS(8, 10, 0).toSeconds(),
236
+ Time.fromHMS(8, 15, 0).toSeconds(),
237
+ ]),
238
+ pickUpDropOffTypes: new Uint8Array([0, 0, 0, 0]),
217
239
  },
218
240
  ],
219
241
  ]),
@@ -235,10 +257,33 @@ describe('GTFS stop times parser', () => {
235
257
  ['tripA', 'routeA'],
236
258
  ['tripB', 'routeA'],
237
259
  ]);
238
- const validStopIds: StopIds = new Set(['stop1', 'stop2']);
260
+ const validStopIds: Set<StopId> = new Set([0, 1]);
261
+ const stopsMap: ParsedStopsMap = new Map([
262
+ [
263
+ 'stop1',
264
+ {
265
+ id: 0,
266
+ sourceStopId: 'stop1',
267
+ name: 'Stop 1',
268
+ children: [],
269
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
270
+ },
271
+ ],
272
+ [
273
+ 'stop2',
274
+ {
275
+ id: 1,
276
+ sourceStopId: 'stop2',
277
+ name: 'Stop 2',
278
+ children: [],
279
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
280
+ },
281
+ ],
282
+ ]);
239
283
 
240
284
  const routes = await parseStopTimes(
241
285
  mockedStream,
286
+ stopsMap,
242
287
  validTripIds,
243
288
  validStopIds,
244
289
  );
@@ -246,40 +291,25 @@ describe('GTFS stop times parser', () => {
246
291
  routes,
247
292
  new Map([
248
293
  [
249
- 'routeA_1e0e4u3',
294
+ 'routeA_1',
250
295
  {
251
296
  serviceRouteId: 'routeA',
252
- stops: ['stop1', 'stop2'],
297
+ stops: new Uint32Array([0, 1]),
253
298
  stopIndices: new Map([
254
- ['stop1', 0],
255
- ['stop2', 1],
299
+ [0, 0],
300
+ [1, 1],
256
301
  ]),
257
- stopTimes: [
258
- {
259
- arrival: Time.fromHMS(8, 0, 0),
260
- departure: Time.fromHMS(8, 5, 0),
261
- pickUpType: 'REGULAR',
262
- dropOffType: 'REGULAR',
263
- },
264
- {
265
- arrival: Time.fromHMS(8, 10, 0),
266
- departure: Time.fromHMS(8, 15, 0),
267
- pickUpType: 'REGULAR',
268
- dropOffType: 'REGULAR',
269
- },
270
- {
271
- arrival: Time.fromHMS(9, 0, 0),
272
- departure: Time.fromHMS(9, 5, 0),
273
- pickUpType: 'REGULAR',
274
- dropOffType: 'REGULAR',
275
- },
276
- {
277
- arrival: Time.fromHMS(9, 10, 0),
278
- departure: Time.fromHMS(9, 15, 0),
279
- pickUpType: 'REGULAR',
280
- dropOffType: 'REGULAR',
281
- },
282
- ],
302
+ stopTimes: new Uint32Array([
303
+ Time.fromHMS(8, 0, 0).toSeconds(),
304
+ Time.fromHMS(8, 5, 0).toSeconds(),
305
+ Time.fromHMS(8, 10, 0).toSeconds(),
306
+ Time.fromHMS(8, 15, 0).toSeconds(),
307
+ Time.fromHMS(9, 0, 0).toSeconds(),
308
+ Time.fromHMS(9, 5, 0).toSeconds(),
309
+ Time.fromHMS(9, 10, 0).toSeconds(),
310
+ Time.fromHMS(9, 15, 0).toSeconds(),
311
+ ]),
312
+ pickUpDropOffTypes: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]),
283
313
  },
284
314
  ],
285
315
  ]),
@@ -301,10 +331,33 @@ describe('GTFS stop times parser', () => {
301
331
  ['tripA', 'routeA'],
302
332
  ['tripB', 'routeA'],
303
333
  ]);
304
- const validStopIds: StopIds = new Set(['stop1', 'stop2']);
334
+ const validStopIds: Set<StopId> = new Set([0, 1]);
335
+ const stopsMap: ParsedStopsMap = new Map([
336
+ [
337
+ 'stop1',
338
+ {
339
+ id: 0,
340
+ sourceStopId: 'stop1',
341
+ name: 'Stop 1',
342
+ children: [],
343
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
344
+ },
345
+ ],
346
+ [
347
+ 'stop2',
348
+ {
349
+ id: 1,
350
+ sourceStopId: 'stop2',
351
+ name: 'Stop 2',
352
+ children: [],
353
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
354
+ },
355
+ ],
356
+ ]);
305
357
 
306
358
  const routes = await parseStopTimes(
307
359
  mockedStream,
360
+ stopsMap,
308
361
  validTripIds,
309
362
  validStopIds,
310
363
  );
@@ -312,40 +365,25 @@ describe('GTFS stop times parser', () => {
312
365
  routes,
313
366
  new Map([
314
367
  [
315
- 'routeA_1e0e4u3',
368
+ 'routeA_1',
316
369
  {
317
370
  serviceRouteId: 'routeA',
318
- stops: ['stop1', 'stop2'],
371
+ stops: new Uint32Array([0, 1]),
319
372
  stopIndices: new Map([
320
- ['stop1', 0],
321
- ['stop2', 1],
373
+ [0, 0],
374
+ [1, 1],
322
375
  ]),
323
- stopTimes: [
324
- {
325
- arrival: Time.fromHMS(8, 0, 0),
326
- departure: Time.fromHMS(8, 5, 0),
327
- pickUpType: 'REGULAR',
328
- dropOffType: 'REGULAR',
329
- },
330
- {
331
- arrival: Time.fromHMS(8, 10, 0),
332
- departure: Time.fromHMS(8, 15, 0),
333
- pickUpType: 'REGULAR',
334
- dropOffType: 'REGULAR',
335
- },
336
- {
337
- arrival: Time.fromHMS(9, 0, 0),
338
- departure: Time.fromHMS(9, 5, 0),
339
- pickUpType: 'REGULAR',
340
- dropOffType: 'REGULAR',
341
- },
342
- {
343
- arrival: Time.fromHMS(9, 10, 0),
344
- departure: Time.fromHMS(9, 15, 0),
345
- pickUpType: 'REGULAR',
346
- dropOffType: 'REGULAR',
347
- },
348
- ],
376
+ stopTimes: new Uint32Array([
377
+ Time.fromHMS(8, 0, 0).toSeconds(),
378
+ Time.fromHMS(8, 5, 0).toSeconds(),
379
+ Time.fromHMS(8, 10, 0).toSeconds(),
380
+ Time.fromHMS(8, 15, 0).toSeconds(),
381
+ Time.fromHMS(9, 0, 0).toSeconds(),
382
+ Time.fromHMS(9, 5, 0).toSeconds(),
383
+ Time.fromHMS(9, 10, 0).toSeconds(),
384
+ Time.fromHMS(9, 15, 0).toSeconds(),
385
+ ]),
386
+ pickUpDropOffTypes: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]),
349
387
  },
350
388
  ],
351
389
  ]),
@@ -366,10 +404,33 @@ describe('GTFS stop times parser', () => {
366
404
  ['tripA', 'routeA'],
367
405
  ['tripB', 'routeA'],
368
406
  ]);
369
- const validStopIds: StopIds = new Set(['stop1', 'stop2']);
407
+ const validStopIds: Set<StopId> = new Set([0, 1]);
408
+ const stopsMap: ParsedStopsMap = new Map([
409
+ [
410
+ 'stop1',
411
+ {
412
+ id: 0,
413
+ sourceStopId: 'stop1',
414
+ name: 'Stop 1',
415
+ children: [],
416
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
417
+ },
418
+ ],
419
+ [
420
+ 'stop2',
421
+ {
422
+ id: 1,
423
+ sourceStopId: 'stop2',
424
+ name: 'Stop 2',
425
+ children: [],
426
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
427
+ },
428
+ ],
429
+ ]);
370
430
 
371
431
  const routes = await parseStopTimes(
372
432
  mockedStream,
433
+ stopsMap,
373
434
  validTripIds,
374
435
  validStopIds,
375
436
  );
@@ -377,51 +438,41 @@ describe('GTFS stop times parser', () => {
377
438
  routes,
378
439
  new Map([
379
440
  [
380
- 'routeA_1e0e4u3',
441
+ 'routeA_1',
381
442
  {
382
443
  serviceRouteId: 'routeA',
383
- stops: ['stop1', 'stop2'],
444
+ stops: new Uint32Array([0, 1]),
384
445
  stopIndices: new Map([
385
- ['stop1', 0],
386
- ['stop2', 1],
446
+ [0, 0],
447
+ [1, 1],
387
448
  ]),
388
- stopTimes: [
389
- {
390
- arrival: Time.fromHMS(8, 0, 0),
391
- departure: Time.fromHMS(8, 5, 0),
392
- pickUpType: 'REGULAR',
393
- dropOffType: 'REGULAR',
394
- },
395
- {
396
- arrival: Time.fromHMS(8, 10, 0),
397
- departure: Time.fromHMS(8, 15, 0),
398
- pickUpType: 'REGULAR',
399
- dropOffType: 'REGULAR',
400
- },
401
- ],
449
+ stopTimes: new Uint32Array([
450
+ Time.fromHMS(8, 0, 0).toSeconds(),
451
+ Time.fromHMS(8, 5, 0).toSeconds(),
452
+ Time.fromHMS(8, 10, 0).toSeconds(),
453
+ Time.fromHMS(8, 15, 0).toSeconds(),
454
+ ]),
455
+ pickUpDropOffTypes: new Uint8Array([0, 0, 0, 0]),
402
456
  },
403
457
  ],
404
458
  [
405
- 'routeA_1tcrqn',
459
+ 'routeA_0',
406
460
  {
407
461
  serviceRouteId: 'routeA',
408
- stops: ['stop1'],
409
- stopIndices: new Map([['stop1', 0]]),
410
- stopTimes: [
411
- {
412
- arrival: Time.fromHMS(9, 0, 0),
413
- departure: Time.fromHMS(9, 15, 0),
414
- pickUpType: 'REGULAR',
415
- dropOffType: 'REGULAR',
416
- },
417
- ],
462
+ stops: new Uint32Array([0]),
463
+ stopIndices: new Map([[0, 0]]),
464
+ stopTimes: new Uint32Array([
465
+ Time.fromHMS(9, 0, 0).toSeconds(),
466
+ Time.fromHMS(9, 15, 0).toSeconds(),
467
+ ]),
468
+ pickUpDropOffTypes: new Uint8Array([0, 0]),
418
469
  },
419
470
  ],
420
471
  ]),
421
472
  );
422
473
  });
423
474
 
424
- it('should throw an error for non-increasing stop sequences', async () => {
475
+ it('should ignore non-increasing stop sequences', async () => {
425
476
  const mockedStream = new Readable();
426
477
  mockedStream.push(
427
478
  'trip_id,arrival_time,departure_time,stop_id,stop_sequence,pickup_type,drop_off_type\n',
@@ -431,10 +482,33 @@ describe('GTFS stop times parser', () => {
431
482
  mockedStream.push(null);
432
483
 
433
484
  const validTripIds: TripIdsMap = new Map([['tripA', 'routeA']]);
434
- const validStopIds: StopIds = new Set(['stop1', 'stop2']);
485
+ const validStopIds: Set<StopId> = new Set([0, 1]);
486
+ const stopsMap: ParsedStopsMap = new Map([
487
+ [
488
+ 'stop1',
489
+ {
490
+ id: 0,
491
+ sourceStopId: 'stop1',
492
+ name: 'Stop 1',
493
+ children: [],
494
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
495
+ },
496
+ ],
497
+ [
498
+ 'stop2',
499
+ {
500
+ id: 1,
501
+ sourceStopId: 'stop2',
502
+ name: 'Stop 2',
503
+ children: [],
504
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
505
+ },
506
+ ],
507
+ ]);
435
508
 
436
509
  const routes = await parseStopTimes(
437
510
  mockedStream,
511
+ stopsMap,
438
512
  validTripIds,
439
513
  validStopIds,
440
514
  );
@@ -442,19 +516,16 @@ describe('GTFS stop times parser', () => {
442
516
  routes,
443
517
  new Map([
444
518
  [
445
- 'routeA_1tcrqn',
519
+ 'routeA_0',
446
520
  {
447
521
  serviceRouteId: 'routeA',
448
- stops: ['stop1'],
449
- stopIndices: new Map([['stop1', 0]]),
450
- stopTimes: [
451
- {
452
- arrival: Time.fromHMS(8, 0, 0),
453
- departure: Time.fromHMS(8, 5, 0),
454
- pickUpType: 'REGULAR',
455
- dropOffType: 'REGULAR',
456
- },
457
- ],
522
+ stops: new Uint32Array([0]),
523
+ stopIndices: new Map([[0, 0]]),
524
+ stopTimes: new Uint32Array([
525
+ Time.fromHMS(8, 0, 0).toSeconds(),
526
+ Time.fromHMS(8, 5, 0).toSeconds(),
527
+ ]),
528
+ pickUpDropOffTypes: new Uint8Array([0, 0]),
458
529
  },
459
530
  ],
460
531
  ]),
@@ -1,13 +1,13 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
 
4
- import { hash } from '../utils.js';
4
+ import { hashIds } from '../utils.js';
5
5
 
6
- describe('Utility hash function', () => {
6
+ describe('Utility hashIds function', () => {
7
7
  it('should be consistent for a given input', () => {
8
- assert.equal(hash('stationA'), 'lswfbh');
8
+ assert.equal(hashIds([1, 2, 3]), hashIds([1, 2, 3]));
9
9
  });
10
10
  it('should not collide with different input', () => {
11
- assert.equal(hash('stationA') === hash('stationB'), false);
11
+ assert.notEqual(hashIds([1, 2, 3]), hashIds([4, 5, 6]));
12
12
  });
13
13
  });
@@ -2,13 +2,13 @@ import log from 'loglevel';
2
2
  import { DateTime } from 'luxon';
3
3
  import StreamZip from 'node-stream-zip';
4
4
 
5
- import { Platform } from '../stops/stops.js';
5
+ import { Platform, StopId } from '../stops/stops.js';
6
6
  import { StopsIndex } from '../stops/stopsIndex.js';
7
7
  import { RouteType, Timetable } from '../timetable/timetable.js';
8
8
  import { standardProfile } from './profiles/standard.js';
9
9
  import { parseRoutes } from './routes.js';
10
10
  import { parseCalendar, parseCalendarDates, ServiceIds } from './services.js';
11
- import { parseStops, StopEntry, StopIds } from './stops.js';
11
+ import { indexStops, parseStops, StopEntry } from './stops.js';
12
12
  import { parseTransfers, TransfersMap } from './transfers.js';
13
13
  import {
14
14
  buildStopsAdjacencyStructure,
@@ -56,7 +56,15 @@ export class GtfsParser {
56
56
  const datetime = DateTime.fromJSDate(date);
57
57
 
58
58
  const validServiceIds: ServiceIds = new Set();
59
- const validStopIds: StopIds = new Set();
59
+ const validStopIds = new Set<StopId>();
60
+
61
+ log.info(`Parsing ${STOPS_FILE}`);
62
+ const stopsStream = await zip.stream(STOPS_FILE);
63
+ const parsedStops = await parseStops(
64
+ stopsStream,
65
+ this.profile.platformParser,
66
+ );
67
+ log.info(`${parsedStops.size} parsed stops.`);
60
68
 
61
69
  if (entries[CALENDAR_FILE]) {
62
70
  log.info(`Parsing ${CALENDAR_FILE}`);
@@ -90,7 +98,7 @@ export class GtfsParser {
90
98
  if (entries[TRANSFERS_FILE]) {
91
99
  log.info(`Parsing ${TRANSFERS_FILE}`);
92
100
  const transfersStream = await zip.stream(TRANSFERS_FILE);
93
- transfers = await parseTransfers(transfersStream);
101
+ transfers = await parseTransfers(transfersStream, parsedStops);
94
102
  log.info(`${transfers.size} valid transfers.`);
95
103
  }
96
104
 
@@ -98,6 +106,7 @@ export class GtfsParser {
98
106
  const stopTimesStream = await zip.stream(STOP_TIMES_FILE);
99
107
  const routesAdjacency = await parseStopTimes(
100
108
  stopTimesStream,
109
+ parsedStops,
101
110
  trips,
102
111
  validStopIds,
103
112
  );
@@ -108,14 +117,11 @@ export class GtfsParser {
108
117
  );
109
118
  log.info(`${routesAdjacency.size} valid unique routes.`);
110
119
 
111
- log.info(`Parsing ${STOPS_FILE}`);
112
- const stopsStream = await zip.stream(STOPS_FILE);
113
- const stops = await parseStops(
114
- stopsStream,
115
- this.profile.platformParser,
116
- validStopIds,
120
+ log.info(`Removing unused stops.`);
121
+ const stops = indexStops(parsedStops, validStopIds);
122
+ log.info(
123
+ `${stops.size} used stop stops, ${parsedStops.size - stops.size} unused.`,
117
124
  );
118
- log.info(`${stops.size} valid stops.`);
119
125
 
120
126
  await zip.close();
121
127
 
@@ -144,8 +150,11 @@ export class GtfsParser {
144
150
 
145
151
  log.info(`Parsing ${STOPS_FILE}`);
146
152
  const stopsStream = await zip.stream(STOPS_FILE);
147
- const stops = await parseStops(stopsStream, this.profile.platformParser);
148
- log.info(`${stops.size} valid stops.`);
153
+ const stops = indexStops(
154
+ await parseStops(stopsStream, this.profile.platformParser),
155
+ );
156
+
157
+ log.info(`${stops.size} parsed stops.`);
149
158
 
150
159
  await zip.close();
151
160