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.
Files changed (48) hide show
  1. package/CHANGELOG.md +3 -8
  2. package/dist/cli.mjs +128 -249
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/gtfs/parser.d.ts +0 -3
  5. package/dist/gtfs/stops.d.ts +1 -2
  6. package/dist/gtfs/trips.d.ts +6 -5
  7. package/dist/parser.cjs.js +127 -248
  8. package/dist/parser.cjs.js.map +1 -1
  9. package/dist/parser.esm.js +127 -248
  10. package/dist/parser.esm.js.map +1 -1
  11. package/dist/router.cjs.js +1 -1
  12. package/dist/router.cjs.js.map +1 -1
  13. package/dist/router.d.ts +2 -2
  14. package/dist/router.esm.js +1 -1
  15. package/dist/router.esm.js.map +1 -1
  16. package/dist/router.umd.js +1 -1
  17. package/dist/router.umd.js.map +1 -1
  18. package/dist/routing/route.d.ts +3 -3
  19. package/dist/timetable/io.d.ts +5 -4
  20. package/dist/timetable/proto/timetable.d.ts +5 -15
  21. package/dist/timetable/route.d.ts +1 -1
  22. package/dist/timetable/timetable.d.ts +7 -5
  23. package/package.json +1 -1
  24. package/src/__e2e__/timetable/stops.bin +2 -2
  25. package/src/__e2e__/timetable/timetable.bin +2 -2
  26. package/src/gtfs/__tests__/parser.test.ts +2 -2
  27. package/src/gtfs/__tests__/routes.test.ts +3 -0
  28. package/src/gtfs/__tests__/stops.test.ts +6 -13
  29. package/src/gtfs/__tests__/trips.test.ts +122 -154
  30. package/src/gtfs/parser.ts +6 -11
  31. package/src/gtfs/profiles/__tests__/ch.test.ts +0 -28
  32. package/src/gtfs/profiles/ch.ts +1 -18
  33. package/src/gtfs/profiles/standard.ts +0 -9
  34. package/src/gtfs/routes.ts +1 -0
  35. package/src/gtfs/stops.ts +2 -12
  36. package/src/gtfs/trips.ts +21 -19
  37. package/src/router.ts +2 -2
  38. package/src/routing/__tests__/route.test.ts +3 -3
  39. package/src/routing/__tests__/router.test.ts +186 -203
  40. package/src/routing/route.ts +3 -3
  41. package/src/routing/router.ts +1 -1
  42. package/src/timetable/__tests__/io.test.ts +52 -64
  43. package/src/timetable/__tests__/timetable.test.ts +9 -13
  44. package/src/timetable/io.ts +20 -19
  45. package/src/timetable/proto/timetable.proto +5 -8
  46. package/src/timetable/proto/timetable.ts +78 -201
  47. package/src/timetable/route.ts +1 -1
  48. package/src/timetable/timetable.ts +20 -16
@@ -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, StopId } from '../stops/stops.js';
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, StopEntry } from './stops.js';
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.size} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`,
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');
@@ -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
  };
@@ -42,6 +42,7 @@ export const parseRoutes = async (
42
42
  routes.set(line.route_id, {
43
43
  name: line.route_short_name,
44
44
  type: routeType,
45
+ routes: [],
45
46
  });
46
47
  }
47
48
  return routes;
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 { Maybe, parseCsv } from './utils.js';
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 routeIds A mapping of route IDs to route details.
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
- routeIds: ServiceRoutesMap,
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 (!routeIds.get(line.route_id)) {
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
- routes: RoutesAdjacency,
196
+ serviceRoutes: ServiceRoutesMap,
197
+ routes: Route[],
199
198
  transfersMap: TransfersMap,
200
199
  ): StopsAdjacency => {
201
200
  const stopsAdjacency: StopsAdjacency = new Map();
202
- for (const routeId of routes.keys()) {
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(routeId);
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<RoutesAdjacency> => {
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
- const routeBuilders: Map<RouteId, RouteBuilder> = new Map();
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: RoutesAdjacency = new Map<RouteId, Route>();
359
- for (const [routeId, routeBuilder] of routeBuilders) {
361
+ const routesAdjacency: Route[] = [];
362
+ for (const [, routeBuilder] of routeBuilders) {
360
363
  const routeData = finalizeRouteFromBuilder(routeBuilder);
361
- routesAdjacency.set(
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
- ServiceRoute,
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
- ServiceRoute,
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 { ServiceRoute, TransferType } from '../../timetable/timetable.js';
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: ServiceRoute = {
43
+ const serviceRoute: ServiceRouteInfo = {
44
44
  type: 'BUS',
45
45
  name: 'Route 1',
46
46
  };
47
47
 
48
- const serviceRoute2: ServiceRoute = {
48
+ const serviceRoute2: ServiceRouteInfo = {
49
49
  type: 'RAIL',
50
50
  name: 'Route 2',
51
51
  };