minotor 7.0.2 → 9.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/.cspell.json +11 -1
- package/CHANGELOG.md +8 -3
- package/README.md +26 -24
- package/dist/cli.mjs +1786 -791
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/transfers.d.ts +29 -5
- package/dist/gtfs/trips.d.ts +10 -5
- package/dist/parser.cjs.js +972 -525
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +972 -525
- 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.d.ts +2 -2
- 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/routing/__tests__/plotter.test.d.ts +1 -0
- package/dist/routing/plotter.d.ts +42 -3
- package/dist/routing/result.d.ts +23 -7
- package/dist/routing/route.d.ts +2 -0
- package/dist/routing/router.d.ts +78 -19
- package/dist/timetable/__tests__/tripBoardingId.test.d.ts +1 -0
- package/dist/timetable/io.d.ts +4 -2
- package/dist/timetable/proto/timetable.d.ts +15 -1
- package/dist/timetable/route.d.ts +48 -23
- package/dist/timetable/timetable.d.ts +24 -7
- package/dist/timetable/tripBoardingId.d.ts +34 -0
- package/package.json +1 -1
- package/src/__e2e__/router.test.ts +114 -105
- package/src/__e2e__/timetable/stops.bin +2 -2
- package/src/__e2e__/timetable/timetable.bin +2 -2
- package/src/cli/repl.ts +245 -1
- package/src/gtfs/__tests__/parser.test.ts +19 -4
- package/src/gtfs/__tests__/transfers.test.ts +773 -37
- package/src/gtfs/__tests__/trips.test.ts +308 -27
- package/src/gtfs/parser.ts +36 -6
- package/src/gtfs/transfers.ts +193 -19
- package/src/gtfs/trips.ts +58 -21
- package/src/router.ts +2 -2
- package/src/routing/__tests__/plotter.test.ts +230 -0
- package/src/routing/__tests__/result.test.ts +486 -125
- package/src/routing/__tests__/route.test.ts +7 -3
- package/src/routing/__tests__/router.test.ts +380 -172
- package/src/routing/plotter.ts +279 -48
- package/src/routing/result.ts +114 -34
- package/src/routing/route.ts +0 -3
- package/src/routing/router.ts +344 -211
- package/src/timetable/__tests__/io.test.ts +34 -1
- package/src/timetable/__tests__/route.test.ts +74 -81
- package/src/timetable/__tests__/timetable.test.ts +232 -61
- package/src/timetable/__tests__/tripBoardingId.test.ts +57 -0
- package/src/timetable/io.ts +72 -10
- package/src/timetable/proto/timetable.proto +16 -2
- package/src/timetable/proto/timetable.ts +256 -22
- package/src/timetable/route.ts +174 -58
- package/src/timetable/timetable.ts +66 -16
- package/src/timetable/tripBoardingId.ts +94 -0
- package/tsconfig.json +2 -2
package/src/gtfs/transfers.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { SourceStopId, StopId } from '../stops/stops.js';
|
|
2
2
|
import { Duration } from '../timetable/duration.js';
|
|
3
|
+
import { Route } from '../timetable/route.js';
|
|
3
4
|
import {
|
|
4
5
|
ServiceRouteId,
|
|
6
|
+
Timetable,
|
|
5
7
|
Transfer,
|
|
6
8
|
TransferType,
|
|
9
|
+
TripBoarding,
|
|
10
|
+
TripContinuations,
|
|
7
11
|
} from '../timetable/timetable.js';
|
|
12
|
+
import { encode } from '../timetable/tripBoardingId.js';
|
|
8
13
|
import { GtfsStopsMap } from './stops.js';
|
|
9
|
-
import {
|
|
14
|
+
import { GtfsTripId, TripsMapping } from './trips.js';
|
|
10
15
|
import { parseCsv } from './utils.js';
|
|
11
16
|
|
|
12
17
|
export type GtfsTransferType =
|
|
@@ -19,11 +24,18 @@ export type GtfsTransferType =
|
|
|
19
24
|
|
|
20
25
|
export type TransfersMap = Map<StopId, Transfer[]>;
|
|
21
26
|
|
|
27
|
+
export type GtfsTripContinuation = {
|
|
28
|
+
fromStop: StopId;
|
|
29
|
+
fromTrip: GtfsTripId;
|
|
30
|
+
toStop: StopId;
|
|
31
|
+
toTrip: GtfsTripId;
|
|
32
|
+
};
|
|
33
|
+
|
|
22
34
|
export type TransferEntry = {
|
|
23
35
|
from_stop_id?: SourceStopId;
|
|
24
36
|
to_stop_id?: SourceStopId;
|
|
25
|
-
from_trip_id?:
|
|
26
|
-
to_trip_id?:
|
|
37
|
+
from_trip_id?: GtfsTripId;
|
|
38
|
+
to_trip_id?: GtfsTripId;
|
|
27
39
|
from_route_id?: ServiceRouteId;
|
|
28
40
|
to_route_id?: ServiceRouteId;
|
|
29
41
|
transfer_type: GtfsTransferType;
|
|
@@ -39,9 +51,12 @@ export type TransferEntry = {
|
|
|
39
51
|
export const parseTransfers = async (
|
|
40
52
|
transfersStream: NodeJS.ReadableStream,
|
|
41
53
|
stopsMap: GtfsStopsMap,
|
|
42
|
-
): Promise<
|
|
54
|
+
): Promise<{
|
|
55
|
+
transfers: TransfersMap;
|
|
56
|
+
tripContinuations: GtfsTripContinuation[];
|
|
57
|
+
}> => {
|
|
43
58
|
const transfers: TransfersMap = new Map();
|
|
44
|
-
|
|
59
|
+
const tripContinuations: GtfsTripContinuation[] = [];
|
|
45
60
|
for await (const rawLine of parseCsv(transfersStream, [
|
|
46
61
|
'transfer_type',
|
|
47
62
|
'min_transfer_time',
|
|
@@ -54,37 +69,67 @@ export const parseTransfers = async (
|
|
|
54
69
|
) {
|
|
55
70
|
continue;
|
|
56
71
|
}
|
|
57
|
-
if (transferEntry.
|
|
72
|
+
if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
|
|
73
|
+
console.warn(`Missing transfer origin or destination stop.`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const fromStop = stopsMap.get(transferEntry.from_stop_id);
|
|
77
|
+
const toStop = stopsMap.get(transferEntry.to_stop_id);
|
|
78
|
+
|
|
79
|
+
if (!fromStop || !toStop) {
|
|
58
80
|
console.warn(
|
|
59
|
-
`
|
|
81
|
+
`Transfer references non-existent stop(s): from_stop_id=${transferEntry.from_stop_id}, to_stop_id=${transferEntry.to_stop_id}`,
|
|
60
82
|
);
|
|
61
83
|
continue;
|
|
62
84
|
}
|
|
63
|
-
|
|
85
|
+
|
|
86
|
+
if (transferEntry.transfer_type === 4) {
|
|
87
|
+
if (
|
|
88
|
+
transferEntry.from_trip_id === undefined ||
|
|
89
|
+
transferEntry.from_trip_id === '' ||
|
|
90
|
+
transferEntry.to_trip_id === undefined ||
|
|
91
|
+
transferEntry.to_trip_id === ''
|
|
92
|
+
) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`Unsupported in-seat transfer, missing from_trip_id and/or to_trip_id.`,
|
|
95
|
+
);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const tripBoardingEntry: GtfsTripContinuation = {
|
|
99
|
+
fromStop: fromStop.id,
|
|
100
|
+
fromTrip: transferEntry.from_trip_id,
|
|
101
|
+
toStop: toStop.id,
|
|
102
|
+
toTrip: transferEntry.to_trip_id,
|
|
103
|
+
};
|
|
104
|
+
tripContinuations.push(tripBoardingEntry);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
|
|
64
108
|
console.warn(
|
|
65
|
-
`Unsupported transfer between
|
|
109
|
+
`Unsupported transfer of type ${transferEntry.transfer_type} between trips ${transferEntry.from_trip_id} and ${transferEntry.to_trip_id}.`,
|
|
66
110
|
);
|
|
67
111
|
continue;
|
|
68
112
|
}
|
|
69
|
-
if (
|
|
70
|
-
console.warn(
|
|
113
|
+
if (transferEntry.from_route_id && transferEntry.to_route_id) {
|
|
114
|
+
console.warn(
|
|
115
|
+
`Unsupported transfer of type ${transferEntry.transfer_type} between routes ${transferEntry.from_route_id} and ${transferEntry.to_route_id}.`,
|
|
116
|
+
);
|
|
71
117
|
continue;
|
|
72
118
|
}
|
|
73
|
-
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
transferEntry.transfer_type === 2 &&
|
|
122
|
+
transferEntry.min_transfer_time === undefined
|
|
123
|
+
) {
|
|
74
124
|
console.info(
|
|
75
125
|
`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`,
|
|
76
126
|
);
|
|
77
127
|
}
|
|
78
128
|
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
80
|
-
const fromStop = stopsMap.get(transferEntry.from_stop_id)!;
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
82
|
-
const toStop = stopsMap.get(transferEntry.to_stop_id)!;
|
|
83
|
-
|
|
84
129
|
const transfer: Transfer = {
|
|
85
130
|
destination: toStop.id,
|
|
86
131
|
type: parseGtfsTransferType(transferEntry.transfer_type),
|
|
87
|
-
...(transferEntry.min_transfer_time && {
|
|
132
|
+
...(transferEntry.min_transfer_time !== undefined && {
|
|
88
133
|
minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
|
|
89
134
|
}),
|
|
90
135
|
};
|
|
@@ -93,7 +138,136 @@ export const parseTransfers = async (
|
|
|
93
138
|
fromStopTransfers.push(transfer);
|
|
94
139
|
transfers.set(fromStop.id, fromStopTransfers);
|
|
95
140
|
}
|
|
96
|
-
return
|
|
141
|
+
return {
|
|
142
|
+
transfers,
|
|
143
|
+
tripContinuations,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Disambiguates stops involved in a transfer.
|
|
149
|
+
*
|
|
150
|
+
* The GTFS specification only refers to a stopId in the trip-to-trip transfers and not the
|
|
151
|
+
* specific stop index in the route. For routes that have multiple stops with the same stopId,
|
|
152
|
+
* we need to determine which are the from / to stop indices in respective routes.
|
|
153
|
+
* We do so by picking the stop indices leading to the most coherent transfer.
|
|
154
|
+
* (we pick the closest from stop index happening after the to stop index).
|
|
155
|
+
*/
|
|
156
|
+
const disambiguateTransferStopsIndices = (
|
|
157
|
+
fromStop: StopId,
|
|
158
|
+
fromRoute: Route,
|
|
159
|
+
fromTripIndex: number,
|
|
160
|
+
toStop: StopId,
|
|
161
|
+
toRoute: Route,
|
|
162
|
+
toTripIndex: number,
|
|
163
|
+
): { fromStopIndex: number; toStopIndex: number } | undefined => {
|
|
164
|
+
const fromStopIndices = fromRoute.stopRouteIndices(fromStop);
|
|
165
|
+
const toStopIndices = toRoute.stopRouteIndices(toStop);
|
|
166
|
+
let bestFromStopIndex: number | undefined;
|
|
167
|
+
let bestToStopIndex: number | undefined;
|
|
168
|
+
let bestTimeDifference = Infinity;
|
|
169
|
+
|
|
170
|
+
for (const originStopIndex of fromStopIndices) {
|
|
171
|
+
const fromArrivalTime = fromRoute.arrivalAt(originStopIndex, fromTripIndex);
|
|
172
|
+
for (const toStopIndex of toStopIndices) {
|
|
173
|
+
const toDepartureTime = toRoute.departureFrom(toStopIndex, toTripIndex);
|
|
174
|
+
if (toDepartureTime.isAfter(fromArrivalTime)) {
|
|
175
|
+
const timeDifference =
|
|
176
|
+
toDepartureTime.toMinutes() - fromArrivalTime.toMinutes();
|
|
177
|
+
if (timeDifference < bestTimeDifference) {
|
|
178
|
+
bestTimeDifference = timeDifference;
|
|
179
|
+
bestFromStopIndex = originStopIndex;
|
|
180
|
+
bestToStopIndex = toStopIndex;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (bestFromStopIndex !== undefined && bestToStopIndex !== undefined) {
|
|
187
|
+
return {
|
|
188
|
+
fromStopIndex: bestFromStopIndex,
|
|
189
|
+
toStopIndex: bestToStopIndex,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return undefined;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Builds trip continuations map from GTFS trip continuation data.
|
|
198
|
+
*
|
|
199
|
+
* This function processes GTFS in-seat transfer data and creates a mapping
|
|
200
|
+
* from trip boarding IDs to continuation boarding information. It disambiguates
|
|
201
|
+
* stop indices when routes have multiple stops with the same ID by finding
|
|
202
|
+
* the most coherent transfer timing.
|
|
203
|
+
*
|
|
204
|
+
* @param tripsMapping Mapping from GTFS trip IDs to internal trip representations
|
|
205
|
+
* @param tripContinuations Array of GTFS trip continuation data from transfers.txt
|
|
206
|
+
* @param timetable The timetable containing route and timing information
|
|
207
|
+
* @param activeStopIds Set of stop IDs that are active/enabled in the system
|
|
208
|
+
* @returns A map from trip boarding IDs to arrays of continuation boarding options
|
|
209
|
+
*/
|
|
210
|
+
export const buildTripContinuations = (
|
|
211
|
+
tripsMapping: TripsMapping,
|
|
212
|
+
tripContinuations: GtfsTripContinuation[],
|
|
213
|
+
timetable: Timetable,
|
|
214
|
+
activeStopIds: Set<StopId>,
|
|
215
|
+
): TripContinuations => {
|
|
216
|
+
const continuations: TripContinuations = new Map();
|
|
217
|
+
|
|
218
|
+
for (const gtfsContinuation of tripContinuations) {
|
|
219
|
+
if (
|
|
220
|
+
!activeStopIds.has(gtfsContinuation.fromStop) ||
|
|
221
|
+
!activeStopIds.has(gtfsContinuation.toStop)
|
|
222
|
+
) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const fromTripMapping = tripsMapping.get(gtfsContinuation.fromTrip);
|
|
226
|
+
const toTripMapping = tripsMapping.get(gtfsContinuation.toTrip);
|
|
227
|
+
|
|
228
|
+
if (!fromTripMapping || !toTripMapping) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fromRoute = timetable.getRoute(fromTripMapping.routeId);
|
|
233
|
+
const toRoute = timetable.getRoute(toTripMapping.routeId);
|
|
234
|
+
|
|
235
|
+
if (!fromRoute || !toRoute) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const bestStopIndices = disambiguateTransferStopsIndices(
|
|
240
|
+
gtfsContinuation.fromStop,
|
|
241
|
+
fromRoute,
|
|
242
|
+
fromTripMapping.tripRouteIndex,
|
|
243
|
+
gtfsContinuation.toStop,
|
|
244
|
+
toRoute,
|
|
245
|
+
toTripMapping.tripRouteIndex,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (!bestStopIndices) {
|
|
249
|
+
// No valid continuation found
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const tripBoardingId = encode(
|
|
254
|
+
bestStopIndices.fromStopIndex,
|
|
255
|
+
fromTripMapping.routeId,
|
|
256
|
+
fromTripMapping.tripRouteIndex,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const continuationBoarding: TripBoarding = {
|
|
260
|
+
hopOnStopIndex: bestStopIndices.toStopIndex,
|
|
261
|
+
routeId: toTripMapping.routeId,
|
|
262
|
+
tripIndex: toTripMapping.tripRouteIndex,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const existingContinuations = continuations.get(tripBoardingId) || [];
|
|
266
|
+
existingContinuations.push(continuationBoarding);
|
|
267
|
+
continuations.set(tripBoardingId, existingContinuations);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return continuations;
|
|
97
271
|
};
|
|
98
272
|
|
|
99
273
|
const parseGtfsTransferType = (
|
package/src/gtfs/trips.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
NOT_AVAILABLE,
|
|
7
7
|
REGULAR,
|
|
8
8
|
Route,
|
|
9
|
+
RouteId,
|
|
10
|
+
TripRouteIndex,
|
|
9
11
|
} from '../timetable/route.js';
|
|
10
12
|
import {
|
|
11
13
|
ServiceRoute,
|
|
@@ -19,14 +21,19 @@ import { GtfsTime, toTime } from './time.js';
|
|
|
19
21
|
import { TransfersMap } from './transfers.js';
|
|
20
22
|
import { hashIds, parseCsv } from './utils.js';
|
|
21
23
|
|
|
22
|
-
export type
|
|
24
|
+
export type GtfsTripId = string;
|
|
23
25
|
|
|
24
|
-
export type
|
|
26
|
+
export type GtfsTripIdsMap = Map<GtfsTripId, GtfsRouteId>;
|
|
27
|
+
|
|
28
|
+
export type TripsMapping = Map<
|
|
29
|
+
GtfsTripId,
|
|
30
|
+
{ routeId: RouteId; tripRouteIndex: TripRouteIndex }
|
|
31
|
+
>;
|
|
25
32
|
|
|
26
33
|
type TripEntry = {
|
|
27
34
|
route_id: GtfsRouteId;
|
|
28
35
|
service_id: ServiceId;
|
|
29
|
-
trip_id:
|
|
36
|
+
trip_id: GtfsTripId;
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
export type GtfsPickupDropOffType =
|
|
@@ -37,7 +44,7 @@ export type GtfsPickupDropOffType =
|
|
|
37
44
|
| '3'; // Must coordinate with driver
|
|
38
45
|
|
|
39
46
|
type StopTimeEntry = {
|
|
40
|
-
trip_id:
|
|
47
|
+
trip_id: GtfsTripId;
|
|
41
48
|
arrival_time?: GtfsTime;
|
|
42
49
|
departure_time?: GtfsTime;
|
|
43
50
|
stop_id: SourceStopId;
|
|
@@ -55,6 +62,7 @@ type RouteBuilder = {
|
|
|
55
62
|
serviceRouteId: ServiceRouteId;
|
|
56
63
|
stops: StopId[];
|
|
57
64
|
trips: Array<{
|
|
65
|
+
gtfsTripId: GtfsTripId;
|
|
58
66
|
firstDeparture: number;
|
|
59
67
|
arrivalTimes: number[];
|
|
60
68
|
departureTimes: number[];
|
|
@@ -109,7 +117,9 @@ export const encodePickUpDropOffTypes = (
|
|
|
109
117
|
/**
|
|
110
118
|
* Sorts trips by departure time and creates optimized typed arrays
|
|
111
119
|
*/
|
|
112
|
-
const finalizeRouteFromBuilder = (
|
|
120
|
+
const finalizeRouteFromBuilder = (
|
|
121
|
+
builder: RouteBuilder,
|
|
122
|
+
): [SerializedRoute, GtfsTripId[]] => {
|
|
113
123
|
builder.trips.sort((a, b) => a.firstDeparture - b.firstDeparture);
|
|
114
124
|
|
|
115
125
|
const stopsCount = builder.stops.length;
|
|
@@ -119,11 +129,13 @@ const finalizeRouteFromBuilder = (builder: RouteBuilder): SerializedRoute => {
|
|
|
119
129
|
const allPickUpTypes: SerializedPickUpDropOffType[] = [];
|
|
120
130
|
const allDropOffTypes: SerializedPickUpDropOffType[] = [];
|
|
121
131
|
|
|
132
|
+
const gtfsTripIds = [];
|
|
122
133
|
for (let tripIndex = 0; tripIndex < tripsCount; tripIndex++) {
|
|
123
134
|
const trip = builder.trips[tripIndex];
|
|
124
135
|
if (!trip) {
|
|
125
136
|
throw new Error(`Missing trip data at index ${tripIndex}`);
|
|
126
137
|
}
|
|
138
|
+
gtfsTripIds.push(trip.gtfsTripId);
|
|
127
139
|
const baseIndex = tripIndex * stopsCount * 2;
|
|
128
140
|
|
|
129
141
|
for (let stopIndex = 0; stopIndex < stopsCount; stopIndex++) {
|
|
@@ -155,12 +167,15 @@ const finalizeRouteFromBuilder = (builder: RouteBuilder): SerializedRoute => {
|
|
|
155
167
|
allPickUpTypes,
|
|
156
168
|
allDropOffTypes,
|
|
157
169
|
);
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
serviceRouteId: builder.serviceRouteId,
|
|
173
|
+
stops: stopsArray,
|
|
174
|
+
stopTimes: stopTimesArray,
|
|
175
|
+
pickUpDropOffTypes: pickUpDropOffTypesArray,
|
|
176
|
+
},
|
|
177
|
+
gtfsTripIds,
|
|
178
|
+
];
|
|
164
179
|
};
|
|
165
180
|
|
|
166
181
|
/**
|
|
@@ -175,8 +190,8 @@ export const parseTrips = async (
|
|
|
175
190
|
tripsStream: NodeJS.ReadableStream,
|
|
176
191
|
serviceIds: ServiceIds,
|
|
177
192
|
validGtfsRoutes: GtfsRoutesMap,
|
|
178
|
-
): Promise<
|
|
179
|
-
const trips:
|
|
193
|
+
): Promise<GtfsTripIdsMap> => {
|
|
194
|
+
const trips: GtfsTripIdsMap = new Map();
|
|
180
195
|
for await (const rawLine of parseCsv(tripsStream, ['stop_sequence'])) {
|
|
181
196
|
const line = rawLine as TripEntry;
|
|
182
197
|
if (!serviceIds.has(line.service_id)) {
|
|
@@ -199,10 +214,11 @@ export const buildStopsAdjacencyStructure = (
|
|
|
199
214
|
nbStops: number,
|
|
200
215
|
activeStops: Set<StopId>,
|
|
201
216
|
): StopAdjacency[] => {
|
|
202
|
-
// TODO somehow works when it's a map
|
|
203
217
|
const stopsAdjacency = new Array<StopAdjacency>(nbStops);
|
|
204
218
|
for (let i = 0; i < nbStops; i++) {
|
|
205
|
-
stopsAdjacency[i] = {
|
|
219
|
+
stopsAdjacency[i] = {
|
|
220
|
+
routes: [],
|
|
221
|
+
};
|
|
206
222
|
}
|
|
207
223
|
for (let index = 0; index < routes.length; index++) {
|
|
208
224
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -229,7 +245,11 @@ export const buildStopsAdjacencyStructure = (
|
|
|
229
245
|
const transfer = transfers[i]!;
|
|
230
246
|
if (activeStops.has(stop) || activeStops.has(transfer.destination)) {
|
|
231
247
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
232
|
-
stopsAdjacency[stop]
|
|
248
|
+
const stopAdj = stopsAdjacency[stop]!;
|
|
249
|
+
if (!stopAdj.transfers) {
|
|
250
|
+
stopAdj.transfers = [];
|
|
251
|
+
}
|
|
252
|
+
stopAdj.transfers.push(transfer);
|
|
233
253
|
activeStops.add(transfer.destination);
|
|
234
254
|
activeStops.add(stop);
|
|
235
255
|
}
|
|
@@ -250,16 +270,17 @@ export const buildStopsAdjacencyStructure = (
|
|
|
250
270
|
export const parseStopTimes = async (
|
|
251
271
|
stopTimesStream: NodeJS.ReadableStream,
|
|
252
272
|
stopsMap: GtfsStopsMap,
|
|
253
|
-
activeTripIds:
|
|
273
|
+
activeTripIds: GtfsTripIdsMap,
|
|
254
274
|
activeStopIds: Set<StopId>,
|
|
255
275
|
): Promise<{
|
|
256
276
|
routes: Route[];
|
|
257
277
|
serviceRoutesMap: Map<GtfsRouteId, ServiceRouteId>;
|
|
278
|
+
tripsMapping: TripsMapping;
|
|
258
279
|
}> => {
|
|
259
280
|
/**
|
|
260
281
|
* Adds a trip to the appropriate route builder
|
|
261
282
|
*/
|
|
262
|
-
const addTrip = (currentTripId:
|
|
283
|
+
const addTrip = (currentTripId: GtfsTripId) => {
|
|
263
284
|
const gtfsRouteId = activeTripIds.get(currentTripId);
|
|
264
285
|
|
|
265
286
|
if (!gtfsRouteId || stops.length === 0) {
|
|
@@ -304,6 +325,7 @@ export const parseStopTimes = async (
|
|
|
304
325
|
|
|
305
326
|
routeBuilder.trips.push({
|
|
306
327
|
firstDeparture,
|
|
328
|
+
gtfsTripId: currentTripId,
|
|
307
329
|
arrivalTimes: arrivalTimes,
|
|
308
330
|
departureTimes: departureTimes,
|
|
309
331
|
pickUpTypes: pickUpTypes,
|
|
@@ -330,7 +352,7 @@ export const parseStopTimes = async (
|
|
|
330
352
|
let departureTimes: number[] = [];
|
|
331
353
|
let pickUpTypes: SerializedPickUpDropOffType[] = [];
|
|
332
354
|
let dropOffTypes: SerializedPickUpDropOffType[] = [];
|
|
333
|
-
let currentTripId:
|
|
355
|
+
let currentTripId: GtfsTripId | undefined = undefined;
|
|
334
356
|
|
|
335
357
|
for await (const rawLine of parseCsv(stopTimesStream, ['stop_sequence'])) {
|
|
336
358
|
const line = rawLine as StopTimeEntry;
|
|
@@ -347,6 +369,9 @@ export const parseStopTimes = async (
|
|
|
347
369
|
continue;
|
|
348
370
|
}
|
|
349
371
|
if (line.pickup_type === '1' && line.drop_off_type === '1') {
|
|
372
|
+
// Warning: could potentially lead to issues if there is an in-seat transfer
|
|
373
|
+
// at this stop - it can be not boardable nor alightable but still useful for an in-seat transfer.
|
|
374
|
+
// This doesn't seem to happen in practice for now so keeping this condition to save memory.
|
|
350
375
|
continue;
|
|
351
376
|
}
|
|
352
377
|
if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
|
|
@@ -382,18 +407,30 @@ export const parseStopTimes = async (
|
|
|
382
407
|
}
|
|
383
408
|
|
|
384
409
|
const routesAdjacency: Route[] = [];
|
|
410
|
+
const tripsMapping = new Map<
|
|
411
|
+
GtfsTripId,
|
|
412
|
+
{ routeId: RouteId; tripRouteIndex: TripRouteIndex }
|
|
413
|
+
>();
|
|
385
414
|
for (const [, routeBuilder] of routeBuilders) {
|
|
386
|
-
const routeData = finalizeRouteFromBuilder(routeBuilder);
|
|
415
|
+
const [routeData, gtfsTripIds] = finalizeRouteFromBuilder(routeBuilder);
|
|
416
|
+
const routeId = routesAdjacency.length;
|
|
387
417
|
routesAdjacency.push(
|
|
388
418
|
new Route(
|
|
419
|
+
routeId,
|
|
389
420
|
routeData.stopTimes,
|
|
390
421
|
routeData.pickUpDropOffTypes,
|
|
391
422
|
routeData.stops,
|
|
392
423
|
routeData.serviceRouteId,
|
|
393
424
|
),
|
|
394
425
|
);
|
|
426
|
+
gtfsTripIds.forEach((tripId, index) => {
|
|
427
|
+
tripsMapping.set(tripId, {
|
|
428
|
+
routeId,
|
|
429
|
+
tripRouteIndex: index,
|
|
430
|
+
});
|
|
431
|
+
});
|
|
395
432
|
}
|
|
396
|
-
return { routes: routesAdjacency, serviceRoutesMap };
|
|
433
|
+
return { routes: routesAdjacency, serviceRoutesMap, tripsMapping };
|
|
397
434
|
};
|
|
398
435
|
|
|
399
436
|
const parsePickupDropOffType = (
|
package/src/router.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Query } from './routing/query.js';
|
|
|
3
3
|
import { Result } from './routing/result.js';
|
|
4
4
|
import type { Leg, Transfer, VehicleLeg } from './routing/route.js';
|
|
5
5
|
import { Route } from './routing/route.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Arrival } from './routing/router.js';
|
|
7
7
|
import { Router } from './routing/router.js';
|
|
8
8
|
import type { LocationType, SourceStopId, StopId } from './stops/stops.js';
|
|
9
9
|
import type { Stop } from './stops/stops.js';
|
|
@@ -30,9 +30,9 @@ export {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export type {
|
|
33
|
+
Arrival,
|
|
33
34
|
Leg,
|
|
34
35
|
LocationType,
|
|
35
|
-
ReachingTime,
|
|
36
36
|
RouteType,
|
|
37
37
|
ServiceRouteInfo,
|
|
38
38
|
SourceStopId,
|