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.
- package/CHANGELOG.md +8 -3
- package/dist/cli.mjs +282 -654
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/parser.d.ts +4 -10
- package/dist/gtfs/routes.d.ts +16 -2
- package/dist/gtfs/stops.d.ts +3 -13
- package/dist/gtfs/transfers.d.ts +2 -2
- package/dist/gtfs/trips.d.ts +12 -8
- package/dist/parser.cjs.js +257 -644
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +257 -644
- 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/stops/io.d.ts +3 -3
- package/dist/stops/proto/stops.d.ts +1 -8
- package/dist/stops/stops.d.ts +0 -4
- package/dist/stops/stopsIndex.d.ts +3 -3
- package/dist/timetable/io.d.ts +6 -6
- package/dist/timetable/proto/timetable.d.ts +5 -27
- package/dist/timetable/route.d.ts +2 -11
- package/dist/timetable/timetable.d.ts +17 -9
- package/package.json +1 -1
- package/src/__e2e__/timetable/stops.bin +2 -2
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/minotor.ts +3 -4
- package/src/gtfs/__tests__/parser.test.ts +5 -6
- package/src/gtfs/__tests__/routes.test.ts +0 -3
- package/src/gtfs/__tests__/stops.test.ts +1 -124
- package/src/gtfs/__tests__/transfers.test.ts +7 -7
- package/src/gtfs/__tests__/trips.test.ts +74 -45
- package/src/gtfs/parser.ts +32 -49
- package/src/gtfs/routes.ts +43 -5
- package/src/gtfs/stops.ts +2 -44
- package/src/gtfs/transfers.ts +2 -2
- package/src/gtfs/trips.ts +66 -43
- package/src/routing/__tests__/result.test.ts +48 -48
- package/src/routing/__tests__/router.test.ts +279 -363
- package/src/routing/router.ts +22 -8
- package/src/stops/__tests__/io.test.ts +25 -31
- package/src/stops/__tests__/stopFinder.test.ts +82 -103
- package/src/stops/io.ts +8 -17
- package/src/stops/proto/stops.proto +3 -3
- package/src/stops/proto/stops.ts +16 -120
- package/src/stops/stops.ts +0 -4
- package/src/stops/stopsIndex.ts +37 -41
- package/src/timetable/__tests__/io.test.ts +44 -54
- package/src/timetable/__tests__/route.test.ts +10 -29
- package/src/timetable/__tests__/timetable.test.ts +29 -37
- package/src/timetable/io.ts +66 -74
- package/src/timetable/proto/timetable.proto +7 -14
- package/src/timetable/proto/timetable.ts +49 -391
- package/src/timetable/route.ts +2 -32
- 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 {
|
|
8
|
+
import { ServiceRoute } from '../../timetable/timetable.js';
|
|
9
|
+
import { GtfsRoutesMap } from '../routes.js';
|
|
9
10
|
import { ServiceIds } from '../services.js';
|
|
10
|
-
import {
|
|
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
|
-
|
|
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:
|
|
35
|
-
|
|
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
|
|
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
|
-
|
|
78
|
+
0,
|
|
65
79
|
),
|
|
66
80
|
];
|
|
67
81
|
const transfersMap: TransfersMap = new Map([
|
|
68
|
-
[
|
|
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
|
|
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:
|
|
110
|
-
['routeA', { type: 'BUS', name: 'B1'
|
|
111
|
-
['routeB', { type: 'TRAM', name: 'T1'
|
|
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:
|
|
137
|
-
['routeA', { type: 'BUS', name: 'B1'
|
|
138
|
-
['routeB', { type: 'TRAM', name: 'T1'
|
|
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:
|
|
158
|
-
['routeA', { type: 'BUS', name: 'B1'
|
|
159
|
-
['routeB', { type: 'TRAM', name: 'T1'
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
509
|
+
0,
|
|
481
510
|
),
|
|
482
511
|
]);
|
|
483
512
|
});
|
package/src/gtfs/parser.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
* @
|
|
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
|
|
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
|
|
59
|
-
const
|
|
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,
|
|
70
|
+
await parseCalendar(calendarStream, activeServiceIds, datetime);
|
|
75
71
|
const calendarEnd = performance.now();
|
|
76
72
|
log.info(
|
|
77
|
-
`${
|
|
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,
|
|
81
|
+
await parseCalendarDates(calendarDatesStream, activeServiceIds, datetime);
|
|
86
82
|
const calendarDatesEnd = performance.now();
|
|
87
83
|
log.info(
|
|
88
|
-
`${
|
|
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
|
-
|
|
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
|
|
125
|
+
const { routes, serviceRoutesMap } = await parseStopTimes(
|
|
130
126
|
stopTimesStream,
|
|
131
127
|
parsedStops,
|
|
132
128
|
trips,
|
|
133
|
-
|
|
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
|
-
`${
|
|
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
|
-
|
|
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
|
-
`${
|
|
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
|
|
155
|
+
return timetable;
|
|
172
156
|
}
|
|
173
157
|
|
|
174
158
|
/**
|
|
175
159
|
* Parses a GTFS feed to extract all stops.
|
|
176
160
|
*
|
|
177
|
-
* @param
|
|
178
|
-
* @
|
|
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 =
|
|
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
|
}
|
package/src/gtfs/routes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import log from 'loglevel';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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:
|
|
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<
|
|
32
|
-
const routes:
|
|
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
|
|
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<
|
|
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 => {
|
package/src/gtfs/transfers.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
Transfer,
|
|
6
6
|
TransferType,
|
|
7
7
|
} from '../timetable/timetable.js';
|
|
8
|
-
import {
|
|
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:
|
|
41
|
+
stopsMap: GtfsStopsMap,
|
|
42
42
|
): Promise<TransfersMap> => {
|
|
43
43
|
const transfers: TransfersMap = new Map();
|
|
44
44
|
|