minotor 5.0.1 → 7.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +8 -3
  2. package/dist/cli.mjs +282 -654
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/gtfs/parser.d.ts +4 -10
  5. package/dist/gtfs/routes.d.ts +16 -2
  6. package/dist/gtfs/stops.d.ts +3 -13
  7. package/dist/gtfs/transfers.d.ts +2 -2
  8. package/dist/gtfs/trips.d.ts +12 -8
  9. package/dist/parser.cjs.js +257 -644
  10. package/dist/parser.cjs.js.map +1 -1
  11. package/dist/parser.esm.js +257 -644
  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.esm.js +1 -1
  16. package/dist/router.esm.js.map +1 -1
  17. package/dist/router.umd.js +1 -1
  18. package/dist/router.umd.js.map +1 -1
  19. package/dist/stops/io.d.ts +3 -3
  20. package/dist/stops/proto/stops.d.ts +1 -8
  21. package/dist/stops/stops.d.ts +0 -4
  22. package/dist/stops/stopsIndex.d.ts +3 -3
  23. package/dist/timetable/io.d.ts +6 -6
  24. package/dist/timetable/proto/timetable.d.ts +5 -27
  25. package/dist/timetable/route.d.ts +2 -11
  26. package/dist/timetable/timetable.d.ts +17 -9
  27. package/package.json +1 -1
  28. package/src/__e2e__/timetable/stops.bin +2 -2
  29. package/src/__e2e__/timetable/timetable.bin +2 -2
  30. package/src/cli/minotor.ts +3 -4
  31. package/src/gtfs/__tests__/parser.test.ts +5 -6
  32. package/src/gtfs/__tests__/routes.test.ts +0 -3
  33. package/src/gtfs/__tests__/stops.test.ts +1 -124
  34. package/src/gtfs/__tests__/transfers.test.ts +7 -7
  35. package/src/gtfs/__tests__/trips.test.ts +74 -45
  36. package/src/gtfs/parser.ts +32 -49
  37. package/src/gtfs/routes.ts +43 -5
  38. package/src/gtfs/stops.ts +2 -44
  39. package/src/gtfs/transfers.ts +2 -2
  40. package/src/gtfs/trips.ts +66 -43
  41. package/src/routing/__tests__/result.test.ts +48 -48
  42. package/src/routing/__tests__/router.test.ts +279 -363
  43. package/src/routing/router.ts +22 -8
  44. package/src/stops/__tests__/io.test.ts +25 -31
  45. package/src/stops/__tests__/stopFinder.test.ts +82 -103
  46. package/src/stops/io.ts +8 -17
  47. package/src/stops/proto/stops.proto +3 -3
  48. package/src/stops/proto/stops.ts +16 -120
  49. package/src/stops/stops.ts +0 -4
  50. package/src/stops/stopsIndex.ts +37 -41
  51. package/src/timetable/__tests__/io.test.ts +44 -54
  52. package/src/timetable/__tests__/route.test.ts +10 -29
  53. package/src/timetable/__tests__/timetable.test.ts +29 -37
  54. package/src/timetable/io.ts +66 -74
  55. package/src/timetable/proto/timetable.proto +7 -14
  56. package/src/timetable/proto/timetable.ts +49 -391
  57. package/src/timetable/route.ts +2 -32
  58. package/src/timetable/timetable.ts +51 -31
@@ -5,9 +5,10 @@ import { describe, it } from 'node:test';
5
5
  import { StopId } from '../../stops/stops.js';
6
6
  import { REGULAR, Route } from '../../timetable/route.js';
7
7
  import { Time } from '../../timetable/time.js';
8
- import { ServiceRoutesMap } from '../../timetable/timetable.js';
8
+ import { ServiceRoute } from '../../timetable/timetable.js';
9
+ import { GtfsRoutesMap } from '../routes.js';
9
10
  import { ServiceIds } from '../services.js';
10
- import { ParsedStopsMap } from '../stops.js';
11
+ import { GtfsStopsMap } from '../stops.js';
11
12
  import { TransfersMap } from '../transfers.js';
12
13
  import {
13
14
  buildStopsAdjacencyStructure,
@@ -25,21 +26,22 @@ describe('buildStopsAdjacencyStructure', () => {
25
26
  new Uint16Array(),
26
27
  new Uint8Array(),
27
28
  new Uint32Array([0, 1]),
28
- 'service1',
29
+ 0,
29
30
  ),
30
31
  ];
31
32
  const transfersMap: TransfersMap = new Map([
32
33
  [0, [{ destination: 1, type: 'RECOMMENDED' }]],
33
34
  ]);
34
- const serviceRoutes: ServiceRoutesMap = new Map([
35
- ['service1', { type: 'BUS', name: 'B1', routes: [] }],
36
- ]);
35
+ const serviceRoutes: ServiceRoute[] = [
36
+ { type: 'BUS', name: 'B1', routes: [] },
37
+ ];
37
38
 
38
39
  const stopsAdjacency = buildStopsAdjacencyStructure(
39
- validStops,
40
40
  serviceRoutes,
41
41
  routesAdjacency,
42
42
  transfersMap,
43
+ 2,
44
+ validStops,
43
45
  );
44
46
 
45
47
  assert.deepEqual(Array.from(stopsAdjacency.entries()), [
@@ -47,11 +49,23 @@ describe('buildStopsAdjacencyStructure', () => {
47
49
  0,
48
50
  {
49
51
  routes: [0],
52
+ transfers: [
53
+ {
54
+ destination: 1,
55
+ type: 'RECOMMENDED',
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ [
61
+ 1,
62
+ {
63
+ routes: [],
50
64
  transfers: [],
51
65
  },
52
66
  ],
53
67
  ]);
54
- assert.deepEqual(serviceRoutes.get('service1')?.routes, [0]);
68
+ assert.deepEqual(serviceRoutes[0]?.routes, [0]);
55
69
  });
56
70
 
57
71
  it('should ignore transfers to invalid stops', () => {
@@ -61,21 +75,22 @@ describe('buildStopsAdjacencyStructure', () => {
61
75
  new Uint16Array(),
62
76
  new Uint8Array(),
63
77
  new Uint32Array([0, 1]),
64
- 'service1',
78
+ 0,
65
79
  ),
66
80
  ];
67
81
  const transfersMap: TransfersMap = new Map([
68
- [0, [{ destination: 2, type: 'RECOMMENDED' }]],
69
- ]);
70
- const serviceRoutes: ServiceRoutesMap = new Map([
71
- ['service1', { type: 'BUS', name: 'B1', routes: [] }],
82
+ [3, [{ destination: 2, type: 'RECOMMENDED' }]],
72
83
  ]);
84
+ const serviceRoutes: ServiceRoute[] = [
85
+ { type: 'BUS', name: 'B1', routes: [] },
86
+ ];
73
87
 
74
88
  const stopsAdjacency = buildStopsAdjacencyStructure(
75
- validStops,
76
89
  serviceRoutes,
77
90
  routesAdjacency,
78
91
  transfersMap,
92
+ 4,
93
+ validStops,
79
94
  );
80
95
 
81
96
  assert.deepEqual(Array.from(stopsAdjacency.entries()), [
@@ -93,8 +108,22 @@ describe('buildStopsAdjacencyStructure', () => {
93
108
  transfers: [],
94
109
  },
95
110
  ],
111
+ [
112
+ 2,
113
+ {
114
+ routes: [],
115
+ transfers: [],
116
+ },
117
+ ],
118
+ [
119
+ 3,
120
+ {
121
+ routes: [],
122
+ transfers: [],
123
+ },
124
+ ],
96
125
  ]);
97
- assert.deepEqual(serviceRoutes.get('service1')?.routes, [0]);
126
+ assert.deepEqual(serviceRoutes[0]?.routes, [0]);
98
127
  });
99
128
  });
100
129
  describe('GTFS trips parser', () => {
@@ -106,9 +135,9 @@ describe('GTFS trips parser', () => {
106
135
  mockedStream.push(null);
107
136
 
108
137
  const validServiceIds: ServiceIds = new Set(['service1', 'service2']);
109
- const validRouteIds: ServiceRoutesMap = new Map([
110
- ['routeA', { type: 'BUS', name: 'B1', routes: [0] }],
111
- ['routeB', { type: 'TRAM', name: 'T1', routes: [1] }],
138
+ const validRouteIds: GtfsRoutesMap = new Map([
139
+ ['routeA', { type: 'BUS', name: 'B1' }],
140
+ ['routeB', { type: 'TRAM', name: 'T1' }],
112
141
  ]);
113
142
 
114
143
  const trips = await parseTrips(
@@ -133,9 +162,9 @@ describe('GTFS trips parser', () => {
133
162
  mockedStream.push(null);
134
163
 
135
164
  const validServiceIds: ServiceIds = new Set(['service1', 'service2']);
136
- const validRouteIds: ServiceRoutesMap = new Map([
137
- ['routeA', { type: 'BUS', name: 'B1', routes: [0] }],
138
- ['routeB', { type: 'TRAM', name: 'T1', routes: [1] }],
165
+ const validRouteIds: GtfsRoutesMap = new Map([
166
+ ['routeA', { type: 'BUS', name: 'B1' }],
167
+ ['routeB', { type: 'TRAM', name: 'T1' }],
139
168
  ]);
140
169
 
141
170
  const trips = await parseTrips(
@@ -154,9 +183,9 @@ describe('GTFS trips parser', () => {
154
183
  mockedStream.push(null);
155
184
 
156
185
  const validServiceIds: ServiceIds = new Set(['service1', 'service2']);
157
- const validRouteIds: ServiceRoutesMap = new Map([
158
- ['routeA', { type: 'BUS', name: 'B1', routes: [0] }],
159
- ['routeB', { type: 'TRAM', name: 'T1', routes: [1] }],
186
+ const validRouteIds: GtfsRoutesMap = new Map([
187
+ ['routeA', { type: 'BUS', name: 'B1' }],
188
+ ['routeB', { type: 'TRAM', name: 'T1' }],
160
189
  ]);
161
190
 
162
191
  const trips = await parseTrips(
@@ -180,7 +209,7 @@ describe('GTFS stop times parser', () => {
180
209
 
181
210
  const validTripIds: TripIdsMap = new Map([['tripA', 'routeA']]);
182
211
  const validStopIds: Set<StopId> = new Set([0, 1]);
183
- const stopsMap: ParsedStopsMap = new Map([
212
+ const stopsMap: GtfsStopsMap = new Map([
184
213
  [
185
214
  'stop1',
186
215
  {
@@ -206,13 +235,13 @@ describe('GTFS stop times parser', () => {
206
235
  },
207
236
  ],
208
237
  ]);
209
- const routes = await parseStopTimes(
238
+ const result = await parseStopTimes(
210
239
  mockedStream,
211
240
  stopsMap,
212
241
  validTripIds,
213
242
  validStopIds,
214
243
  );
215
- assert.deepEqual(routes, [
244
+ assert.deepEqual(result.routes, [
216
245
  new Route(
217
246
  new Uint16Array([
218
247
  Time.fromHMS(8, 0, 0).toMinutes(),
@@ -222,7 +251,7 @@ describe('GTFS stop times parser', () => {
222
251
  ]),
223
252
  encodePickUpDropOffTypes([REGULAR, REGULAR], [REGULAR, REGULAR]),
224
253
  new Uint32Array([0, 1]),
225
- 'routeA',
254
+ 0,
226
255
  ),
227
256
  ]);
228
257
  });
@@ -243,7 +272,7 @@ describe('GTFS stop times parser', () => {
243
272
  ['tripB', 'routeA'],
244
273
  ]);
245
274
  const validStopIds: Set<StopId> = new Set([0, 1]);
246
- const stopsMap: ParsedStopsMap = new Map([
275
+ const stopsMap: GtfsStopsMap = new Map([
247
276
  [
248
277
  'stop1',
249
278
  {
@@ -266,13 +295,13 @@ describe('GTFS stop times parser', () => {
266
295
  ],
267
296
  ]);
268
297
 
269
- const routes = await parseStopTimes(
298
+ const result = await parseStopTimes(
270
299
  mockedStream,
271
300
  stopsMap,
272
301
  validTripIds,
273
302
  validStopIds,
274
303
  );
275
- assert.deepEqual(routes, [
304
+ assert.deepEqual(result.routes, [
276
305
  new Route(
277
306
  new Uint16Array([
278
307
  Time.fromHMS(8, 0, 0).toMinutes(),
@@ -289,7 +318,7 @@ describe('GTFS stop times parser', () => {
289
318
  [REGULAR, REGULAR, REGULAR, REGULAR],
290
319
  ),
291
320
  new Uint32Array([0, 1]),
292
- 'routeA',
321
+ 0,
293
322
  ),
294
323
  ]);
295
324
  });
@@ -310,7 +339,7 @@ describe('GTFS stop times parser', () => {
310
339
  ['tripB', 'routeA'],
311
340
  ]);
312
341
  const validStopIds: Set<StopId> = new Set([0, 1]);
313
- const stopsMap: ParsedStopsMap = new Map([
342
+ const stopsMap: GtfsStopsMap = new Map([
314
343
  [
315
344
  'stop1',
316
345
  {
@@ -333,13 +362,13 @@ describe('GTFS stop times parser', () => {
333
362
  ],
334
363
  ]);
335
364
 
336
- const routes = await parseStopTimes(
365
+ const result = await parseStopTimes(
337
366
  mockedStream,
338
367
  stopsMap,
339
368
  validTripIds,
340
369
  validStopIds,
341
370
  );
342
- assert.deepEqual(routes, [
371
+ assert.deepEqual(result.routes, [
343
372
  new Route(
344
373
  new Uint16Array([
345
374
  Time.fromHMS(8, 0, 0).toMinutes(),
@@ -356,7 +385,7 @@ describe('GTFS stop times parser', () => {
356
385
  [REGULAR, REGULAR, REGULAR, REGULAR],
357
386
  ),
358
387
  new Uint32Array([0, 1]),
359
- 'routeA',
388
+ 0,
360
389
  ),
361
390
  ]);
362
391
  });
@@ -376,7 +405,7 @@ describe('GTFS stop times parser', () => {
376
405
  ['tripB', 'routeA'],
377
406
  ]);
378
407
  const validStopIds: Set<StopId> = new Set([0, 1]);
379
- const stopsMap: ParsedStopsMap = new Map([
408
+ const stopsMap: GtfsStopsMap = new Map([
380
409
  [
381
410
  'stop1',
382
411
  {
@@ -399,13 +428,13 @@ describe('GTFS stop times parser', () => {
399
428
  ],
400
429
  ]);
401
430
 
402
- const routes = await parseStopTimes(
431
+ const result = await parseStopTimes(
403
432
  mockedStream,
404
433
  stopsMap,
405
434
  validTripIds,
406
435
  validStopIds,
407
436
  );
408
- assert.deepEqual(routes, [
437
+ assert.deepEqual(result.routes, [
409
438
  new Route(
410
439
  new Uint16Array([
411
440
  Time.fromHMS(8, 0, 0).toMinutes(),
@@ -415,7 +444,7 @@ describe('GTFS stop times parser', () => {
415
444
  ]),
416
445
  encodePickUpDropOffTypes([REGULAR, REGULAR], [REGULAR, REGULAR]),
417
446
  new Uint32Array([0, 1]),
418
- 'routeA',
447
+ 0,
419
448
  ),
420
449
  new Route(
421
450
  new Uint16Array([
@@ -424,7 +453,7 @@ describe('GTFS stop times parser', () => {
424
453
  ]),
425
454
  encodePickUpDropOffTypes([REGULAR], [REGULAR]),
426
455
  new Uint32Array([0]),
427
- 'routeA',
456
+ 0,
428
457
  ),
429
458
  ]);
430
459
  });
@@ -440,7 +469,7 @@ describe('GTFS stop times parser', () => {
440
469
 
441
470
  const validTripIds: TripIdsMap = new Map([['tripA', 'routeA']]);
442
471
  const validStopIds: Set<StopId> = new Set([0, 1]);
443
- const stopsMap: ParsedStopsMap = new Map([
472
+ const stopsMap: GtfsStopsMap = new Map([
444
473
  [
445
474
  'stop1',
446
475
  {
@@ -463,13 +492,13 @@ describe('GTFS stop times parser', () => {
463
492
  ],
464
493
  ]);
465
494
 
466
- const routes = await parseStopTimes(
495
+ const result = await parseStopTimes(
467
496
  mockedStream,
468
497
  stopsMap,
469
498
  validTripIds,
470
499
  validStopIds,
471
500
  );
472
- assert.deepEqual(routes, [
501
+ assert.deepEqual(result.routes, [
473
502
  new Route(
474
503
  new Uint16Array([
475
504
  Time.fromHMS(8, 0, 0).toMinutes(),
@@ -477,7 +506,7 @@ describe('GTFS stop times parser', () => {
477
506
  ]),
478
507
  encodePickUpDropOffTypes([REGULAR], [REGULAR]),
479
508
  new Uint32Array([0]),
480
- 'routeA',
509
+ 0,
481
510
  ),
482
511
  ]);
483
512
  });
@@ -6,9 +6,9 @@ import { 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
- import { parseRoutes } from './routes.js';
9
+ import { indexRoutes, parseRoutes } from './routes.js';
10
10
  import { parseCalendar, parseCalendarDates, ServiceIds } from './services.js';
11
- import { indexStops, parseStops } from './stops.js';
11
+ import { parseStops } from './stops.js';
12
12
  import { parseTransfers, TransfersMap } from './transfers.js';
13
13
  import {
14
14
  buildStopsAdjacencyStructure,
@@ -43,20 +43,16 @@ export class GtfsParser {
43
43
  * Parses a GTFS feed to extract all the data relevant to a given day in a transit-planner friendly format.
44
44
  *
45
45
  * @param date The active date.
46
- * @param gtfsPath A path to the zipped GTFS feed.
47
- * @param gtfsProfile The GTFS profile configuration.
48
- * @returns An object containing the timetable and stops map.
46
+ * @returns The parsed timetable.
49
47
  */
50
- async parse(
51
- date: Date,
52
- ): Promise<{ timetable: Timetable; stopsIndex: StopsIndex }> {
48
+ async parseTimetable(date: Date): Promise<Timetable> {
53
49
  log.setLevel('INFO');
54
50
  const zip = new StreamZip.async({ file: this.path });
55
51
  const entries = await zip.entries();
56
52
  const datetime = DateTime.fromJSDate(date);
57
53
 
58
- const validServiceIds: ServiceIds = new Set();
59
- const validStopIds = new Set<StopId>();
54
+ const activeServiceIds: ServiceIds = new Set();
55
+ const activeStopIds = new Set<StopId>();
60
56
 
61
57
  log.info(`Parsing ${STOPS_FILE}`);
62
58
  const stopsStart = performance.now();
@@ -71,10 +67,10 @@ export class GtfsParser {
71
67
  log.info(`Parsing ${CALENDAR_FILE}`);
72
68
  const calendarStart = performance.now();
73
69
  const calendarStream = await zip.stream(CALENDAR_FILE);
74
- await parseCalendar(calendarStream, validServiceIds, datetime);
70
+ await parseCalendar(calendarStream, activeServiceIds, datetime);
75
71
  const calendarEnd = performance.now();
76
72
  log.info(
77
- `${validServiceIds.size} valid services. (${(calendarEnd - calendarStart).toFixed(2)}ms)`,
73
+ `${activeServiceIds.size} valid services. (${(calendarEnd - calendarStart).toFixed(2)}ms)`,
78
74
  );
79
75
  }
80
76
 
@@ -82,10 +78,10 @@ export class GtfsParser {
82
78
  log.info(`Parsing ${CALENDAR_DATES_FILE}`);
83
79
  const calendarDatesStart = performance.now();
84
80
  const calendarDatesStream = await zip.stream(CALENDAR_DATES_FILE);
85
- await parseCalendarDates(calendarDatesStream, validServiceIds, datetime);
81
+ await parseCalendarDates(calendarDatesStream, activeServiceIds, datetime);
86
82
  const calendarDatesEnd = performance.now();
87
83
  log.info(
88
- `${validServiceIds.size} valid services. (${(calendarDatesEnd - calendarDatesStart).toFixed(2)}ms)`,
84
+ `${activeServiceIds.size} valid services. (${(calendarDatesEnd - calendarDatesStart).toFixed(2)}ms)`,
89
85
  );
90
86
  }
91
87
 
@@ -103,7 +99,7 @@ export class GtfsParser {
103
99
  const tripsStream = await zip.stream(TRIPS_FILE);
104
100
  const trips = await parseTrips(
105
101
  tripsStream,
106
- validServiceIds,
102
+ activeServiceIds,
107
103
  validGtfsRoutes,
108
104
  );
109
105
  const tripsEnd = performance.now();
@@ -126,57 +122,44 @@ export class GtfsParser {
126
122
  log.info(`Parsing ${STOP_TIMES_FILE}`);
127
123
  const stopTimesStart = performance.now();
128
124
  const stopTimesStream = await zip.stream(STOP_TIMES_FILE);
129
- const routesAdjacency = await parseStopTimes(
125
+ const { routes, serviceRoutesMap } = await parseStopTimes(
130
126
  stopTimesStream,
131
127
  parsedStops,
132
128
  trips,
133
- validStopIds,
134
- );
135
- const stopsAdjacency = buildStopsAdjacencyStructure(
136
- validStopIds,
137
- validGtfsRoutes,
138
- routesAdjacency,
139
- transfers,
129
+ activeStopIds,
140
130
  );
131
+ const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
141
132
  const stopTimesEnd = performance.now();
142
133
  log.info(
143
- `${routesAdjacency.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`,
134
+ `${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`,
135
+ );
136
+ log.info('Building stops adjacency structure');
137
+ const stopsAdjacencyStart = performance.now();
138
+ const stopsAdjacency = buildStopsAdjacencyStructure(
139
+ serviceRoutes,
140
+ routes,
141
+ transfers,
142
+ parsedStops.size,
143
+ activeStopIds,
144
144
  );
145
145
 
146
- log.info(`Removing unused stops.`);
147
- const indexStopsStart = performance.now();
148
- const stops = indexStops(parsedStops, validStopIds);
149
- const indexStopsEnd = performance.now();
146
+ const stopsAdjacencyEnd = performance.now();
150
147
  log.info(
151
- `${stops.size} used stop stops, ${parsedStops.size - stops.size} unused. (${(indexStopsEnd - indexStopsStart).toFixed(2)}ms)`,
148
+ `${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`,
152
149
  );
153
-
154
150
  await zip.close();
155
151
 
156
- const timetable = new Timetable(
157
- stopsAdjacency,
158
- routesAdjacency,
159
- validGtfsRoutes,
160
- );
161
-
162
- log.info(`Building stops index.`);
163
- const stopsIndexStart = performance.now();
164
- const stopsIndex = new StopsIndex(stops);
165
- const stopsIndexEnd = performance.now();
166
- log.info(
167
- `Stops index built. (${(stopsIndexEnd - stopsIndexStart).toFixed(2)}ms)`,
168
- );
152
+ const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
169
153
 
170
154
  log.info('Parsing complete.');
171
- return { timetable, stopsIndex };
155
+ return timetable;
172
156
  }
173
157
 
174
158
  /**
175
159
  * Parses a GTFS feed to extract all stops.
176
160
  *
177
- * @param gtfsPath A path the zipped GTFS feed.
178
- * @param gtfsProfile The GTFS profile configuration.
179
- * @returns An object containing the timetable and stops map.
161
+ * @param activeStops The set of active stop IDs to include in the index.
162
+ * @returns An index of stops.
180
163
  */
181
164
  async parseStops(): Promise<StopsIndex> {
182
165
  const zip = new StreamZip.async({ file: this.path });
@@ -184,7 +167,7 @@ export class GtfsParser {
184
167
  log.info(`Parsing ${STOPS_FILE}`);
185
168
  const stopsStart = performance.now();
186
169
  const stopsStream = await zip.stream(STOPS_FILE);
187
- const stops = indexStops(await parseStops(stopsStream));
170
+ const stops = await parseStops(stopsStream);
188
171
  const stopsEnd = performance.now();
189
172
 
190
173
  log.info(
@@ -193,6 +176,6 @@ export class GtfsParser {
193
176
 
194
177
  await zip.close();
195
178
 
196
- return new StopsIndex(stops);
179
+ return new StopsIndex(Array.from(stops.values()));
197
180
  }
198
181
  }
@@ -1,6 +1,7 @@
1
1
  import log from 'loglevel';
2
2
 
3
- import { ServiceRouteId, ServiceRoutesMap } from '../timetable/timetable.js';
3
+ import { RouteType } from '../router.js';
4
+ import { ServiceRoute, ServiceRouteId } from '../timetable/timetable.js';
4
5
  import { GtfsProfile } from './parser.js';
5
6
  import { standardProfile } from './profiles/standard.js';
6
7
  import { parseCsv } from './utils.js';
@@ -8,9 +9,18 @@ import { parseCsv } from './utils.js';
8
9
  // Can be a standard gtfs route type or an extended route type
9
10
  // the profile converter handles the conversion to a route type.
10
11
  export type GtfsRouteType = number;
12
+ export type GtfsRouteId = string;
13
+
14
+ export type GtfsRoutesMap = Map<
15
+ GtfsRouteId,
16
+ {
17
+ name: string;
18
+ type: RouteType;
19
+ }
20
+ >;
11
21
 
12
22
  type RouteEntry = {
13
- route_id: ServiceRouteId;
23
+ route_id: GtfsRouteId;
14
24
  agency_id: string;
15
25
  route_short_name: string;
16
26
  route_long_name: string;
@@ -28,8 +38,8 @@ type RouteEntry = {
28
38
  export const parseRoutes = async (
29
39
  routesStream: NodeJS.ReadableStream,
30
40
  profile: GtfsProfile = standardProfile,
31
- ): Promise<ServiceRoutesMap> => {
32
- const routes: ServiceRoutesMap = new Map();
41
+ ): Promise<GtfsRoutesMap> => {
42
+ const routes: GtfsRoutesMap = new Map();
33
43
  for await (const rawLine of parseCsv(routesStream, ['route_type'])) {
34
44
  const line = rawLine as RouteEntry;
35
45
  const routeType = profile.routeTypeParser(line.route_type);
@@ -42,8 +52,36 @@ export const parseRoutes = async (
42
52
  routes.set(line.route_id, {
43
53
  name: line.route_short_name,
44
54
  type: routeType,
45
- routes: [],
46
55
  });
47
56
  }
48
57
  return routes;
49
58
  };
59
+
60
+ /**
61
+ * Creates an array of ServiceRoute objects by combining GTFS route data with service route mappings.
62
+ *
63
+ * @param gtfsRoutesMap A map containing GTFS route information indexed by route ID
64
+ * @param serviceRoutesMap A map linking GTFS route IDs to service route IDs
65
+ * @returns An array of ServiceRoute objects with route information
66
+ */
67
+ export const indexRoutes = (
68
+ gtfsRoutesMap: GtfsRoutesMap,
69
+ serviceRoutesMap: Map<GtfsRouteId, ServiceRouteId>,
70
+ ): ServiceRoute[] => {
71
+ const serviceRoutes: ServiceRoute[] = new Array<ServiceRoute>(
72
+ serviceRoutesMap.size,
73
+ );
74
+ for (const [gtfsRouteId, serviceRouteId] of serviceRoutesMap) {
75
+ const route = gtfsRoutesMap.get(gtfsRouteId);
76
+ if (route === undefined) {
77
+ log.warn(`Route ${gtfsRouteId} not found.`);
78
+ continue;
79
+ }
80
+ serviceRoutes[serviceRouteId] = {
81
+ name: route.name,
82
+ type: route.type,
83
+ routes: [],
84
+ };
85
+ }
86
+ return serviceRoutes;
87
+ };
package/src/gtfs/stops.ts CHANGED
@@ -5,8 +5,6 @@ import {
5
5
  Platform,
6
6
  SourceStopId,
7
7
  Stop,
8
- StopId,
9
- StopsMap,
10
8
  } from '../stops/stops.js';
11
9
  import { parseCsv } from './utils.js';
12
10
 
@@ -31,7 +29,7 @@ type ParsedStop = Stop & {
31
29
  parentSourceId?: SourceStopId;
32
30
  };
33
31
 
34
- export type ParsedStopsMap = Map<SourceStopId, ParsedStop>;
32
+ export type GtfsStopsMap = Map<SourceStopId, ParsedStop>;
35
33
  /**
36
34
  * Parses the stops.txt file from a GTFS feed.
37
35
  *
@@ -40,7 +38,7 @@ export type ParsedStopsMap = Map<SourceStopId, ParsedStop>;
40
38
  */
41
39
  export const parseStops = async (
42
40
  stopsStream: NodeJS.ReadableStream,
43
- ): Promise<ParsedStopsMap> => {
41
+ ): Promise<GtfsStopsMap> => {
44
42
  const parsedStops = new Map<SourceStopId, ParsedStop>();
45
43
  let i = 0;
46
44
  for await (const rawLine of parseCsv(stopsStream, [
@@ -82,46 +80,6 @@ export const parseStops = async (
82
80
  return parsedStops;
83
81
  };
84
82
 
85
- /**
86
- * Builds the final stop map indexed by internal IDs.
87
- * Excludes all stops that do not have at least one valid stopId
88
- * as a child, a parent, or being valid itself.
89
- *
90
- * @param parsedStops - The map of parsed stops.
91
- * @param validStops - A set of valid stop IDs.
92
- * @returns A map of stops indexed by internal IDs.
93
- */
94
- export const indexStops = (
95
- parsedStops: ParsedStopsMap,
96
- validStops?: Set<StopId>,
97
- ): StopsMap => {
98
- const stops = new Map<StopId, Stop>();
99
-
100
- for (const [, stop] of parsedStops) {
101
- if (
102
- !validStops ||
103
- validStops.has(stop.id) ||
104
- (stop.parent && validStops.has(stop.parent)) ||
105
- stop.children.some((childId) => validStops.has(childId))
106
- ) {
107
- stops.set(stop.id, {
108
- id: stop.id,
109
- sourceStopId: stop.sourceStopId,
110
- name: stop.name,
111
- lat: stop.lat,
112
- lon: stop.lon,
113
- locationType: stop.locationType,
114
- platform: stop.platform,
115
- children: stop.children.filter(
116
- (childId) => !validStops || validStops.has(childId),
117
- ),
118
- parent: stop.parent,
119
- });
120
- }
121
- }
122
- return stops;
123
- };
124
-
125
83
  const parseGtfsLocationType = (
126
84
  gtfsLocationType: GtfsLocationType,
127
85
  ): LocationType => {
@@ -5,7 +5,7 @@ import {
5
5
  Transfer,
6
6
  TransferType,
7
7
  } from '../timetable/timetable.js';
8
- import { ParsedStopsMap } from './stops.js';
8
+ import { GtfsStopsMap } from './stops.js';
9
9
  import { TripId } from './trips.js';
10
10
  import { parseCsv } from './utils.js';
11
11
 
@@ -38,7 +38,7 @@ export type TransferEntry = {
38
38
  */
39
39
  export const parseTransfers = async (
40
40
  transfersStream: NodeJS.ReadableStream,
41
- stopsMap: ParsedStopsMap,
41
+ stopsMap: GtfsStopsMap,
42
42
  ): Promise<TransfersMap> => {
43
43
  const transfers: TransfersMap = new Map();
44
44