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
@@ -26,10 +26,6 @@ export type Stop = {
26
26
  platform?: Platform;
27
27
  };
28
28
 
29
- /**
30
- * Mapping internal StopIds to Stop objects.
31
- */
32
- export type StopsMap = Map<StopId, Stop>;
33
29
  /**
34
30
  * Mapping source stopIds to internal stopIds;
35
31
  */
@@ -6,13 +6,7 @@ import { addAll, createIndex, search, SearchResult } from 'slimsearch';
6
6
  import { generateAccentVariants } from './i18n.js';
7
7
  import { deserializeStopsMap, serializeStopsMap } from './io.js';
8
8
  import { StopsMap as ProtoStopsMap } from './proto/stops.js';
9
- import {
10
- SourceStopId,
11
- SourceStopsMap,
12
- Stop,
13
- StopId,
14
- StopsMap,
15
- } from './stops.js';
9
+ import { SourceStopId, SourceStopsMap, Stop, StopId } from './stops.js';
16
10
 
17
11
  type StopPoint = { id: StopId; lat: number; lon: number };
18
12
 
@@ -22,50 +16,52 @@ type StopPoint = { id: StopId; lat: number; lon: number };
22
16
  * to efficiently find stops based on user queries.
23
17
  */
24
18
  export class StopsIndex {
25
- private readonly stopsMap: StopsMap;
19
+ private readonly stops: Stop[];
26
20
  private readonly sourceStopsMap: SourceStopsMap;
27
21
  private readonly textIndex;
28
22
  private readonly geoIndex: KDTree;
29
23
  private readonly stopPoints: StopPoint[];
30
24
 
31
- constructor(stopsMap: StopsMap) {
32
- this.stopsMap = stopsMap;
25
+ constructor(stops: Stop[]) {
26
+ this.stops = stops;
33
27
  this.sourceStopsMap = new Map<SourceStopId, StopId>();
34
- for (const [id, stop] of stopsMap.entries()) {
35
- this.sourceStopsMap.set(stop.sourceStopId, id);
36
- }
37
- this.textIndex = createIndex({
38
- fields: ['name'],
39
- storeFields: ['id'],
40
- searchOptions: { prefix: true, fuzzy: 0.2 },
41
- processTerm: generateAccentVariants,
42
- });
43
28
  const stopsSet = new Map<StopId, { id: StopId; name: string }>();
44
- for (const [id, stop] of stopsMap.entries()) {
29
+ this.stopPoints = [];
30
+ for (let id = 0; id < stops.length; id++) {
31
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
32
+ const stop = stops[id]!;
33
+
34
+ this.sourceStopsMap.set(stop.sourceStopId, id);
35
+
45
36
  const effectiveStopId = stop.parent ?? id;
46
37
  if (!stopsSet.has(effectiveStopId)) {
47
38
  stopsSet.set(effectiveStopId, {
48
39
  id: effectiveStopId,
49
40
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
50
- name: stop.parent ? this.stopsMap.get(stop.parent)!.name : stop.name,
41
+ name: stop.parent ? this.stops[stop.parent]!.name : stop.name,
42
+ });
43
+ }
44
+
45
+ if (stop.lat && stop.lon) {
46
+ this.stopPoints.push({
47
+ id: id,
48
+ lat: stop.lat,
49
+ lon: stop.lon,
51
50
  });
52
51
  }
53
52
  }
53
+ this.textIndex = createIndex({
54
+ fields: ['name'],
55
+ storeFields: ['id'],
56
+ searchOptions: { prefix: true, fuzzy: 0.2 },
57
+ processTerm: generateAccentVariants,
58
+ });
54
59
  const stopsArray = Array.from(stopsSet.values());
55
60
  addAll(this.textIndex, stopsArray);
56
-
57
- this.stopPoints = Array.from(this.stopsMap.entries())
58
- .filter(([, stop]) => {
59
- if (stop.lat && stop.lon) return true;
60
- return false;
61
- })
62
- .map(([id, stop]) => ({
63
- id: id,
64
- lat: stop.lat as number,
65
- lon: stop.lon as number,
66
- }));
67
61
  this.geoIndex = new KDTree(this.stopPoints.length);
68
- for (const { lat, lon } of this.stopPoints) {
62
+ for (let i = 0; i < this.stopPoints.length; i++) {
63
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
64
+ const { lat, lon } = this.stopPoints[i]!;
69
65
  this.geoIndex.add(lon, lat);
70
66
  }
71
67
  this.geoIndex.finish();
@@ -90,7 +86,7 @@ export class StopsIndex {
90
86
  * @returns The serialized binary data.
91
87
  */
92
88
  serialize(): Uint8Array {
93
- const protoStopsMap: ProtoStopsMap = serializeStopsMap(this.stopsMap);
89
+ const protoStopsMap: ProtoStopsMap = serializeStopsMap(this.stops);
94
90
 
95
91
  const writer = new BinaryWriter();
96
92
  ProtoStopsMap.encode(protoStopsMap, writer);
@@ -103,7 +99,7 @@ export class StopsIndex {
103
99
  * @returns The total number of stops.
104
100
  */
105
101
  size(): number {
106
- return this.stopsMap.size;
102
+ return this.stops.length;
107
103
  }
108
104
 
109
105
  /**
@@ -115,7 +111,7 @@ export class StopsIndex {
115
111
  */
116
112
  findStopsByName(query: string, maxResults = 5): Stop[] {
117
113
  const results = search(this.textIndex, query).map(
118
- (result: SearchResult) => this.stopsMap.get(result.id as number) as Stop,
114
+ (result: SearchResult) => this.stops[result.id as number] as Stop,
119
115
  );
120
116
  return results.slice(0, maxResults);
121
117
  }
@@ -143,7 +139,7 @@ export class StopsIndex {
143
139
  radius,
144
140
  ).map((id) => {
145
141
  const stopPoint = this.stopPoints[id as number] as StopPoint;
146
- return this.stopsMap.get(stopPoint.id) as Stop;
142
+ return this.stops[stopPoint.id] as Stop;
147
143
  });
148
144
  return nearestStops;
149
145
  }
@@ -155,7 +151,7 @@ export class StopsIndex {
155
151
  * @returns The Stop object that matches the specified ID, or undefined if not found.
156
152
  */
157
153
  findStopById(id: StopId): Stop | undefined {
158
- return this.stopsMap.get(id);
154
+ return this.stops[id];
159
155
  }
160
156
 
161
157
  /**
@@ -180,15 +176,15 @@ export class StopsIndex {
180
176
  if (id === undefined) {
181
177
  return [];
182
178
  }
183
- const stop = this.stopsMap.get(id);
179
+ const stop = this.stops[id];
184
180
  if (!stop) {
185
181
  return [];
186
182
  }
187
183
  const equivalentStops = stop.parent
188
- ? (this.stopsMap.get(stop.parent)?.children ?? [])
184
+ ? (this.stops[stop.parent]?.children ?? [])
189
185
  : stop.children;
190
186
  return Array.from(new Set([id, ...equivalentStops])).map(
191
- (stopId) => this.stopsMap.get(stopId) as Stop,
187
+ (stopId) => this.stops[stopId] as Stop,
192
188
  );
193
189
  }
194
190
  }
@@ -12,31 +12,25 @@ import {
12
12
  } from '../io.js';
13
13
  import { REGULAR, Route } from '../route.js';
14
14
  import { Time } from '../time.js';
15
- import { ServiceRoutesMap, StopsAdjacency } from '../timetable.js';
15
+ import { ServiceRoute, StopAdjacency } from '../timetable.js';
16
16
 
17
17
  describe('Timetable IO', () => {
18
- const stopsAdjacency: StopsAdjacency = new Map([
19
- [
20
- 1,
21
- {
22
- transfers: [{ destination: 2, type: 'RECOMMENDED' }],
23
- routes: [0],
24
- },
25
- ],
26
- [
27
- 2,
28
- {
29
- transfers: [
30
- {
31
- destination: 1,
32
- type: 'GUARANTEED',
33
- minTransferTime: Duration.fromMinutes(3),
34
- },
35
- ],
36
- routes: [1],
37
- },
38
- ],
39
- ]);
18
+ const stopsAdjacency: StopAdjacency[] = [
19
+ {
20
+ transfers: [{ destination: 2, type: 'RECOMMENDED' }],
21
+ routes: [0],
22
+ },
23
+ {
24
+ transfers: [
25
+ {
26
+ destination: 1,
27
+ type: 'GUARANTEED',
28
+ minTransferTime: Duration.fromMinutes(3),
29
+ },
30
+ ],
31
+ routes: [1],
32
+ },
33
+ ];
40
34
  const routesAdjacency = [
41
35
  new Route(
42
36
  new Uint16Array([
@@ -45,7 +39,7 @@ describe('Timetable IO', () => {
45
39
  ]),
46
40
  new Uint8Array([REGULAR, REGULAR]),
47
41
  new Uint32Array([1, 2]),
48
- 'gtfs1',
42
+ 0,
49
43
  ),
50
44
  new Route(
51
45
  new Uint16Array([
@@ -54,31 +48,29 @@ describe('Timetable IO', () => {
54
48
  ]),
55
49
  new Uint8Array([REGULAR, REGULAR]),
56
50
  new Uint32Array([2, 1]),
57
- 'gtfs2',
51
+ 1,
58
52
  ),
59
53
  ];
60
- const routes: ServiceRoutesMap = new Map([
61
- ['gtfs1', { type: 'RAIL', name: 'Route 1', routes: [0] }],
62
- ['gtfs2', { type: 'RAIL', name: 'Route 2', routes: [1] }],
63
- ]);
64
- const stopsAdjacencyProto = {
65
- stops: {
66
- '1': {
67
- transfers: [{ destination: 2, type: 0 }],
68
- routes: [0],
69
- },
70
- '2': {
71
- transfers: [
72
- {
73
- destination: 1,
74
- type: 1,
75
- minTransferTime: 180,
76
- },
77
- ],
78
- routes: [1],
79
- },
54
+ const routes: ServiceRoute[] = [
55
+ { type: 'RAIL', name: 'Route 1', routes: [0] },
56
+ { type: 'RAIL', name: 'Route 2', routes: [1] },
57
+ ];
58
+ const stopsAdjacencyProto = [
59
+ {
60
+ transfers: [{ destination: 2, type: 0 }],
61
+ routes: [0],
62
+ },
63
+ {
64
+ transfers: [
65
+ {
66
+ destination: 1,
67
+ type: 1,
68
+ minTransferTime: 180,
69
+ },
70
+ ],
71
+ routes: [1],
80
72
  },
81
- };
73
+ ];
82
74
 
83
75
  const routesAdjacencyProto = [
84
76
  {
@@ -90,7 +82,7 @@ describe('Timetable IO', () => {
90
82
  ),
91
83
  pickUpDropOffTypes: new Uint8Array([REGULAR, REGULAR]),
92
84
  stops: new Uint8Array(new Uint32Array([1, 2]).buffer),
93
- serviceRouteId: 'gtfs1',
85
+ serviceRouteId: 0,
94
86
  },
95
87
  {
96
88
  stopTimes: new Uint8Array(
@@ -101,16 +93,14 @@ describe('Timetable IO', () => {
101
93
  ),
102
94
  pickUpDropOffTypes: new Uint8Array([REGULAR, REGULAR]),
103
95
  stops: new Uint8Array(new Uint32Array([2, 1]).buffer),
104
- serviceRouteId: 'gtfs2',
96
+ serviceRouteId: 1,
105
97
  },
106
98
  ];
107
99
 
108
- const routesProto = {
109
- routes: {
110
- gtfs1: { type: 2, name: 'Route 1', routes: [0] },
111
- gtfs2: { type: 2, name: 'Route 2', routes: [1] },
112
- },
113
- };
100
+ const routesProto = [
101
+ { type: 2, name: 'Route 1', routes: [0] },
102
+ { type: 2, name: 'Route 2', routes: [1] },
103
+ ];
114
104
 
115
105
  it('should serialize a stops adjacency matrix to a Uint8Array', () => {
116
106
  const serializedData = serializeStopsAdjacency(stopsAdjacency);
@@ -56,7 +56,7 @@ describe('Route', () => {
56
56
  );
57
57
 
58
58
  const stops = new Uint32Array([1001, 1002]);
59
- const serviceRouteId = 'test-route-1';
59
+ const serviceRouteId = 0;
60
60
 
61
61
  const route = new Route(stopTimes, pickUpDropOffTypes, stops, serviceRouteId);
62
62
 
@@ -71,10 +71,10 @@ describe('Route', () => {
71
71
  new Uint16Array([]),
72
72
  new Uint8Array([]),
73
73
  new Uint32Array([]),
74
- 'empty-route',
74
+ 1,
75
75
  );
76
76
  assert.strictEqual(emptyRoute.getNbStops(), 0);
77
- assert.strictEqual(emptyRoute.serviceRoute(), 'empty-route');
77
+ assert.strictEqual(emptyRoute.serviceRoute(), 1);
78
78
  });
79
79
  });
80
80
 
@@ -104,14 +104,14 @@ describe('Route', () => {
104
104
  it('should throw error when stopA is not found', () => {
105
105
  assert.throws(
106
106
  () => route.isBefore(9999, 1002),
107
- /Stop index undefined not found in route test-route-1/,
107
+ /Stop index undefined not found in route 0/,
108
108
  );
109
109
  });
110
110
 
111
111
  it('should throw error when stopB is not found', () => {
112
112
  assert.throws(
113
113
  () => route.isBefore(1001, 9999),
114
- /Stop index undefined not found in route test-route-1/,
114
+ /Stop index undefined not found in route 0/,
115
115
  );
116
116
  });
117
117
  });
@@ -148,7 +148,7 @@ describe('Route', () => {
148
148
  it('should throw error for invalid stop ID', () => {
149
149
  assert.throws(
150
150
  () => route.arrivalAt(9999, 0),
151
- /Stop index for 9999 not found in route test-route-1/,
151
+ /Stop index for 9999 not found in route 0/,
152
152
  );
153
153
  });
154
154
 
@@ -180,7 +180,7 @@ describe('Route', () => {
180
180
  it('should throw error for invalid stop ID', () => {
181
181
  assert.throws(
182
182
  () => route.departureFrom(9999, 0),
183
- /Stop index for 9999 not found in route test-route-1/,
183
+ /Stop index for 9999 not found in route 0/,
184
184
  );
185
185
  });
186
186
 
@@ -211,7 +211,7 @@ describe('Route', () => {
211
211
  it('should throw error for invalid stop ID', () => {
212
212
  assert.throws(
213
213
  () => route.pickUpTypeFrom(9999, 0),
214
- /Stop index for 9999 not found in route test-route-1/,
214
+ /Stop index for 9999 not found in route 0/,
215
215
  );
216
216
  });
217
217
 
@@ -237,7 +237,7 @@ describe('Route', () => {
237
237
  it('should throw error for invalid stop ID', () => {
238
238
  assert.throws(
239
239
  () => route.dropOffTypeAt(9999, 0),
240
- /Stop index for 9999 not found in route test-route-1/,
240
+ /Stop index for 9999 not found in route 0/,
241
241
  );
242
242
  });
243
243
 
@@ -249,25 +249,6 @@ describe('Route', () => {
249
249
  });
250
250
  });
251
251
 
252
- describe('stopsIterator', () => {
253
- it('should iterate over all stops when no start stop is provided', () => {
254
- const stopsList = Array.from(route.stopsIterator());
255
- assert.deepStrictEqual(stopsList, [1001, 1002]);
256
- });
257
-
258
- it('should iterate from specified start stop', () => {
259
- const stopsList = Array.from(route.stopsIterator(1002));
260
- assert.deepStrictEqual(stopsList, [1002]);
261
- });
262
-
263
- it('should throw error for invalid start stop ID', () => {
264
- assert.throws(
265
- () => Array.from(route.stopsIterator(9999)),
266
- /Start stop 9999 not found in route test-route-1/,
267
- );
268
- });
269
- });
270
-
271
252
  describe('findEarliestTrip', () => {
272
253
  it('should find earliest trip without time constraint', () => {
273
254
  const tripIndex = route.findEarliestTrip(1001);
@@ -318,7 +299,7 @@ describe('Route', () => {
318
299
  it('should throw error for invalid stop ID', () => {
319
300
  assert.throws(
320
301
  () => route.findEarliestTrip(9999),
321
- /Stop index for 9999 not found in route test-route-1/,
302
+ /Stop index for 9999 not found in route 0/,
322
303
  );
323
304
  });
324
305
  });
@@ -7,41 +7,33 @@ import { NOT_AVAILABLE, REGULAR, Route } from '../route.js';
7
7
  import { Time } from '../time.js';
8
8
  import {
9
9
  RouteType,
10
- ServiceRoutesMap,
11
- StopsAdjacency,
10
+ ServiceRoute,
11
+ StopAdjacency,
12
12
  Timetable,
13
13
  } from '../timetable.js';
14
14
 
15
15
  describe('Timetable', () => {
16
- const stopsAdjacency: StopsAdjacency = new Map([
17
- [
18
- 1,
19
- {
20
- transfers: [{ destination: 2, type: 'RECOMMENDED' }],
21
- routes: [0, 1],
22
- },
23
- ],
24
- [
25
- 2,
26
- {
27
- transfers: [
28
- {
29
- destination: 1,
30
- type: 'GUARANTEED',
31
- minTransferTime: Duration.fromMinutes(3),
32
- },
33
- ],
34
- routes: [1, 0],
35
- },
36
- ],
37
- [
38
- 3,
39
- {
40
- transfers: [],
41
- routes: [],
42
- },
43
- ],
44
- ]);
16
+ const stopsAdjacency: StopAdjacency[] = [
17
+ { transfers: [], routes: [] },
18
+ {
19
+ transfers: [{ destination: 2, type: 'RECOMMENDED' }],
20
+ routes: [0, 1],
21
+ },
22
+ {
23
+ transfers: [
24
+ {
25
+ destination: 1,
26
+ type: 'GUARANTEED',
27
+ minTransferTime: Duration.fromMinutes(3),
28
+ },
29
+ ],
30
+ routes: [1, 0],
31
+ },
32
+ {
33
+ transfers: [],
34
+ routes: [],
35
+ },
36
+ ];
45
37
 
46
38
  const route1 = new Route(
47
39
  new Uint16Array([
@@ -59,7 +51,7 @@ describe('Timetable', () => {
59
51
  [REGULAR, REGULAR, REGULAR, REGULAR],
60
52
  ),
61
53
  new Uint32Array([1, 2]),
62
- 'gtfs1',
54
+ 0,
63
55
  );
64
56
  const route2 = new Route(
65
57
  new Uint16Array([
@@ -70,13 +62,13 @@ describe('Timetable', () => {
70
62
  ]),
71
63
  encodePickUpDropOffTypes([REGULAR, REGULAR], [REGULAR, REGULAR]),
72
64
  new Uint32Array([2, 1]),
73
- 'gtfs2',
65
+ 1,
74
66
  );
75
67
  const routesAdjacency = [route1, route2];
76
- const routes: ServiceRoutesMap = new Map([
77
- ['gtfs1', { type: 'RAIL', name: 'Route 1', routes: [0] }],
78
- ['gtfs2', { type: 'RAIL', name: 'Route 2', routes: [1] }],
79
- ]);
68
+ const routes: ServiceRoute[] = [
69
+ { type: 'RAIL', name: 'Route 1', routes: [0] },
70
+ { type: 'RAIL', name: 'Route 2', routes: [1] },
71
+ ];
80
72
 
81
73
  const sampleTimetable: Timetable = new Timetable(
82
74
  stopsAdjacency,