minotor 1.0.6 → 2.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 (60) hide show
  1. package/CHANGELOG.md +9 -3
  2. package/README.md +3 -2
  3. package/dist/cli.mjs +604 -531
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/gtfs/stops.d.ts +19 -5
  6. package/dist/gtfs/transfers.d.ts +5 -4
  7. package/dist/gtfs/trips.d.ts +7 -5
  8. package/dist/gtfs/utils.d.ts +7 -8
  9. package/dist/parser.cjs.js +569 -501
  10. package/dist/parser.cjs.js.map +1 -1
  11. package/dist/parser.esm.js +569 -501
  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.d.ts +3 -3
  16. package/dist/router.esm.js +1 -1
  17. package/dist/router.esm.js.map +1 -1
  18. package/dist/router.umd.js +1 -1
  19. package/dist/router.umd.js.map +1 -1
  20. package/dist/routing/__tests__/route.test.d.ts +1 -0
  21. package/dist/routing/query.d.ts +7 -7
  22. package/dist/routing/result.d.ts +3 -3
  23. package/dist/routing/route.d.ts +1 -0
  24. package/dist/stops/proto/stops.d.ts +5 -4
  25. package/dist/stops/stops.d.ts +10 -1
  26. package/dist/stops/stopsIndex.d.ts +21 -4
  27. package/dist/timetable/proto/timetable.d.ts +21 -18
  28. package/dist/timetable/timetable.d.ts +38 -14
  29. package/package.json +4 -3
  30. package/src/cli/repl.ts +13 -10
  31. package/src/gtfs/__tests__/parser.test.ts +50 -579
  32. package/src/gtfs/__tests__/stops.test.ts +181 -112
  33. package/src/gtfs/__tests__/transfers.test.ts +170 -12
  34. package/src/gtfs/__tests__/trips.test.ts +212 -141
  35. package/src/gtfs/__tests__/utils.test.ts +4 -4
  36. package/src/gtfs/parser.ts +22 -13
  37. package/src/gtfs/stops.ts +63 -28
  38. package/src/gtfs/transfers.ts +14 -6
  39. package/src/gtfs/trips.ts +110 -47
  40. package/src/gtfs/utils.ts +11 -11
  41. package/src/router.ts +2 -4
  42. package/src/routing/__tests__/route.test.ts +112 -0
  43. package/src/routing/__tests__/router.test.ts +234 -244
  44. package/src/routing/query.ts +7 -7
  45. package/src/routing/result.ts +9 -6
  46. package/src/routing/route.ts +11 -0
  47. package/src/routing/router.ts +26 -24
  48. package/src/stops/__tests__/io.test.ts +9 -8
  49. package/src/stops/__tests__/stopFinder.test.ts +45 -36
  50. package/src/stops/io.ts +8 -5
  51. package/src/stops/proto/stops.proto +8 -7
  52. package/src/stops/proto/stops.ts +68 -38
  53. package/src/stops/stops.ts +13 -1
  54. package/src/stops/stopsIndex.ts +50 -7
  55. package/src/timetable/__tests__/io.test.ts +40 -49
  56. package/src/timetable/__tests__/timetable.test.ts +50 -58
  57. package/src/timetable/io.ts +69 -56
  58. package/src/timetable/proto/timetable.proto +22 -17
  59. package/src/timetable/proto/timetable.ts +94 -184
  60. package/src/timetable/timetable.ts +62 -29
@@ -13,18 +13,18 @@ import {
13
13
  describe('timetable io', () => {
14
14
  const stopsAdjacency: StopsAdjacency = new Map([
15
15
  [
16
- 'stop1',
16
+ 1,
17
17
  {
18
- transfers: [{ destination: 'stop2', type: 'RECOMMENDED' }],
18
+ transfers: [{ destination: 2, type: 'RECOMMENDED' }],
19
19
  routes: ['route1'],
20
20
  },
21
21
  ],
22
22
  [
23
- 'stop2',
23
+ 2,
24
24
  {
25
25
  transfers: [
26
26
  {
27
- destination: 'stop1',
27
+ destination: 1,
28
28
  type: 'GUARANTEED',
29
29
  minTransferTime: Duration.fromMinutes(3),
30
30
  },
@@ -37,36 +37,30 @@ describe('timetable io', () => {
37
37
  [
38
38
  'route1',
39
39
  {
40
- stopTimes: [
41
- {
42
- arrival: Time.fromHMS(0, 16, 40),
43
- departure: Time.fromHMS(0, 16, 50),
44
- pickUpType: 'REGULAR',
45
- dropOffType: 'REGULAR',
46
- },
47
- {
48
- arrival: Time.fromHMS(0, 33, 20),
49
- departure: Time.fromHMS(0, 33, 30),
50
- pickUpType: 'NOT_AVAILABLE',
51
- dropOffType: 'REGULAR',
52
- },
53
- {
54
- arrival: Time.fromHMS(0, 50, 0),
55
- departure: Time.fromHMS(0, 50, 10),
56
- pickUpType: 'REGULAR',
57
- dropOffType: 'REGULAR',
58
- },
59
- {
60
- arrival: Time.fromHMS(1, 10, 0),
61
- departure: Time.fromHMS(1, 10, 10),
62
- pickUpType: 'REGULAR',
63
- dropOffType: 'REGULAR',
64
- },
65
- ],
66
- stops: ['stop1', 'stop2'],
40
+ stopTimes: new Uint32Array([
41
+ Time.fromHMS(0, 16, 40).toSeconds(),
42
+ Time.fromHMS(0, 16, 50).toSeconds(),
43
+ Time.fromHMS(0, 33, 20).toSeconds(),
44
+ Time.fromHMS(0, 33, 30).toSeconds(),
45
+ Time.fromHMS(0, 50, 0).toSeconds(),
46
+ Time.fromHMS(0, 50, 10).toSeconds(),
47
+ Time.fromHMS(1, 10, 0).toSeconds(),
48
+ Time.fromHMS(1, 10, 10).toSeconds(),
49
+ ]),
50
+ pickUpDropOffTypes: new Uint8Array([
51
+ 0,
52
+ 0, // REGULAR
53
+ 1,
54
+ 0, // NOT_AVAILABLE, REGULAR
55
+ 0,
56
+ 0, // REGULAR
57
+ 0,
58
+ 0, // REGULAR
59
+ ]),
60
+ stops: new Uint32Array([1, 2]),
67
61
  stopIndices: new Map([
68
- ['stop1', 0],
69
- ['stop2', 1],
62
+ [1, 0],
63
+ [2, 1],
70
64
  ]),
71
65
  serviceRouteId: 'gtfs1',
72
66
  },
@@ -74,24 +68,22 @@ describe('timetable io', () => {
74
68
  [
75
69
  'route2',
76
70
  {
77
- stopTimes: [
78
- {
79
- arrival: Time.fromHMS(1, 6, 40),
80
- departure: Time.fromHMS(1, 6, 50),
81
- pickUpType: 'REGULAR',
82
- dropOffType: 'REGULAR',
83
- },
84
- {
85
- arrival: Time.fromHMS(1, 23, 20),
86
- departure: Time.fromHMS(1, 23, 30),
87
- pickUpType: 'REGULAR',
88
- dropOffType: 'REGULAR',
89
- },
90
- ],
91
- stops: ['stop2', 'stop1'],
71
+ stopTimes: new Uint32Array([
72
+ Time.fromHMS(1, 6, 40).toSeconds(),
73
+ Time.fromHMS(1, 6, 50).toSeconds(),
74
+ Time.fromHMS(1, 23, 20).toSeconds(),
75
+ Time.fromHMS(1, 23, 30).toSeconds(),
76
+ ]),
77
+ pickUpDropOffTypes: new Uint8Array([
78
+ 0,
79
+ 0, // REGULAR
80
+ 0,
81
+ 0, // REGULAR
82
+ ]),
83
+ stops: new Uint32Array([2, 1]),
92
84
  stopIndices: new Map([
93
- ['stop2', 0],
94
- ['stop1', 1],
85
+ [2, 0],
86
+ [1, 1],
95
87
  ]),
96
88
  serviceRouteId: 'gtfs2',
97
89
  },
@@ -122,7 +114,7 @@ describe('timetable io', () => {
122
114
  it('should find the earliest trip for stop1 on route1', () => {
123
115
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
124
116
  const route = sampleTimetable.getRoute('route1')!;
125
- const tripIndex = sampleTimetable.findEarliestTrip(route, 'stop1');
117
+ const tripIndex = sampleTimetable.findEarliestTrip(route, 1);
126
118
  assert.strictEqual(tripIndex, 0);
127
119
  });
128
120
 
@@ -132,7 +124,7 @@ describe('timetable io', () => {
132
124
  const afterTime = Time.fromHMS(0, 25, 0);
133
125
  const tripIndex = sampleTimetable.findEarliestTrip(
134
126
  route,
135
- 'stop1',
127
+ 1,
136
128
  undefined,
137
129
  afterTime,
138
130
  );
@@ -145,7 +137,7 @@ describe('timetable io', () => {
145
137
  const afterTime = Time.fromHMS(0, 58, 20);
146
138
  const tripIndex = sampleTimetable.findEarliestTrip(
147
139
  route,
148
- 'stop1',
140
+ 1,
149
141
  undefined,
150
142
  afterTime,
151
143
  );
@@ -154,24 +146,24 @@ describe('timetable io', () => {
154
146
  it('should return undefined if the stop on a trip has pick up not available', () => {
155
147
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
156
148
  const route = sampleTimetable.getRoute('route1')!;
157
- const tripIndex = sampleTimetable.findEarliestTrip(route, 'stop2');
149
+ const tripIndex = sampleTimetable.findEarliestTrip(route, 2);
158
150
  assert.strictEqual(tripIndex, 1);
159
151
  });
160
152
  it('should find reachable routes from a set of stop IDs', () => {
161
- const fromStops = new Set(['stop1']);
153
+ const fromStops = new Set([1]);
162
154
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
163
155
  assert.strictEqual(reachableRoutes.size, 1);
164
- assert.strictEqual(reachableRoutes.get('route1'), 'stop1');
156
+ assert.strictEqual(reachableRoutes.get('route1'), 1);
165
157
  });
166
158
 
167
159
  it('should find no reachable routes if starting from a non-existent stop', () => {
168
- const fromStops = new Set(['non_existent_stop']);
160
+ const fromStops = new Set([5]);
169
161
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
170
162
  assert.strictEqual(reachableRoutes.size, 0);
171
163
  });
172
164
 
173
165
  it('should find reachable routes filtered by transport modes', () => {
174
- const fromStops = new Set(['stop1']);
166
+ const fromStops = new Set([1]);
175
167
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops, [
176
168
  'BUS',
177
169
  ]);
@@ -1,6 +1,5 @@
1
1
  import { Duration } from './duration.js';
2
2
  import {
3
- PickUpDropOffType as ProtoPickUpDropOffType,
4
3
  RoutesAdjacency as ProtoRoutesAdjacency,
5
4
  RouteType as ProtoRouteType,
6
5
  ServiceRoutesMap as ProtoServiceRoutesMap,
@@ -8,9 +7,7 @@ import {
8
7
  Transfer as ProtoTransfer,
9
8
  TransferType as ProtoTransferType,
10
9
  } from './proto/timetable.js';
11
- import { Time } from './time.js';
12
10
  import {
13
- PickUpDropOffType,
14
11
  Route,
15
12
  RoutesAdjacency,
16
13
  RouteType,
@@ -20,6 +17,59 @@ import {
20
17
  TransferType,
21
18
  } from './timetable.js';
22
19
 
20
+ const isLittleEndian = (() => {
21
+ const buffer = new ArrayBuffer(4);
22
+ const view = new DataView(buffer);
23
+ view.setUint32(0, 0x12345678);
24
+ return new Uint8Array(buffer)[0] === 0x78;
25
+ })();
26
+
27
+ const STANDARD_ENDIANNESS = true; // true = little-endian
28
+
29
+ function uint32ArrayToBytes(array: Uint32Array): Uint8Array {
30
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
31
+ return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
32
+ }
33
+
34
+ // If endianness doesn't match, we need to swap byte order
35
+ const result = new Uint8Array(array.length * 4);
36
+ const view = new DataView(result.buffer);
37
+
38
+ for (let i = 0; i < array.length; i++) {
39
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
40
+ view.setUint32(i * 4, array[i]!, STANDARD_ENDIANNESS);
41
+ }
42
+
43
+ return result;
44
+ }
45
+
46
+ function bytesToUint32Array(bytes: Uint8Array): Uint32Array {
47
+ if (bytes.byteLength % 4 !== 0) {
48
+ throw new Error(
49
+ 'Byte array length must be a multiple of 4 to convert to Uint32Array',
50
+ );
51
+ }
52
+
53
+ // If system endianness matches our standard, we can create a view directly
54
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
55
+ return new Uint32Array(
56
+ bytes.buffer,
57
+ bytes.byteOffset,
58
+ bytes.byteLength / 4,
59
+ );
60
+ }
61
+
62
+ // If endianness doesn't match, we need to swap byte order
63
+ const result = new Uint32Array(bytes.byteLength / 4);
64
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
65
+
66
+ for (let i = 0; i < result.length; i++) {
67
+ result[i] = view.getUint32(i * 4, STANDARD_ENDIANNESS);
68
+ }
69
+
70
+ return result;
71
+ }
72
+
23
73
  export const serializeStopsAdjacency = (
24
74
  stopsAdjacency: StopsAdjacency,
25
75
  ): ProtoStopsAdjacency => {
@@ -28,7 +78,7 @@ export const serializeStopsAdjacency = (
28
78
  };
29
79
 
30
80
  stopsAdjacency.forEach(
31
- (value: { transfers: Transfer[]; routes: string[] }, key: string) => {
81
+ (value: { transfers: Transfer[]; routes: string[] }, key: number) => {
32
82
  protoStopsAdjacency.stops[key] = {
33
83
  transfers: value.transfers.map((transfer) => ({
34
84
  destination: transfer.destination,
@@ -54,13 +104,9 @@ export const serializeRoutesAdjacency = (
54
104
 
55
105
  routesAdjacency.forEach((value: Route, key: string) => {
56
106
  protoRoutesAdjacency.routes[key] = {
57
- stopTimes: value.stopTimes.map((stopTimes) => ({
58
- arrival: stopTimes.arrival.toSeconds(),
59
- departure: stopTimes.departure.toSeconds(),
60
- pickUpType: serializePickUpDropOffType(stopTimes.pickUpType),
61
- dropOffType: serializePickUpDropOffType(stopTimes.dropOffType),
62
- })),
63
- stops: value.stops,
107
+ stopTimes: uint32ArrayToBytes(value.stopTimes),
108
+ pickUpDropOffTypes: value.pickUpDropOffTypes,
109
+ stops: uint32ArrayToBytes(value.stops),
64
110
  serviceRouteId: value.serviceRouteId,
65
111
  };
66
112
  });
@@ -92,7 +138,8 @@ export const deserializeStopsAdjacency = (
92
138
  ): StopsAdjacency => {
93
139
  const stopsAdjacency: StopsAdjacency = new Map();
94
140
 
95
- Object.entries(protoStopsAdjacency.stops).forEach(([key, value]) => {
141
+ Object.entries(protoStopsAdjacency.stops).forEach(([keyStr, value]) => {
142
+ const key = parseInt(keyStr, 10);
96
143
  stopsAdjacency.set(key, {
97
144
  transfers: value.transfers.map(
98
145
  (transfer: ProtoTransfer): Transfer => ({
@@ -116,21 +163,17 @@ export const deserializeRoutesAdjacency = (
116
163
  const routesAdjacency: RoutesAdjacency = new Map();
117
164
 
118
165
  Object.entries(protoRoutesAdjacency.routes).forEach(([key, value]) => {
166
+ const stops = bytesToUint32Array(value.stops);
167
+ const indices = new Map<number, number>();
168
+ for (let i = 0; i < stops.length; i++) {
169
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
+ indices.set(stops[i]!, i);
171
+ }
119
172
  routesAdjacency.set(key, {
120
- stopTimes: value.stopTimes.map((stopTimes) => ({
121
- arrival: Time.fromSeconds(stopTimes.arrival),
122
- departure: Time.fromSeconds(stopTimes.departure),
123
- pickUpType:
124
- stopTimes.pickUpType !== undefined
125
- ? parsePickUpDropOffType(stopTimes.pickUpType)
126
- : 'REGULAR',
127
- dropOffType:
128
- stopTimes.dropOffType !== undefined
129
- ? parsePickUpDropOffType(stopTimes.dropOffType)
130
- : 'REGULAR',
131
- })),
132
- stops: value.stops,
133
- stopIndices: new Map(value.stops.map((stop, index) => [stop, index])),
173
+ stopTimes: bytesToUint32Array(value.stopTimes),
174
+ pickUpDropOffTypes: value.pickUpDropOffTypes,
175
+ stops: stops,
176
+ stopIndices: indices,
134
177
  serviceRouteId: value.serviceRouteId,
135
178
  });
136
179
  });
@@ -233,33 +276,3 @@ const serializeRouteType = (type: RouteType): ProtoRouteType => {
233
276
  return ProtoRouteType.MONORAIL;
234
277
  }
235
278
  };
236
-
237
- const parsePickUpDropOffType = (
238
- type: ProtoPickUpDropOffType,
239
- ): PickUpDropOffType => {
240
- switch (type) {
241
- case ProtoPickUpDropOffType.MUST_PHONE_AGENCY:
242
- return 'MUST_PHONE_AGENCY';
243
- case ProtoPickUpDropOffType.MUST_COORDINATE_WITH_DRIVER:
244
- return 'MUST_COORDINATE_WITH_DRIVER';
245
- case ProtoPickUpDropOffType.NOT_AVAILABLE:
246
- return 'NOT_AVAILABLE';
247
- default:
248
- return 'REGULAR';
249
- }
250
- };
251
-
252
- const serializePickUpDropOffType = (
253
- type: PickUpDropOffType,
254
- ): ProtoPickUpDropOffType | undefined => {
255
- switch (type) {
256
- case 'REGULAR':
257
- return undefined;
258
- case 'NOT_AVAILABLE':
259
- return ProtoPickUpDropOffType.NOT_AVAILABLE;
260
- case 'MUST_COORDINATE_WITH_DRIVER':
261
- return ProtoPickUpDropOffType.MUST_COORDINATE_WITH_DRIVER;
262
- case 'MUST_PHONE_AGENCY':
263
- return ProtoPickUpDropOffType.MUST_PHONE_AGENCY;
264
- }
265
- };
@@ -2,23 +2,28 @@ syntax = "proto3";
2
2
 
3
3
  package minotor.timetable;
4
4
 
5
- enum PickUpDropOffType {
6
- NOT_AVAILABLE = 0;
7
- MUST_PHONE_AGENCY = 1;
8
- MUST_COORDINATE_WITH_DRIVER = 2;
9
- }
10
-
11
- message StopTimes {
12
- int32 arrival = 1;
13
- int32 departure = 2;
14
- optional PickUpDropOffType pickUpType = 3;
15
- optional PickUpDropOffType dropOffType = 4;
16
- }
17
-
18
5
  message Route {
19
- repeated StopTimes stopTimes = 1;
20
- repeated string stops = 2;
21
- string serviceRouteId = 3;
6
+ /**
7
+ * Arrivals and departures encoded as a 32 bit uint array.
8
+ * Format: [arrival1, departure1, arrival2, departure2, etc.]
9
+ */
10
+ bytes stopTimes = 1;
11
+ /**
12
+ * PickUp and DropOff types represented as an 8 bit uint array.
13
+ * Values:
14
+ * 0: REGULAR
15
+ * 1: NOT_AVAILABLE
16
+ * 2: MUST_PHONE_AGENCY
17
+ * 3: MUST_COORDINATE_WITH_DRIVER
18
+ * Format: [pickupTypeStop1, dropOffTypeStop1, pickupTypeStop2, dropOffTypeStop2, etc.]
19
+ */
20
+ bytes pickUpDropOffTypes = 2;
21
+ /**
22
+ * Stops encoded as a 32 bit uint array.
23
+ * Format: [stop1, stop2, stop3, etc.]
24
+ */
25
+ bytes stops = 3;
26
+ string serviceRouteId = 4;
22
27
  }
23
28
 
24
29
  message RoutesAdjacency {
@@ -33,7 +38,7 @@ enum TransferType {
33
38
  }
34
39
 
35
40
  message Transfer {
36
- string destination = 1;
41
+ uint32 destination = 1;
37
42
  TransferType type = 2;
38
43
  optional int32 minTransferTime = 3;
39
44
  }