minotor 2.0.1 → 3.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 +9 -3
- package/dist/cli.mjs +89 -47
- package/dist/cli.mjs.map +1 -1
- package/dist/parser.cjs.js +82 -40
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +82 -40
- 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/timetable/time.d.ts +18 -9
- package/dist/timetable/timetable.d.ts +3 -3
- package/package.json +1 -1
- package/src/gtfs/__tests__/parser.test.ts +4 -4
- package/src/gtfs/__tests__/time.test.ts +2 -2
- package/src/gtfs/__tests__/trips.test.ts +36 -36
- package/src/gtfs/trips.ts +4 -4
- package/src/routing/__tests__/route.test.ts +4 -4
- package/src/routing/__tests__/router.test.ts +60 -60
- package/src/routing/result.ts +2 -2
- package/src/routing/router.ts +5 -5
- package/src/timetable/__tests__/io.test.ts +12 -12
- package/src/timetable/__tests__/timetable.test.ts +16 -16
- package/src/timetable/io.ts +46 -2
- package/src/timetable/time.ts +51 -30
- package/src/timetable/timetable.ts +7 -7
|
@@ -37,15 +37,15 @@ describe('timetable io', () => {
|
|
|
37
37
|
[
|
|
38
38
|
'route1',
|
|
39
39
|
{
|
|
40
|
-
stopTimes: new
|
|
41
|
-
Time.fromHMS(
|
|
42
|
-
Time.fromHMS(
|
|
43
|
-
Time.fromHMS(
|
|
44
|
-
Time.fromHMS(
|
|
45
|
-
Time.fromHMS(
|
|
46
|
-
Time.fromHMS(
|
|
47
|
-
Time.fromHMS(
|
|
48
|
-
Time.fromHMS(
|
|
40
|
+
stopTimes: new Uint16Array([
|
|
41
|
+
Time.fromHMS(16, 40, 0).toMinutes(),
|
|
42
|
+
Time.fromHMS(16, 50, 0).toMinutes(),
|
|
43
|
+
Time.fromHMS(17, 20, 0).toMinutes(),
|
|
44
|
+
Time.fromHMS(17, 30, 0).toMinutes(),
|
|
45
|
+
Time.fromHMS(18, 0, 0).toMinutes(),
|
|
46
|
+
Time.fromHMS(18, 10, 0).toMinutes(),
|
|
47
|
+
Time.fromHMS(19, 0, 0).toMinutes(),
|
|
48
|
+
Time.fromHMS(19, 10, 0).toMinutes(),
|
|
49
49
|
]),
|
|
50
50
|
pickUpDropOffTypes: new Uint8Array([
|
|
51
51
|
0,
|
|
@@ -68,11 +68,11 @@ describe('timetable io', () => {
|
|
|
68
68
|
[
|
|
69
69
|
'route2',
|
|
70
70
|
{
|
|
71
|
-
stopTimes: new
|
|
72
|
-
Time.fromHMS(
|
|
73
|
-
Time.fromHMS(
|
|
74
|
-
Time.fromHMS(
|
|
75
|
-
Time.fromHMS(
|
|
71
|
+
stopTimes: new Uint16Array([
|
|
72
|
+
Time.fromHMS(18, 20, 0).toMinutes(),
|
|
73
|
+
Time.fromHMS(18, 30, 0).toMinutes(),
|
|
74
|
+
Time.fromHMS(23, 20, 0).toMinutes(),
|
|
75
|
+
Time.fromHMS(23, 30, 0).toMinutes(),
|
|
76
76
|
]),
|
|
77
77
|
pickUpDropOffTypes: new Uint8Array([
|
|
78
78
|
0,
|
|
@@ -121,7 +121,7 @@ describe('timetable io', () => {
|
|
|
121
121
|
it('should find the earliest trip for stop1 on route1 after a specific time', () => {
|
|
122
122
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
123
123
|
const route = sampleTimetable.getRoute('route1')!;
|
|
124
|
-
const afterTime = Time.fromHMS(
|
|
124
|
+
const afterTime = Time.fromHMS(17, 0, 0);
|
|
125
125
|
const tripIndex = sampleTimetable.findEarliestTrip(
|
|
126
126
|
route,
|
|
127
127
|
1,
|
|
@@ -134,7 +134,7 @@ describe('timetable io', () => {
|
|
|
134
134
|
it('should return undefined if no valid trip exists after a specific time', () => {
|
|
135
135
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
136
136
|
const route = sampleTimetable.getRoute('route1')!;
|
|
137
|
-
const afterTime = Time.fromHMS(
|
|
137
|
+
const afterTime = Time.fromHMS(23, 40, 0);
|
|
138
138
|
const tripIndex = sampleTimetable.findEarliestTrip(
|
|
139
139
|
route,
|
|
140
140
|
1,
|
package/src/timetable/io.ts
CHANGED
|
@@ -70,6 +70,50 @@ function bytesToUint32Array(bytes: Uint8Array): Uint32Array {
|
|
|
70
70
|
return result;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function uint16ArrayToBytes(array: Uint16Array): Uint8Array {
|
|
74
|
+
if (isLittleEndian === STANDARD_ENDIANNESS) {
|
|
75
|
+
return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If endianness doesn't match, we need to swap byte order
|
|
79
|
+
const result = new Uint8Array(array.length * 2);
|
|
80
|
+
const view = new DataView(result.buffer);
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < array.length; i++) {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
84
|
+
view.setUint16(i * 2, array[i]!, STANDARD_ENDIANNESS);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function bytesToUint16Array(bytes: Uint8Array): Uint16Array {
|
|
91
|
+
if (bytes.byteLength % 2 !== 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'Byte array length must be a multiple of 2 to convert to Uint16Array',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If system endianness matches our standard, we can create a view directly
|
|
98
|
+
if (isLittleEndian === STANDARD_ENDIANNESS) {
|
|
99
|
+
return new Uint16Array(
|
|
100
|
+
bytes.buffer,
|
|
101
|
+
bytes.byteOffset,
|
|
102
|
+
bytes.byteLength / 2,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If endianness doesn't match, we need to swap byte order
|
|
107
|
+
const result = new Uint16Array(bytes.byteLength / 2);
|
|
108
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < result.length; i++) {
|
|
111
|
+
result[i] = view.getUint16(i * 2, STANDARD_ENDIANNESS);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
73
117
|
export const serializeStopsAdjacency = (
|
|
74
118
|
stopsAdjacency: StopsAdjacency,
|
|
75
119
|
): ProtoStopsAdjacency => {
|
|
@@ -104,7 +148,7 @@ export const serializeRoutesAdjacency = (
|
|
|
104
148
|
|
|
105
149
|
routesAdjacency.forEach((value: Route, key: string) => {
|
|
106
150
|
protoRoutesAdjacency.routes[key] = {
|
|
107
|
-
stopTimes:
|
|
151
|
+
stopTimes: uint16ArrayToBytes(value.stopTimes),
|
|
108
152
|
pickUpDropOffTypes: value.pickUpDropOffTypes,
|
|
109
153
|
stops: uint32ArrayToBytes(value.stops),
|
|
110
154
|
serviceRouteId: value.serviceRouteId,
|
|
@@ -170,7 +214,7 @@ export const deserializeRoutesAdjacency = (
|
|
|
170
214
|
indices.set(stops[i]!, i);
|
|
171
215
|
}
|
|
172
216
|
routesAdjacency.set(key, {
|
|
173
|
-
stopTimes:
|
|
217
|
+
stopTimes: bytesToUint16Array(value.stopTimes),
|
|
174
218
|
pickUpDropOffTypes: value.pickUpDropOffTypes,
|
|
175
219
|
stops: stops,
|
|
176
220
|
stopIndices: indices,
|
package/src/timetable/time.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Duration } from './duration.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A class representing a time
|
|
4
|
+
* A class representing a time as minutes since midnight.
|
|
5
5
|
*/
|
|
6
6
|
export class Time {
|
|
7
|
-
|
|
7
|
+
/*
|
|
8
|
+
* Number of minutes since midnight.
|
|
9
|
+
Note that this value can go beyond 3600 to model services overlapping with the next day.
|
|
10
|
+
*/
|
|
11
|
+
private minutesSinceMidnight: number;
|
|
8
12
|
/**
|
|
9
13
|
* Gets the infinity time as a Time instance.
|
|
10
14
|
* This represents a time that is conceptually beyond any real possible time.
|
|
@@ -17,28 +21,29 @@ export class Time {
|
|
|
17
21
|
/**
|
|
18
22
|
* Gets the midnight time as a Time instance.
|
|
19
23
|
*
|
|
20
|
-
* @returns A Time instance representing midnight
|
|
24
|
+
* @returns A Time instance representing midnight.
|
|
21
25
|
*/
|
|
22
26
|
static origin(): Time {
|
|
23
27
|
return new Time(0);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
private constructor(
|
|
27
|
-
this.
|
|
30
|
+
private constructor(minutes: number) {
|
|
31
|
+
this.minutesSinceMidnight = minutes;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
|
-
* Creates a Time instance from the number of
|
|
35
|
+
* Creates a Time instance from the number of minutes since midnight.
|
|
32
36
|
*
|
|
33
|
-
* @param
|
|
37
|
+
* @param minutes - The number of minutes since midnight.
|
|
34
38
|
* @returns A Time instance representing the specified time.
|
|
35
39
|
*/
|
|
36
|
-
static
|
|
37
|
-
return new Time(
|
|
40
|
+
static fromMinutes(minutes: number): Time {
|
|
41
|
+
return new Time(minutes);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
45
|
* Creates a Time instance from hours, minutes, and seconds.
|
|
46
|
+
* Rounds to the closest minute as times are represented in minutes from midnight.
|
|
42
47
|
*
|
|
43
48
|
* @param hours - The hours component of the time.
|
|
44
49
|
* @param minutes - The minutes component of the time.
|
|
@@ -57,7 +62,25 @@ export class Time {
|
|
|
57
62
|
'Invalid time. Ensure hours, minutes, and seconds are valid values.',
|
|
58
63
|
);
|
|
59
64
|
}
|
|
60
|
-
|
|
65
|
+
const totalSeconds = seconds + 60 * minutes + 3600 * hours;
|
|
66
|
+
const roundedMinutes = Math.round(totalSeconds / 60);
|
|
67
|
+
return new Time(roundedMinutes);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a Time instance from hours, minutes.
|
|
72
|
+
*
|
|
73
|
+
* @param hours - The hours component of the time.
|
|
74
|
+
* @param minutes - The minutes component of the time.
|
|
75
|
+
* @returns A Time instance representing the specified time.
|
|
76
|
+
*/
|
|
77
|
+
static fromHM(hours: number, minutes: number): Time {
|
|
78
|
+
if (hours < 0 || minutes < 0 || minutes >= 60) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Invalid time. Ensure hours and minutes are valid values.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return new Time(minutes + hours * 60);
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
/**
|
|
@@ -70,7 +93,7 @@ export class Time {
|
|
|
70
93
|
const hours = date.getHours();
|
|
71
94
|
const minutes = date.getMinutes();
|
|
72
95
|
const seconds = date.getSeconds();
|
|
73
|
-
return
|
|
96
|
+
return Time.fromHMS(hours, minutes, seconds);
|
|
74
97
|
}
|
|
75
98
|
|
|
76
99
|
/**
|
|
@@ -95,7 +118,7 @@ export class Time {
|
|
|
95
118
|
const hours = parseInt(hoursStr, 10);
|
|
96
119
|
const minutes = parseInt(minutesStr, 10);
|
|
97
120
|
const seconds = secondsStr !== undefined ? parseInt(secondsStr, 10) : 0;
|
|
98
|
-
return
|
|
121
|
+
return Time.fromHMS(hours, minutes, seconds);
|
|
99
122
|
}
|
|
100
123
|
|
|
101
124
|
/**
|
|
@@ -104,22 +127,20 @@ export class Time {
|
|
|
104
127
|
* @returns A string representing the time.
|
|
105
128
|
*/
|
|
106
129
|
toString(): string {
|
|
107
|
-
const hours = Math.floor(this.
|
|
108
|
-
const minutes = Math.floor(
|
|
109
|
-
|
|
110
|
-
return `${hours.toString().padStart(2, '0')}:${minutes
|
|
111
|
-
.toString()
|
|
112
|
-
.padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
130
|
+
const hours = Math.floor(this.minutesSinceMidnight / 60);
|
|
131
|
+
const minutes = Math.floor(this.minutesSinceMidnight % 60);
|
|
132
|
+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
113
133
|
}
|
|
114
134
|
|
|
115
135
|
/**
|
|
116
|
-
*
|
|
136
|
+
* Converts the Time instance to the total number of minutes since midnight, rounded to the closest minute.
|
|
117
137
|
*
|
|
118
|
-
* @returns The time in
|
|
138
|
+
* @returns The time in minutes since midnight.
|
|
119
139
|
*/
|
|
120
|
-
|
|
121
|
-
return this.
|
|
140
|
+
toMinutes(): number {
|
|
141
|
+
return this.minutesSinceMidnight;
|
|
122
142
|
}
|
|
143
|
+
|
|
123
144
|
/**
|
|
124
145
|
* Adds a Duration to the current Time instance and returns a new Time instance.
|
|
125
146
|
*
|
|
@@ -127,8 +148,8 @@ export class Time {
|
|
|
127
148
|
* @returns A new Time instance with the added duration.
|
|
128
149
|
*/
|
|
129
150
|
plus(duration: Duration): Time {
|
|
130
|
-
const totalSeconds = this.
|
|
131
|
-
return new Time(totalSeconds);
|
|
151
|
+
const totalSeconds = this.minutesSinceMidnight * 60 + duration.toSeconds();
|
|
152
|
+
return new Time(Math.round(totalSeconds / 60));
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
/**
|
|
@@ -138,11 +159,11 @@ export class Time {
|
|
|
138
159
|
* @returns A new Time instance with the subtracted duration.
|
|
139
160
|
*/
|
|
140
161
|
minus(duration: Duration): Time {
|
|
141
|
-
let totalSeconds = this.
|
|
162
|
+
let totalSeconds = this.minutesSinceMidnight * 60 - duration.toSeconds();
|
|
142
163
|
if (totalSeconds < 0) {
|
|
143
164
|
totalSeconds += 24 * 3600; // Adjust for negative time to loop back to previous day
|
|
144
165
|
}
|
|
145
|
-
return new Time(totalSeconds);
|
|
166
|
+
return new Time(Math.round(totalSeconds / 60));
|
|
146
167
|
}
|
|
147
168
|
|
|
148
169
|
/**
|
|
@@ -152,8 +173,8 @@ export class Time {
|
|
|
152
173
|
* @returns A Duration instance representing the time difference.
|
|
153
174
|
*/
|
|
154
175
|
diff(otherTime: Time): Duration {
|
|
155
|
-
const
|
|
156
|
-
return Duration.fromSeconds(Math.abs(
|
|
176
|
+
const totalMinutes = this.minutesSinceMidnight - otherTime.toMinutes();
|
|
177
|
+
return Duration.fromSeconds(Math.abs(totalMinutes * 60));
|
|
157
178
|
}
|
|
158
179
|
|
|
159
180
|
/**
|
|
@@ -167,7 +188,7 @@ export class Time {
|
|
|
167
188
|
throw new Error('At least one Time instance is required.');
|
|
168
189
|
}
|
|
169
190
|
return times.reduce((maxTime, currentTime) => {
|
|
170
|
-
return currentTime.
|
|
191
|
+
return currentTime.toMinutes() > maxTime.toMinutes()
|
|
171
192
|
? currentTime
|
|
172
193
|
: maxTime;
|
|
173
194
|
});
|
|
@@ -184,7 +205,7 @@ export class Time {
|
|
|
184
205
|
throw new Error('At least one Time instance is required.');
|
|
185
206
|
}
|
|
186
207
|
return times.reduce((minTime, currentTime) => {
|
|
187
|
-
return currentTime.
|
|
208
|
+
return currentTime.toMinutes() < minTime.toMinutes()
|
|
188
209
|
? currentTime
|
|
189
210
|
: minTime;
|
|
190
211
|
});
|
|
@@ -29,10 +29,10 @@ export type PickUpDropOffType =
|
|
|
29
29
|
|
|
30
30
|
export type Route = {
|
|
31
31
|
/**
|
|
32
|
-
* Arrivals and departures encoded as
|
|
32
|
+
* Arrivals and departures encoded as minutes from midnight.
|
|
33
33
|
* Format: [arrival1, departure1, arrival2, departure2, etc.]
|
|
34
34
|
*/
|
|
35
|
-
stopTimes:
|
|
35
|
+
stopTimes: Uint16Array;
|
|
36
36
|
/**
|
|
37
37
|
* PickUp and DropOff types represented as a binary Uint8Array.
|
|
38
38
|
* Values:
|
|
@@ -127,7 +127,7 @@ export const ALL_TRANSPORT_MODES: RouteType[] = [
|
|
|
127
127
|
'MONORAIL',
|
|
128
128
|
];
|
|
129
129
|
|
|
130
|
-
export const CURRENT_VERSION = '0.0.
|
|
130
|
+
export const CURRENT_VERSION = '0.0.3';
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* The internal transit timetable format
|
|
@@ -285,7 +285,7 @@ export class Timetable {
|
|
|
285
285
|
const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
|
|
286
286
|
const departure = route.stopTimes[stopTimeIndex * 2 + 1]!;
|
|
287
287
|
const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2]!;
|
|
288
|
-
if (departure >= after.
|
|
288
|
+
if (departure >= after.toMinutes() && pickUpType !== NOT_AVAILABLE) {
|
|
289
289
|
return tripIndex;
|
|
290
290
|
}
|
|
291
291
|
}
|
|
@@ -301,16 +301,16 @@ export class Timetable {
|
|
|
301
301
|
const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
|
|
302
302
|
const departure = route.stopTimes[stopTimeIndex * 2 + 1]!;
|
|
303
303
|
const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2]!;
|
|
304
|
-
if (departure < after.
|
|
304
|
+
if (departure < after.toMinutes()) {
|
|
305
305
|
break;
|
|
306
306
|
}
|
|
307
307
|
if (
|
|
308
308
|
pickUpType !== NOT_AVAILABLE &&
|
|
309
309
|
(earliestDeparture === undefined ||
|
|
310
|
-
departure < earliestDeparture.
|
|
310
|
+
departure < earliestDeparture.toMinutes())
|
|
311
311
|
) {
|
|
312
312
|
earliestTripIndex = tripIndex;
|
|
313
|
-
earliestDeparture = Time.
|
|
313
|
+
earliestDeparture = Time.fromMinutes(departure);
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
return earliestTripIndex;
|