minotor 1.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 (131) hide show
  1. package/.cspell.json +43 -0
  2. package/.czrc +3 -0
  3. package/.editorconfig +10 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +4 -0
  8. package/.github/workflows/minotor.yml +85 -0
  9. package/.prettierrc +7 -0
  10. package/.releaserc.json +27 -0
  11. package/CHANGELOG.md +6 -0
  12. package/LICENSE +21 -0
  13. package/README.md +166 -0
  14. package/dist/bundle.cjs.js +16507 -0
  15. package/dist/bundle.cjs.js.map +1 -0
  16. package/dist/bundle.esm.js +16496 -0
  17. package/dist/bundle.esm.js.map +1 -0
  18. package/dist/bundle.umd.js +2 -0
  19. package/dist/bundle.umd.js.map +1 -0
  20. package/dist/cli/__tests__/minotor.test.d.ts +1 -0
  21. package/dist/cli/minotor.d.ts +5 -0
  22. package/dist/cli/repl.d.ts +1 -0
  23. package/dist/cli/utils.d.ts +3 -0
  24. package/dist/cli.mjs +20504 -0
  25. package/dist/cli.mjs.map +1 -0
  26. package/dist/gtfs/__tests__/parser.test.d.ts +1 -0
  27. package/dist/gtfs/__tests__/routes.test.d.ts +1 -0
  28. package/dist/gtfs/__tests__/services.test.d.ts +1 -0
  29. package/dist/gtfs/__tests__/stops.test.d.ts +1 -0
  30. package/dist/gtfs/__tests__/time.test.d.ts +1 -0
  31. package/dist/gtfs/__tests__/transfers.test.d.ts +1 -0
  32. package/dist/gtfs/__tests__/trips.test.d.ts +1 -0
  33. package/dist/gtfs/__tests__/utils.test.d.ts +1 -0
  34. package/dist/gtfs/parser.d.ts +34 -0
  35. package/dist/gtfs/profiles/__tests__/ch.test.d.ts +1 -0
  36. package/dist/gtfs/profiles/ch.d.ts +2 -0
  37. package/dist/gtfs/profiles/standard.d.ts +2 -0
  38. package/dist/gtfs/routes.d.ts +11 -0
  39. package/dist/gtfs/services.d.ts +19 -0
  40. package/dist/gtfs/stops.d.ts +20 -0
  41. package/dist/gtfs/time.d.ts +17 -0
  42. package/dist/gtfs/transfers.d.ts +22 -0
  43. package/dist/gtfs/trips.d.ts +26 -0
  44. package/dist/gtfs/utils.d.ts +21 -0
  45. package/dist/index.d.ts +11 -0
  46. package/dist/routing/__tests__/router.test.d.ts +1 -0
  47. package/dist/routing/plotter.d.ts +11 -0
  48. package/dist/routing/query.d.ts +35 -0
  49. package/dist/routing/result.d.ts +28 -0
  50. package/dist/routing/route.d.ts +25 -0
  51. package/dist/routing/router.d.ts +33 -0
  52. package/dist/stops/__tests__/io.test.d.ts +1 -0
  53. package/dist/stops/__tests__/stopFinder.test.d.ts +1 -0
  54. package/dist/stops/i18n.d.ts +10 -0
  55. package/dist/stops/io.d.ts +4 -0
  56. package/dist/stops/proto/stops.d.ts +53 -0
  57. package/dist/stops/stops.d.ts +16 -0
  58. package/dist/stops/stopsIndex.d.ts +52 -0
  59. package/dist/timetable/__tests__/io.test.d.ts +1 -0
  60. package/dist/timetable/__tests__/timetable.test.d.ts +1 -0
  61. package/dist/timetable/duration.d.ts +51 -0
  62. package/dist/timetable/io.d.ts +8 -0
  63. package/dist/timetable/proto/timetable.d.ts +122 -0
  64. package/dist/timetable/time.d.ts +98 -0
  65. package/dist/timetable/timetable.d.ts +82 -0
  66. package/dist/umdIndex.d.ts +9 -0
  67. package/eslint.config.mjs +52 -0
  68. package/package.json +109 -0
  69. package/rollup.config.js +44 -0
  70. package/src/cli/__tests__/minotor.test.ts +23 -0
  71. package/src/cli/minotor.ts +112 -0
  72. package/src/cli/repl.ts +200 -0
  73. package/src/cli/utils.ts +36 -0
  74. package/src/gtfs/__tests__/parser.test.ts +591 -0
  75. package/src/gtfs/__tests__/resources/sample-feed/agency.txt +2 -0
  76. package/src/gtfs/__tests__/resources/sample-feed/calendar.txt +3 -0
  77. package/src/gtfs/__tests__/resources/sample-feed/calendar_dates.txt +2 -0
  78. package/src/gtfs/__tests__/resources/sample-feed/fare_attributes.txt +3 -0
  79. package/src/gtfs/__tests__/resources/sample-feed/fare_rules.txt +5 -0
  80. package/src/gtfs/__tests__/resources/sample-feed/frequencies.txt +12 -0
  81. package/src/gtfs/__tests__/resources/sample-feed/routes.txt +6 -0
  82. package/src/gtfs/__tests__/resources/sample-feed/sample-feed.zip +0 -0
  83. package/src/gtfs/__tests__/resources/sample-feed/shapes.txt +1 -0
  84. package/src/gtfs/__tests__/resources/sample-feed/stop_times.txt +34 -0
  85. package/src/gtfs/__tests__/resources/sample-feed/stops.txt +10 -0
  86. package/src/gtfs/__tests__/resources/sample-feed/trips.txt +13 -0
  87. package/src/gtfs/__tests__/resources/sample-feed.zip +0 -0
  88. package/src/gtfs/__tests__/routes.test.ts +63 -0
  89. package/src/gtfs/__tests__/services.test.ts +209 -0
  90. package/src/gtfs/__tests__/stops.test.ts +177 -0
  91. package/src/gtfs/__tests__/time.test.ts +27 -0
  92. package/src/gtfs/__tests__/transfers.test.ts +117 -0
  93. package/src/gtfs/__tests__/trips.test.ts +463 -0
  94. package/src/gtfs/__tests__/utils.test.ts +13 -0
  95. package/src/gtfs/parser.ts +154 -0
  96. package/src/gtfs/profiles/__tests__/ch.test.ts +43 -0
  97. package/src/gtfs/profiles/ch.ts +70 -0
  98. package/src/gtfs/profiles/standard.ts +39 -0
  99. package/src/gtfs/routes.ts +48 -0
  100. package/src/gtfs/services.ts +98 -0
  101. package/src/gtfs/stops.ts +112 -0
  102. package/src/gtfs/time.ts +33 -0
  103. package/src/gtfs/transfers.ts +102 -0
  104. package/src/gtfs/trips.ts +228 -0
  105. package/src/gtfs/utils.ts +42 -0
  106. package/src/index.ts +28 -0
  107. package/src/routing/__tests__/router.test.ts +760 -0
  108. package/src/routing/plotter.ts +70 -0
  109. package/src/routing/query.ts +74 -0
  110. package/src/routing/result.ts +108 -0
  111. package/src/routing/route.ts +94 -0
  112. package/src/routing/router.ts +262 -0
  113. package/src/stops/__tests__/io.test.ts +43 -0
  114. package/src/stops/__tests__/stopFinder.test.ts +185 -0
  115. package/src/stops/i18n.ts +40 -0
  116. package/src/stops/io.ts +94 -0
  117. package/src/stops/proto/stops.proto +26 -0
  118. package/src/stops/proto/stops.ts +445 -0
  119. package/src/stops/stops.ts +24 -0
  120. package/src/stops/stopsIndex.ts +151 -0
  121. package/src/timetable/__tests__/io.test.ts +175 -0
  122. package/src/timetable/__tests__/timetable.test.ts +180 -0
  123. package/src/timetable/duration.ts +85 -0
  124. package/src/timetable/io.ts +265 -0
  125. package/src/timetable/proto/timetable.proto +76 -0
  126. package/src/timetable/proto/timetable.ts +1304 -0
  127. package/src/timetable/time.ts +192 -0
  128. package/src/timetable/timetable.ts +286 -0
  129. package/src/umdIndex.ts +14 -0
  130. package/tsconfig.build.json +4 -0
  131. package/tsconfig.json +21 -0
@@ -0,0 +1,192 @@
1
+ import { Duration } from './duration.js';
2
+
3
+ /**
4
+ * A class representing a time in hours, minutes, and seconds.
5
+ */
6
+ export class Time {
7
+ private secondsSinceMidnight: number;
8
+ /**
9
+ * Gets the infinity time as a Time instance.
10
+ * This represents a time that is conceptually beyond any real possible time.
11
+ *
12
+ * @returns A Time instance representing an "infinity" time.
13
+ */
14
+ static infinity(): Time {
15
+ return new Time(Number.MAX_SAFE_INTEGER);
16
+ }
17
+ /**
18
+ * Gets the midnight time as a Time instance.
19
+ *
20
+ * @returns A Time instance representing midnight (00:00:00).
21
+ */
22
+ static origin(): Time {
23
+ return new Time(0);
24
+ }
25
+
26
+ private constructor(seconds: number) {
27
+ this.secondsSinceMidnight = seconds;
28
+ }
29
+
30
+ /**
31
+ * Creates a Time instance from the number of seconds since midnight.
32
+ *
33
+ * @param seconds - The number of seconds since midnight.
34
+ * @returns A Time instance representing the specified time.
35
+ */
36
+ static fromSeconds(seconds: number): Time {
37
+ return new Time(seconds);
38
+ }
39
+
40
+ /**
41
+ * Creates a Time instance from hours, minutes, and seconds.
42
+ *
43
+ * @param hours - The hours component of the time.
44
+ * @param minutes - The minutes component of the time.
45
+ * @param seconds - The seconds component of the time.
46
+ * @returns A Time instance representing the specified time.
47
+ */
48
+ static fromHMS(hours: number, minutes: number, seconds: number): Time {
49
+ if (
50
+ hours < 0 ||
51
+ minutes < 0 ||
52
+ seconds < 0 ||
53
+ minutes >= 60 ||
54
+ seconds >= 60
55
+ ) {
56
+ throw new Error(
57
+ 'Invalid time. Ensure hours, minutes, and seconds are valid values.',
58
+ );
59
+ }
60
+ return new Time(seconds + 60 * minutes + 3600 * hours);
61
+ }
62
+
63
+ /**
64
+ * Parses a JavaScript Date object and creates a Time instance.
65
+ *
66
+ * @param date - A JavaScript Date object representing the time.
67
+ * @returns A Time instance representing the parsed time.
68
+ */
69
+ static fromDate(date: Date): Time {
70
+ const hours = date.getHours();
71
+ const minutes = date.getMinutes();
72
+ const seconds = date.getSeconds();
73
+ return new Time(seconds + 60 * minutes + 3600 * hours);
74
+ }
75
+
76
+ /**
77
+ * Parses a time string in the format "HH:MM:SS" or "HH:MM" and creates a Time instance.
78
+ *
79
+ * @param timeStr - A string representing the time in "HH:MM:SS" or "HH:MM" format.
80
+ * @returns A Time instance representing the parsed time.
81
+ */
82
+ static fromString(timeStr: string): Time {
83
+ const [hoursStr, minutesStr, secondsStr] = timeStr.split(':');
84
+ if (
85
+ hoursStr === undefined ||
86
+ minutesStr === undefined ||
87
+ isNaN(Number(hoursStr)) ||
88
+ isNaN(Number(minutesStr)) ||
89
+ (secondsStr !== undefined && isNaN(Number(secondsStr)))
90
+ ) {
91
+ throw new Error(
92
+ 'Input string must be in the format "HH:MM:SS" or "HH:MM".',
93
+ );
94
+ }
95
+ const hours = parseInt(hoursStr, 10);
96
+ const minutes = parseInt(minutesStr, 10);
97
+ const seconds = secondsStr !== undefined ? parseInt(secondsStr, 10) : 0;
98
+ return new Time(seconds + 60 * minutes + 3600 * hours);
99
+ }
100
+
101
+ /**
102
+ * Converts the Time instance to a string in "HH:MM:SS" format.
103
+ *
104
+ * @returns A string representing the time.
105
+ */
106
+ 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')}`;
113
+ }
114
+
115
+ /**
116
+ * Gets the time as the number of seconds since midnight.
117
+ *
118
+ * @returns The time in seconds since midnight.
119
+ */
120
+ toSeconds(): number {
121
+ return this.secondsSinceMidnight;
122
+ }
123
+ /**
124
+ * Adds a Duration to the current Time instance and returns a new Time instance.
125
+ *
126
+ * @param duration - A Duration instance representing the duration to add.
127
+ * @returns A new Time instance with the added duration.
128
+ */
129
+ plus(duration: Duration): Time {
130
+ const totalSeconds = this.secondsSinceMidnight + duration.toSeconds();
131
+ return new Time(totalSeconds);
132
+ }
133
+
134
+ /**
135
+ * Subtracts a Duration from the current Time instance and returns a new Time instance.
136
+ *
137
+ * @param duration - A Duration instance representing the duration to subtract.
138
+ * @returns A new Time instance with the subtracted duration.
139
+ */
140
+ minus(duration: Duration): Time {
141
+ let totalSeconds = this.secondsSinceMidnight - duration.toSeconds();
142
+ if (totalSeconds < 0) {
143
+ totalSeconds += 24 * 3600; // Adjust for negative time to loop back to previous day
144
+ }
145
+ return new Time(totalSeconds);
146
+ }
147
+
148
+ /**
149
+ * Subtracts another Time instance from the current Time instance and returns the Duration.
150
+ *
151
+ * @param otherTime - A Time instance representing the time to subtract.
152
+ * @returns A Duration instance representing the time difference.
153
+ */
154
+ diff(otherTime: Time): Duration {
155
+ const totalSeconds = this.secondsSinceMidnight - otherTime.toSeconds();
156
+ return Duration.fromSeconds(Math.abs(totalSeconds));
157
+ }
158
+
159
+ /**
160
+ * Computes the maximum Time instance among the provided Time instances.
161
+ *
162
+ * @param times - An array of Time instances to compare.
163
+ * @returns A Time instance representing the maximum time.
164
+ */
165
+ static max(...times: Time[]): Time {
166
+ if (times.length === 0) {
167
+ throw new Error('At least one Time instance is required.');
168
+ }
169
+ return times.reduce((maxTime, currentTime) => {
170
+ return currentTime.toSeconds() > maxTime.toSeconds()
171
+ ? currentTime
172
+ : maxTime;
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Computes the minimum Time instance among the provided Time instances.
178
+ *
179
+ * @param times - An array of Time instances to compare.
180
+ * @returns A Time instance representing the minimum time.
181
+ */
182
+ static min(...times: Time[]): Time {
183
+ if (times.length === 0) {
184
+ throw new Error('At least one Time instance is required.');
185
+ }
186
+ return times.reduce((minTime, currentTime) => {
187
+ return currentTime.toSeconds() < minTime.toSeconds()
188
+ ? currentTime
189
+ : minTime;
190
+ });
191
+ }
192
+ }
@@ -0,0 +1,286 @@
1
+ import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
2
+
3
+ import { StopId } from '../stops/stops.js';
4
+ import { Duration } from './duration.js';
5
+ import {
6
+ deserializeRoutesAdjacency,
7
+ deserializeServiceRoutesMap,
8
+ deserializeStopsAdjacency,
9
+ serializeRoutesAdjacency,
10
+ serializeServiceRoutesMap,
11
+ serializeStopsAdjacency,
12
+ } from './io.js';
13
+ import { Timetable as ProtoTimetable } from './proto/timetable.js';
14
+ import { Time } from './time.js';
15
+
16
+ export type PickUpDropOffType =
17
+ | 'REGULAR'
18
+ | 'NOT_AVAILABLE'
19
+ | 'MUST_PHONE_AGENCY'
20
+ | 'MUST_COORDINATE_WITH_DRIVER';
21
+
22
+ export type StopTimes = {
23
+ arrival: Time;
24
+ departure: Time;
25
+ pickUpType: PickUpDropOffType;
26
+ dropOffType: PickUpDropOffType;
27
+ };
28
+ export type RouteId = string;
29
+
30
+ export type IndexedStopId = {
31
+ stopId: StopId;
32
+ index: number;
33
+ };
34
+ export type Route = {
35
+ stopTimes: StopTimes[];
36
+ stops: StopId[];
37
+ stopIndices: Map<StopId, number>;
38
+ serviceRouteId: ServiceRouteId;
39
+ };
40
+ export type RoutesAdjacency = Map<RouteId, Route>;
41
+
42
+ export type TransferType =
43
+ | 'RECOMMENDED'
44
+ | 'GUARANTEED'
45
+ | 'REQUIRES_MINIMAL_TIME'
46
+ | 'IN_SEAT';
47
+
48
+ export type Transfer = {
49
+ destination: StopId;
50
+ type: TransferType;
51
+ minTransferTime?: Duration;
52
+ };
53
+
54
+ export type StopsAdjacency = Map<
55
+ StopId,
56
+ {
57
+ transfers: Transfer[];
58
+ routes: RouteId[];
59
+ }
60
+ >;
61
+
62
+ export type ServiceRouteId = string;
63
+
64
+ export type RouteType =
65
+ | 'TRAM'
66
+ | 'SUBWAY'
67
+ | 'RAIL'
68
+ | 'BUS'
69
+ | 'FERRY'
70
+ | 'CABLE_TRAM'
71
+ | 'AERIAL_LIFT'
72
+ | 'FUNICULAR'
73
+ | 'TROLLEYBUS'
74
+ | 'MONORAIL';
75
+
76
+ export type ServiceRoute = {
77
+ type: RouteType;
78
+ name: string;
79
+ };
80
+
81
+ // A service refers to a collection of trips that are displayed to riders as a single service.
82
+ // As opposed to a route which consists of the subset of trips from a service which shares the same list of stops.
83
+ // Service is here a synonym for route in the GTFS sense.
84
+ export type ServiceRoutesMap = Map<ServiceRouteId, ServiceRoute>;
85
+
86
+ // a trip index corresponds to the index of the
87
+ // first stop time in the trip modulo the number of stops
88
+ // in the given route
89
+ type TripIndex = number;
90
+
91
+ export const ALL_TRANSPORT_MODES: RouteType[] = [
92
+ 'TRAM',
93
+ 'SUBWAY',
94
+ 'RAIL',
95
+ 'BUS',
96
+ 'FERRY',
97
+ 'CABLE_TRAM',
98
+ 'AERIAL_LIFT',
99
+ 'FUNICULAR',
100
+ 'TROLLEYBUS',
101
+ 'MONORAIL',
102
+ ];
103
+
104
+ export const CURRENT_VERSION = '0.0.1';
105
+
106
+ /**
107
+ * The internal transit timetable format
108
+ * reuses some GTFS concepts for the sake of simplicity for now.
109
+ */
110
+ export class Timetable {
111
+ private readonly stopsAdjacency: StopsAdjacency;
112
+ private readonly routesAdjacency: RoutesAdjacency;
113
+ private readonly routes: ServiceRoutesMap;
114
+
115
+ constructor(
116
+ stopsAdjacency: StopsAdjacency,
117
+ routesAdjacency: RoutesAdjacency,
118
+ routes: ServiceRoutesMap,
119
+ ) {
120
+ this.stopsAdjacency = stopsAdjacency;
121
+ this.routesAdjacency = routesAdjacency;
122
+ this.routes = routes;
123
+ }
124
+
125
+ /**
126
+ * Serializes the Timetable into a binary protobuf.
127
+ *
128
+ * @returns {Uint8Array} - The serialized binary data.
129
+ */
130
+ serialize(): Uint8Array {
131
+ const protoTimetable = {
132
+ version: CURRENT_VERSION,
133
+ stopsAdjacency: serializeStopsAdjacency(this.stopsAdjacency),
134
+ routesAdjacency: serializeRoutesAdjacency(this.routesAdjacency),
135
+ routes: serializeServiceRoutesMap(this.routes),
136
+ };
137
+ const writer = new BinaryWriter();
138
+ ProtoTimetable.encode(protoTimetable, writer);
139
+ return writer.finish();
140
+ }
141
+
142
+ /**
143
+ * Deserializes a binary protobuf into a Timetable object.
144
+ *
145
+ * @param {Uint8Array} data - The binary data to deserialize.
146
+ * @returns {Timetable} - The deserialized Timetable object.
147
+ */
148
+ static fromData(data: Uint8Array): Timetable {
149
+ const reader = new BinaryReader(data);
150
+ const protoTimetable = ProtoTimetable.decode(reader);
151
+ if (protoTimetable.version !== CURRENT_VERSION) {
152
+ throw new Error(
153
+ `Unsupported timetable version ${protoTimetable.version}`,
154
+ );
155
+ }
156
+ return new Timetable(
157
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
158
+ deserializeStopsAdjacency(protoTimetable.stopsAdjacency!),
159
+ deserializeRoutesAdjacency(
160
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
161
+ protoTimetable.routesAdjacency!,
162
+ ),
163
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
164
+ deserializeServiceRoutesMap(protoTimetable.routes!),
165
+ );
166
+ }
167
+
168
+ getRoute(routeId: RouteId): Route | undefined {
169
+ return this.routesAdjacency.get(routeId);
170
+ }
171
+
172
+ getTransfers(stopId: StopId): Transfer[] {
173
+ return this.stopsAdjacency.get(stopId)?.transfers ?? [];
174
+ }
175
+
176
+ /**
177
+ * Finds routes that are reachable from a set of stop IDs.
178
+ * Also identifies the first stop available to hop on each route among
179
+ * the input stops.
180
+ */
181
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
182
+ findReachableRoutes(
183
+ fromStops: Set<StopId>,
184
+ transportModes: RouteType[] = ALL_TRANSPORT_MODES,
185
+ ): Map<RouteId, StopId> {
186
+ const reachableRoutes = new Map<RouteId, StopId>();
187
+ for (const stop of fromStops) {
188
+ const validRoutes = this.stopsAdjacency
189
+ .get(stop)
190
+ ?.routes.filter((routeId) => {
191
+ const serviceRoute = this.getServiceRouteFromRouteId(routeId);
192
+ if (!serviceRoute) {
193
+ return false;
194
+ }
195
+ return transportModes.includes(serviceRoute.type);
196
+ });
197
+ for (const routeId of validRoutes || []) {
198
+ const hopOnStop = reachableRoutes.get(routeId);
199
+ if (hopOnStop) {
200
+ // Checks if the existing hop on stop is before the current stop
201
+ const routeStopIndices =
202
+ this.routesAdjacency.get(routeId)!.stopIndices;
203
+ const stopIndex = routeStopIndices.get(stop)!;
204
+ const hopOnStopIndex = routeStopIndices.get(hopOnStop)!;
205
+ if (stopIndex < hopOnStopIndex) {
206
+ // if the current stop is before the existing hop on stop, replace it
207
+ reachableRoutes.set(routeId, stop);
208
+ }
209
+ } else {
210
+ reachableRoutes.set(routeId, stop);
211
+ }
212
+ }
213
+ }
214
+ return reachableRoutes;
215
+ }
216
+
217
+ getServiceRouteFromRouteId(routeId: RouteId): ServiceRoute | undefined {
218
+ const route = this.routesAdjacency.get(routeId);
219
+ if (!route) {
220
+ console.warn(`Route ${routeId} not found.`);
221
+ return undefined;
222
+ }
223
+ return this.routes.get(route.serviceRouteId);
224
+ }
225
+
226
+ getServiceRoute(serviceRouteId: ServiceRouteId): ServiceRoute | undefined {
227
+ return this.routes.get(serviceRouteId);
228
+ }
229
+
230
+ /**
231
+ * Finds the earliest trip that can be taken from a specific stop on a given route,
232
+ * optionally constrained by a latest trip index and a time before which the trip
233
+ * should not depart.
234
+ */
235
+ findEarliestTrip(
236
+ route: Route,
237
+ stopId: StopId,
238
+ beforeTrip?: TripIndex,
239
+ after: Time = Time.origin(),
240
+ ): TripIndex | undefined {
241
+ const stopIndex = route.stopIndices.get(stopId)!;
242
+
243
+ const stopsNumber = route.stops.length;
244
+
245
+ if (beforeTrip === undefined) {
246
+ for (
247
+ let tripIndex = 0;
248
+ tripIndex < route.stopTimes.length / stopsNumber;
249
+ tripIndex++
250
+ ) {
251
+ const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
252
+ const stopTime = route.stopTimes[stopTimeIndex]!;
253
+ if (
254
+ stopTime.departure > after &&
255
+ stopTime.pickUpType !== 'NOT_AVAILABLE'
256
+ ) {
257
+ return tripIndex;
258
+ }
259
+ }
260
+ return undefined;
261
+ } else {
262
+ let earliestTripIndex: TripIndex | undefined;
263
+ let earliestDeparture: Time | undefined;
264
+ for (
265
+ let tripIndex = beforeTrip; // ?? route.stopTimes.length / stopsNumber - 1;
266
+ tripIndex >= 0;
267
+ tripIndex--
268
+ ) {
269
+ const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
270
+ const stopTime = route.stopTimes[stopTimeIndex]!;
271
+ if (stopTime.departure <= after) {
272
+ break;
273
+ }
274
+ if (
275
+ stopTime.pickUpType !== 'NOT_AVAILABLE' &&
276
+ (earliestDeparture === undefined ||
277
+ stopTime.departure < earliestDeparture)
278
+ ) {
279
+ earliestTripIndex = tripIndex;
280
+ earliestDeparture = stopTime.departure;
281
+ }
282
+ }
283
+ return earliestTripIndex;
284
+ }
285
+ }
286
+ }
@@ -0,0 +1,14 @@
1
+ /*
2
+ * An index containing only browser compatible modules for the UMD build.
3
+ */
4
+
5
+ import { Plotter } from './routing/plotter.js';
6
+ import { Query } from './routing/query.js';
7
+ import { Result } from './routing/result.js';
8
+ import { Route } from './routing/route.js';
9
+ import { Router } from './routing/router.js';
10
+ import { StopsIndex } from './stops/stopsIndex.js';
11
+ import { Time } from './timetable/time.js';
12
+ import { Timetable } from './timetable/timetable.js';
13
+
14
+ export { Plotter, Query, Result, Route, Router, StopsIndex, Time, Timetable };
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["./src/**/__tests__"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "include": ["./src/**/*.ts"],
3
+ "compilerOptions": {
4
+ "target": "es2015",
5
+ "lib": ["es2019"],
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
+ "rootDir": "./src",
9
+ "outDir": "dist",
10
+ "strict": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "noUncheckedIndexedAccess": true,
15
+ "sourceMap": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "declaration": true,
18
+ "noImplicitReturns": true,
19
+ "noUnusedLocals": true
20
+ }
21
+ }