minotor 2.0.0 → 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.
@@ -37,15 +37,15 @@ describe('timetable io', () => {
37
37
  [
38
38
  'route1',
39
39
  {
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(),
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 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(),
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(0, 25, 0);
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(0, 58, 20);
137
+ const afterTime = Time.fromHMS(23, 40, 0);
138
138
  const tripIndex = sampleTimetable.findEarliestTrip(
139
139
  route,
140
140
  1,
@@ -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: uint32ArrayToBytes(value.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: bytesToUint32Array(value.stopTimes),
217
+ stopTimes: bytesToUint16Array(value.stopTimes),
174
218
  pickUpDropOffTypes: value.pickUpDropOffTypes,
175
219
  stops: stops,
176
220
  stopIndices: indices,
@@ -1,10 +1,14 @@
1
1
  import { Duration } from './duration.js';
2
2
 
3
3
  /**
4
- * A class representing a time in hours, minutes, and seconds.
4
+ * A class representing a time as minutes since midnight.
5
5
  */
6
6
  export class Time {
7
- private secondsSinceMidnight: number;
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 (00:00:00).
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(seconds: number) {
27
- this.secondsSinceMidnight = seconds;
30
+ private constructor(minutes: number) {
31
+ this.minutesSinceMidnight = minutes;
28
32
  }
29
33
 
30
34
  /**
31
- * Creates a Time instance from the number of seconds since midnight.
35
+ * Creates a Time instance from the number of minutes since midnight.
32
36
  *
33
- * @param seconds - The number of seconds since midnight.
37
+ * @param minutes - The number of minutes since midnight.
34
38
  * @returns A Time instance representing the specified time.
35
39
  */
36
- static fromSeconds(seconds: number): Time {
37
- return new Time(seconds);
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
- return new Time(seconds + 60 * minutes + 3600 * hours);
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 new Time(seconds + 60 * minutes + 3600 * hours);
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 new Time(seconds + 60 * minutes + 3600 * hours);
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.secondsSinceMidnight / 3600);
108
- const minutes = Math.floor((this.secondsSinceMidnight % 3600) / 60);
109
- const seconds = this.secondsSinceMidnight % 60;
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
- * Gets the time as the number of seconds since midnight.
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 seconds since midnight.
138
+ * @returns The time in minutes since midnight.
119
139
  */
120
- toSeconds(): number {
121
- return this.secondsSinceMidnight;
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.secondsSinceMidnight + duration.toSeconds();
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.secondsSinceMidnight - duration.toSeconds();
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 totalSeconds = this.secondsSinceMidnight - otherTime.toSeconds();
156
- return Duration.fromSeconds(Math.abs(totalSeconds));
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.toSeconds() > maxTime.toSeconds()
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.toSeconds() < minTime.toSeconds()
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 a binary array.
32
+ * Arrivals and departures encoded as minutes from midnight.
33
33
  * Format: [arrival1, departure1, arrival2, departure2, etc.]
34
34
  */
35
- stopTimes: Uint32Array;
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.2';
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.toSeconds() && pickUpType !== NOT_AVAILABLE) {
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.toSeconds()) {
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.toSeconds())
310
+ departure < earliestDeparture.toMinutes())
311
311
  ) {
312
312
  earliestTripIndex = tripIndex;
313
- earliestDeparture = Time.fromSeconds(departure);
313
+ earliestDeparture = Time.fromMinutes(departure);
314
314
  }
315
315
  }
316
316
  return earliestTripIndex;