minotor 4.0.0 → 5.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/CHANGELOG.md +3 -8
- package/dist/cli.mjs +128 -249
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/parser.d.ts +0 -3
- package/dist/gtfs/stops.d.ts +1 -2
- package/dist/gtfs/trips.d.ts +6 -5
- package/dist/parser.cjs.js +127 -248
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +127 -248
- package/dist/parser.esm.js.map +1 -1
- package/dist/router.cjs.js +1 -1
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +2 -2
- package/dist/router.esm.js +1 -1
- package/dist/router.esm.js.map +1 -1
- package/dist/router.umd.js +1 -1
- package/dist/router.umd.js.map +1 -1
- package/dist/routing/route.d.ts +3 -3
- package/dist/timetable/io.d.ts +5 -4
- package/dist/timetable/proto/timetable.d.ts +5 -15
- package/dist/timetable/route.d.ts +1 -1
- package/dist/timetable/timetable.d.ts +7 -5
- package/package.json +1 -1
- package/src/__e2e__/timetable/stops.bin +2 -2
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/gtfs/__tests__/parser.test.ts +2 -2
- package/src/gtfs/__tests__/routes.test.ts +3 -0
- package/src/gtfs/__tests__/stops.test.ts +6 -13
- package/src/gtfs/__tests__/trips.test.ts +122 -154
- package/src/gtfs/parser.ts +6 -11
- package/src/gtfs/profiles/__tests__/ch.test.ts +0 -28
- package/src/gtfs/profiles/ch.ts +1 -18
- package/src/gtfs/profiles/standard.ts +0 -9
- package/src/gtfs/routes.ts +1 -0
- package/src/gtfs/stops.ts +2 -12
- package/src/gtfs/trips.ts +21 -19
- package/src/router.ts +2 -2
- package/src/routing/__tests__/route.test.ts +3 -3
- package/src/routing/__tests__/router.test.ts +186 -203
- package/src/routing/route.ts +3 -3
- package/src/routing/router.ts +1 -1
- package/src/timetable/__tests__/io.test.ts +52 -64
- package/src/timetable/__tests__/timetable.test.ts +9 -13
- package/src/timetable/io.ts +20 -19
- package/src/timetable/proto/timetable.proto +5 -8
- package/src/timetable/proto/timetable.ts +78 -201
- package/src/timetable/route.ts +1 -1
- package/src/timetable/timetable.ts +20 -16
package/src/gtfs/parser.ts
CHANGED
|
@@ -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 {
|
|
5
|
+
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
9
|
import { parseRoutes } from './routes.js';
|
|
10
10
|
import { parseCalendar, parseCalendarDates, ServiceIds } from './services.js';
|
|
11
|
-
import { indexStops, parseStops
|
|
11
|
+
import { indexStops, parseStops } from './stops.js';
|
|
12
12
|
import { parseTransfers, TransfersMap } from './transfers.js';
|
|
13
13
|
import {
|
|
14
14
|
buildStopsAdjacencyStructure,
|
|
@@ -27,7 +27,6 @@ const TRANSFERS_FILE = 'transfers.txt';
|
|
|
27
27
|
|
|
28
28
|
export type GtfsProfile = {
|
|
29
29
|
routeTypeParser: (routeType: number) => Maybe<RouteType>;
|
|
30
|
-
platformParser?: (stopEntry: StopEntry) => Maybe<Platform>;
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
export class GtfsParser {
|
|
@@ -62,10 +61,7 @@ export class GtfsParser {
|
|
|
62
61
|
log.info(`Parsing ${STOPS_FILE}`);
|
|
63
62
|
const stopsStart = performance.now();
|
|
64
63
|
const stopsStream = await zip.stream(STOPS_FILE);
|
|
65
|
-
const parsedStops = await parseStops(
|
|
66
|
-
stopsStream,
|
|
67
|
-
this.profile.platformParser,
|
|
68
|
-
);
|
|
64
|
+
const parsedStops = await parseStops(stopsStream);
|
|
69
65
|
const stopsEnd = performance.now();
|
|
70
66
|
log.info(
|
|
71
67
|
`${parsedStops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`,
|
|
@@ -138,12 +134,13 @@ export class GtfsParser {
|
|
|
138
134
|
);
|
|
139
135
|
const stopsAdjacency = buildStopsAdjacencyStructure(
|
|
140
136
|
validStopIds,
|
|
137
|
+
validGtfsRoutes,
|
|
141
138
|
routesAdjacency,
|
|
142
139
|
transfers,
|
|
143
140
|
);
|
|
144
141
|
const stopTimesEnd = performance.now();
|
|
145
142
|
log.info(
|
|
146
|
-
`${routesAdjacency.
|
|
143
|
+
`${routesAdjacency.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`,
|
|
147
144
|
);
|
|
148
145
|
|
|
149
146
|
log.info(`Removing unused stops.`);
|
|
@@ -187,9 +184,7 @@ export class GtfsParser {
|
|
|
187
184
|
log.info(`Parsing ${STOPS_FILE}`);
|
|
188
185
|
const stopsStart = performance.now();
|
|
189
186
|
const stopsStream = await zip.stream(STOPS_FILE);
|
|
190
|
-
const stops = indexStops(
|
|
191
|
-
await parseStops(stopsStream, this.profile.platformParser),
|
|
192
|
-
);
|
|
187
|
+
const stops = indexStops(await parseStops(stopsStream));
|
|
193
188
|
const stopsEnd = performance.now();
|
|
194
189
|
|
|
195
190
|
log.info(
|
|
@@ -4,34 +4,6 @@ import { describe, it } from 'node:test';
|
|
|
4
4
|
import { chGtfsProfile } from '../ch.js';
|
|
5
5
|
|
|
6
6
|
describe('The swiss GTFS feed parser', () => {
|
|
7
|
-
it('should extract the platform number from a stop entry', () => {
|
|
8
|
-
assert.ok(chGtfsProfile.platformParser);
|
|
9
|
-
assert.equal(
|
|
10
|
-
chGtfsProfile.platformParser({
|
|
11
|
-
stop_id: '8504100:0:1',
|
|
12
|
-
stop_name: 'Fribourg/Freiburg',
|
|
13
|
-
stop_lat: 46.8018210323626,
|
|
14
|
-
stop_lon: 7.14993389242926,
|
|
15
|
-
location_type: 1,
|
|
16
|
-
parent_station: 'Parent8504100',
|
|
17
|
-
}),
|
|
18
|
-
'1',
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
it('should not extract any platform number when not specified', () => {
|
|
22
|
-
assert.ok(chGtfsProfile.platformParser);
|
|
23
|
-
assert.equal(
|
|
24
|
-
chGtfsProfile.platformParser({
|
|
25
|
-
stop_id: 'Parent8587255',
|
|
26
|
-
stop_name: 'Fribourg, Tilleul/Cathédrale',
|
|
27
|
-
stop_lat: 46.8061375857565,
|
|
28
|
-
stop_lon: 7.16145029437328,
|
|
29
|
-
location_type: 1,
|
|
30
|
-
parent_station: '',
|
|
31
|
-
}),
|
|
32
|
-
undefined,
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
7
|
it('should convert the SBB route type to GTFS route type', () => {
|
|
36
8
|
assert.ok(chGtfsProfile.routeTypeParser);
|
|
37
9
|
assert.equal(chGtfsProfile.routeTypeParser(106), 'RAIL');
|
package/src/gtfs/profiles/ch.ts
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
import { Platform } from '../../stops/stops.js';
|
|
2
1
|
import { RouteType } from '../../timetable/timetable.js';
|
|
3
2
|
import { GtfsProfile } from '../parser.js';
|
|
4
|
-
import { StopEntry } from '../stops.js';
|
|
5
3
|
import { Maybe } from '../utils.js';
|
|
6
4
|
|
|
7
|
-
/**
|
|
8
|
-
* Parses the platform number from a stop entry.
|
|
9
|
-
* @param stopEntry The stop entry.
|
|
10
|
-
* @returns The platform corresponding to this stop.
|
|
11
|
-
*/
|
|
12
|
-
const platformParser = (stopEntry: StopEntry): Maybe<Platform> => {
|
|
13
|
-
const stopId = stopEntry.stop_id;
|
|
14
|
-
const stopParts = stopId.split(':');
|
|
15
|
-
if (stopParts.length > 2) {
|
|
16
|
-
return stopParts[2];
|
|
17
|
-
}
|
|
18
|
-
return undefined;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
5
|
/**
|
|
22
6
|
* Parses the SBB extended route type and returns the corresponding basic GTFS route type.
|
|
23
7
|
* @param routeType The SBB route type to parse.
|
|
@@ -44,6 +28,7 @@ const routeTypeParser = (routeType: number): Maybe<RouteType> => {
|
|
|
44
28
|
return 'FERRY'; // Boat
|
|
45
29
|
case 900: // Tram
|
|
46
30
|
return 'TRAM'; // Tram
|
|
31
|
+
case 116: // ??? train TODO figure out what this means
|
|
47
32
|
case 117: // Special train
|
|
48
33
|
case 102: // International train
|
|
49
34
|
case 104: // Car train
|
|
@@ -55,7 +40,6 @@ const routeTypeParser = (routeType: number): Maybe<RouteType> => {
|
|
|
55
40
|
case 100: // No guaranteed train
|
|
56
41
|
case 106: // Regional train
|
|
57
42
|
case 109: // Urban train
|
|
58
|
-
case 116: // ??? train TODO figure out what this means
|
|
59
43
|
return 'RAIL'; // Train
|
|
60
44
|
case 1100: // Aircraft
|
|
61
45
|
case 1500: // Taxi
|
|
@@ -66,5 +50,4 @@ const routeTypeParser = (routeType: number): Maybe<RouteType> => {
|
|
|
66
50
|
|
|
67
51
|
export const chGtfsProfile: GtfsProfile = {
|
|
68
52
|
routeTypeParser,
|
|
69
|
-
platformParser,
|
|
70
53
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { Platform } from '../../stops/stops.js';
|
|
2
1
|
import { GtfsProfile } from '../parser.js';
|
|
3
|
-
import { StopEntry } from '../stops.js';
|
|
4
|
-
import { Maybe } from '../utils.js';
|
|
5
2
|
|
|
6
3
|
export const standardProfile: GtfsProfile = {
|
|
7
4
|
routeTypeParser: (routeType: number) => {
|
|
@@ -30,10 +27,4 @@ export const standardProfile: GtfsProfile = {
|
|
|
30
27
|
return undefined;
|
|
31
28
|
}
|
|
32
29
|
},
|
|
33
|
-
platformParser: (stopEntry: StopEntry): Maybe<Platform> => {
|
|
34
|
-
if (stopEntry.platform_code) {
|
|
35
|
-
return stopEntry.platform_code;
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
},
|
|
39
30
|
};
|
package/src/gtfs/routes.ts
CHANGED
package/src/gtfs/stops.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
StopId,
|
|
9
9
|
StopsMap,
|
|
10
10
|
} from '../stops/stops.js';
|
|
11
|
-
import {
|
|
11
|
+
import { parseCsv } from './utils.js';
|
|
12
12
|
|
|
13
13
|
export type GtfsLocationType =
|
|
14
14
|
| 0 // simple stop or platform (can also be empty)
|
|
@@ -40,7 +40,6 @@ export type ParsedStopsMap = Map<SourceStopId, ParsedStop>;
|
|
|
40
40
|
*/
|
|
41
41
|
export const parseStops = async (
|
|
42
42
|
stopsStream: NodeJS.ReadableStream,
|
|
43
|
-
platformParser?: (stopEntry: StopEntry) => Maybe<Platform>,
|
|
44
43
|
): Promise<ParsedStopsMap> => {
|
|
45
44
|
const parsedStops = new Map<SourceStopId, ParsedStop>();
|
|
46
45
|
let i = 0;
|
|
@@ -59,19 +58,10 @@ export const parseStops = async (
|
|
|
59
58
|
locationType: line.location_type
|
|
60
59
|
? parseGtfsLocationType(line.location_type)
|
|
61
60
|
: 'SIMPLE_STOP_OR_PLATFORM',
|
|
61
|
+
...(line.platform_code && { platform: line.platform_code }),
|
|
62
62
|
children: [],
|
|
63
63
|
...(line.parent_station && { parentSourceId: line.parent_station }),
|
|
64
64
|
};
|
|
65
|
-
if (platformParser) {
|
|
66
|
-
try {
|
|
67
|
-
const platform = platformParser(line);
|
|
68
|
-
if (platform) {
|
|
69
|
-
stop.platform = platform;
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
console.info(`Could not parse platform for stop ${line.stop_id}.`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
65
|
parsedStops.set(line.stop_id, stop);
|
|
76
66
|
i = i + 1;
|
|
77
67
|
}
|
package/src/gtfs/trips.ts
CHANGED
|
@@ -6,10 +6,8 @@ import {
|
|
|
6
6
|
NOT_AVAILABLE,
|
|
7
7
|
REGULAR,
|
|
8
8
|
Route,
|
|
9
|
-
RouteId,
|
|
10
9
|
} from '../timetable/route.js';
|
|
11
10
|
import {
|
|
12
|
-
RoutesAdjacency,
|
|
13
11
|
ServiceRouteId,
|
|
14
12
|
ServiceRoutesMap,
|
|
15
13
|
StopsAdjacency,
|
|
@@ -169,13 +167,13 @@ const finalizeRouteFromBuilder = (builder: RouteBuilder): SerializedRoute => {
|
|
|
169
167
|
*
|
|
170
168
|
* @param tripsStream The readable stream containing the trips data.
|
|
171
169
|
* @param serviceIds A mapping of service IDs to corresponding route IDs.
|
|
172
|
-
* @param
|
|
170
|
+
* @param serviceRoutes A mapping of route IDs to route details.
|
|
173
171
|
* @returns A mapping of trip IDs to corresponding route IDs.
|
|
174
172
|
*/
|
|
175
173
|
export const parseTrips = async (
|
|
176
174
|
tripsStream: NodeJS.ReadableStream,
|
|
177
175
|
serviceIds: ServiceIds,
|
|
178
|
-
|
|
176
|
+
serviceRoutes: ServiceRoutesMap,
|
|
179
177
|
): Promise<TripIdsMap> => {
|
|
180
178
|
const trips: TripIdsMap = new Map();
|
|
181
179
|
for await (const rawLine of parseCsv(tripsStream, ['stop_sequence'])) {
|
|
@@ -184,7 +182,7 @@ export const parseTrips = async (
|
|
|
184
182
|
// The trip doesn't correspond to an active service
|
|
185
183
|
continue;
|
|
186
184
|
}
|
|
187
|
-
if (!
|
|
185
|
+
if (!serviceRoutes.get(line.route_id)) {
|
|
188
186
|
// The trip doesn't correspond to a supported route
|
|
189
187
|
continue;
|
|
190
188
|
}
|
|
@@ -195,23 +193,27 @@ export const parseTrips = async (
|
|
|
195
193
|
|
|
196
194
|
export const buildStopsAdjacencyStructure = (
|
|
197
195
|
validStops: Set<StopId>,
|
|
198
|
-
|
|
196
|
+
serviceRoutes: ServiceRoutesMap,
|
|
197
|
+
routes: Route[],
|
|
199
198
|
transfersMap: TransfersMap,
|
|
200
199
|
): StopsAdjacency => {
|
|
201
200
|
const stopsAdjacency: StopsAdjacency = new Map();
|
|
202
|
-
|
|
203
|
-
const route = routes.get(routeId);
|
|
204
|
-
if (!route) {
|
|
205
|
-
throw new Error(`Route ${routeId} not found`);
|
|
206
|
-
}
|
|
201
|
+
routes.forEach((route, index) => {
|
|
207
202
|
for (const stop of route.stopsIterator()) {
|
|
208
203
|
if (!stopsAdjacency.get(stop) && validStops.has(stop)) {
|
|
209
204
|
stopsAdjacency.set(stop, { routes: [], transfers: [] });
|
|
210
205
|
}
|
|
211
206
|
|
|
212
|
-
stopsAdjacency.get(stop)?.routes.push(
|
|
207
|
+
stopsAdjacency.get(stop)?.routes.push(index);
|
|
213
208
|
}
|
|
214
|
-
|
|
209
|
+
const serviceRoute = serviceRoutes.get(route.serviceRoute());
|
|
210
|
+
if (!serviceRoute) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Service route ${route.serviceRoute()} not found for route ${index}.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
serviceRoute.routes.push(index);
|
|
216
|
+
});
|
|
215
217
|
for (const [stop, transfers] of transfersMap) {
|
|
216
218
|
const s = stopsAdjacency.get(stop);
|
|
217
219
|
if (s) {
|
|
@@ -239,7 +241,7 @@ export const parseStopTimes = async (
|
|
|
239
241
|
stopsMap: ParsedStopsMap,
|
|
240
242
|
validTripIds: TripIdsMap,
|
|
241
243
|
validStopIds: Set<StopId>,
|
|
242
|
-
): Promise<
|
|
244
|
+
): Promise<Route[]> => {
|
|
243
245
|
/**
|
|
244
246
|
* Adds a trip to the appropriate route builder
|
|
245
247
|
*/
|
|
@@ -296,7 +298,8 @@ export const parseStopTimes = async (
|
|
|
296
298
|
dropOffTypes = [];
|
|
297
299
|
};
|
|
298
300
|
|
|
299
|
-
|
|
301
|
+
type BuilderRouteId = string;
|
|
302
|
+
const routeBuilders: Map<BuilderRouteId, RouteBuilder> = new Map();
|
|
300
303
|
|
|
301
304
|
let previousSeq = 0;
|
|
302
305
|
let stops: StopId[] = [];
|
|
@@ -355,11 +358,10 @@ export const parseStopTimes = async (
|
|
|
355
358
|
addTrip(currentTripId);
|
|
356
359
|
}
|
|
357
360
|
|
|
358
|
-
const routesAdjacency:
|
|
359
|
-
for (const [
|
|
361
|
+
const routesAdjacency: Route[] = [];
|
|
362
|
+
for (const [, routeBuilder] of routeBuilders) {
|
|
360
363
|
const routeData = finalizeRouteFromBuilder(routeBuilder);
|
|
361
|
-
routesAdjacency.
|
|
362
|
-
routeId,
|
|
364
|
+
routesAdjacency.push(
|
|
363
365
|
new Route(
|
|
364
366
|
routeData.stopTimes,
|
|
365
367
|
routeData.pickUpDropOffTypes,
|
package/src/router.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Duration } from './timetable/duration.js';
|
|
|
12
12
|
import { Time } from './timetable/time.js';
|
|
13
13
|
import type {
|
|
14
14
|
RouteType,
|
|
15
|
-
|
|
15
|
+
ServiceRouteInfo,
|
|
16
16
|
TransferType,
|
|
17
17
|
} from './timetable/timetable.js';
|
|
18
18
|
import { Timetable } from './timetable/timetable.js';
|
|
@@ -34,7 +34,7 @@ export type {
|
|
|
34
34
|
LocationType,
|
|
35
35
|
ReachingTime,
|
|
36
36
|
RouteType,
|
|
37
|
-
|
|
37
|
+
ServiceRouteInfo,
|
|
38
38
|
SourceStopId,
|
|
39
39
|
Stop,
|
|
40
40
|
StopId,
|
|
@@ -4,7 +4,7 @@ import { describe, it } from 'node:test';
|
|
|
4
4
|
import { Stop } from '../../stops/stops.js';
|
|
5
5
|
import { Duration } from '../../timetable/duration.js';
|
|
6
6
|
import { Time } from '../../timetable/time.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ServiceRouteInfo, TransferType } from '../../timetable/timetable.js';
|
|
8
8
|
import { Route } from '../route.js';
|
|
9
9
|
|
|
10
10
|
describe('Route', () => {
|
|
@@ -40,12 +40,12 @@ describe('Route', () => {
|
|
|
40
40
|
children: [],
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const serviceRoute:
|
|
43
|
+
const serviceRoute: ServiceRouteInfo = {
|
|
44
44
|
type: 'BUS',
|
|
45
45
|
name: 'Route 1',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
const serviceRoute2:
|
|
48
|
+
const serviceRoute2: ServiceRouteInfo = {
|
|
49
49
|
type: 'RAIL',
|
|
50
50
|
name: 'Route 2',
|
|
51
51
|
};
|