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.
- package/.cspell.json +43 -0
- package/.czrc +3 -0
- package/.editorconfig +10 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +4 -0
- package/.github/workflows/minotor.yml +85 -0
- package/.prettierrc +7 -0
- package/.releaserc.json +27 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/bundle.cjs.js +16507 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/bundle.esm.js +16496 -0
- package/dist/bundle.esm.js.map +1 -0
- package/dist/bundle.umd.js +2 -0
- package/dist/bundle.umd.js.map +1 -0
- package/dist/cli/__tests__/minotor.test.d.ts +1 -0
- package/dist/cli/minotor.d.ts +5 -0
- package/dist/cli/repl.d.ts +1 -0
- package/dist/cli/utils.d.ts +3 -0
- package/dist/cli.mjs +20504 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/gtfs/__tests__/parser.test.d.ts +1 -0
- package/dist/gtfs/__tests__/routes.test.d.ts +1 -0
- package/dist/gtfs/__tests__/services.test.d.ts +1 -0
- package/dist/gtfs/__tests__/stops.test.d.ts +1 -0
- package/dist/gtfs/__tests__/time.test.d.ts +1 -0
- package/dist/gtfs/__tests__/transfers.test.d.ts +1 -0
- package/dist/gtfs/__tests__/trips.test.d.ts +1 -0
- package/dist/gtfs/__tests__/utils.test.d.ts +1 -0
- package/dist/gtfs/parser.d.ts +34 -0
- package/dist/gtfs/profiles/__tests__/ch.test.d.ts +1 -0
- package/dist/gtfs/profiles/ch.d.ts +2 -0
- package/dist/gtfs/profiles/standard.d.ts +2 -0
- package/dist/gtfs/routes.d.ts +11 -0
- package/dist/gtfs/services.d.ts +19 -0
- package/dist/gtfs/stops.d.ts +20 -0
- package/dist/gtfs/time.d.ts +17 -0
- package/dist/gtfs/transfers.d.ts +22 -0
- package/dist/gtfs/trips.d.ts +26 -0
- package/dist/gtfs/utils.d.ts +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/routing/__tests__/router.test.d.ts +1 -0
- package/dist/routing/plotter.d.ts +11 -0
- package/dist/routing/query.d.ts +35 -0
- package/dist/routing/result.d.ts +28 -0
- package/dist/routing/route.d.ts +25 -0
- package/dist/routing/router.d.ts +33 -0
- package/dist/stops/__tests__/io.test.d.ts +1 -0
- package/dist/stops/__tests__/stopFinder.test.d.ts +1 -0
- package/dist/stops/i18n.d.ts +10 -0
- package/dist/stops/io.d.ts +4 -0
- package/dist/stops/proto/stops.d.ts +53 -0
- package/dist/stops/stops.d.ts +16 -0
- package/dist/stops/stopsIndex.d.ts +52 -0
- package/dist/timetable/__tests__/io.test.d.ts +1 -0
- package/dist/timetable/__tests__/timetable.test.d.ts +1 -0
- package/dist/timetable/duration.d.ts +51 -0
- package/dist/timetable/io.d.ts +8 -0
- package/dist/timetable/proto/timetable.d.ts +122 -0
- package/dist/timetable/time.d.ts +98 -0
- package/dist/timetable/timetable.d.ts +82 -0
- package/dist/umdIndex.d.ts +9 -0
- package/eslint.config.mjs +52 -0
- package/package.json +109 -0
- package/rollup.config.js +44 -0
- package/src/cli/__tests__/minotor.test.ts +23 -0
- package/src/cli/minotor.ts +112 -0
- package/src/cli/repl.ts +200 -0
- package/src/cli/utils.ts +36 -0
- package/src/gtfs/__tests__/parser.test.ts +591 -0
- package/src/gtfs/__tests__/resources/sample-feed/agency.txt +2 -0
- package/src/gtfs/__tests__/resources/sample-feed/calendar.txt +3 -0
- package/src/gtfs/__tests__/resources/sample-feed/calendar_dates.txt +2 -0
- package/src/gtfs/__tests__/resources/sample-feed/fare_attributes.txt +3 -0
- package/src/gtfs/__tests__/resources/sample-feed/fare_rules.txt +5 -0
- package/src/gtfs/__tests__/resources/sample-feed/frequencies.txt +12 -0
- package/src/gtfs/__tests__/resources/sample-feed/routes.txt +6 -0
- package/src/gtfs/__tests__/resources/sample-feed/sample-feed.zip +0 -0
- package/src/gtfs/__tests__/resources/sample-feed/shapes.txt +1 -0
- package/src/gtfs/__tests__/resources/sample-feed/stop_times.txt +34 -0
- package/src/gtfs/__tests__/resources/sample-feed/stops.txt +10 -0
- package/src/gtfs/__tests__/resources/sample-feed/trips.txt +13 -0
- package/src/gtfs/__tests__/resources/sample-feed.zip +0 -0
- package/src/gtfs/__tests__/routes.test.ts +63 -0
- package/src/gtfs/__tests__/services.test.ts +209 -0
- package/src/gtfs/__tests__/stops.test.ts +177 -0
- package/src/gtfs/__tests__/time.test.ts +27 -0
- package/src/gtfs/__tests__/transfers.test.ts +117 -0
- package/src/gtfs/__tests__/trips.test.ts +463 -0
- package/src/gtfs/__tests__/utils.test.ts +13 -0
- package/src/gtfs/parser.ts +154 -0
- package/src/gtfs/profiles/__tests__/ch.test.ts +43 -0
- package/src/gtfs/profiles/ch.ts +70 -0
- package/src/gtfs/profiles/standard.ts +39 -0
- package/src/gtfs/routes.ts +48 -0
- package/src/gtfs/services.ts +98 -0
- package/src/gtfs/stops.ts +112 -0
- package/src/gtfs/time.ts +33 -0
- package/src/gtfs/transfers.ts +102 -0
- package/src/gtfs/trips.ts +228 -0
- package/src/gtfs/utils.ts +42 -0
- package/src/index.ts +28 -0
- package/src/routing/__tests__/router.test.ts +760 -0
- package/src/routing/plotter.ts +70 -0
- package/src/routing/query.ts +74 -0
- package/src/routing/result.ts +108 -0
- package/src/routing/route.ts +94 -0
- package/src/routing/router.ts +262 -0
- package/src/stops/__tests__/io.test.ts +43 -0
- package/src/stops/__tests__/stopFinder.test.ts +185 -0
- package/src/stops/i18n.ts +40 -0
- package/src/stops/io.ts +94 -0
- package/src/stops/proto/stops.proto +26 -0
- package/src/stops/proto/stops.ts +445 -0
- package/src/stops/stops.ts +24 -0
- package/src/stops/stopsIndex.ts +151 -0
- package/src/timetable/__tests__/io.test.ts +175 -0
- package/src/timetable/__tests__/timetable.test.ts +180 -0
- package/src/timetable/duration.ts +85 -0
- package/src/timetable/io.ts +265 -0
- package/src/timetable/proto/timetable.proto +76 -0
- package/src/timetable/proto/timetable.ts +1304 -0
- package/src/timetable/time.ts +192 -0
- package/src/timetable/timetable.ts +286 -0
- package/src/umdIndex.ts +14 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { StopId } from '../stops/stops.js';
|
|
2
|
+
import {
|
|
3
|
+
PickUpDropOffType,
|
|
4
|
+
Route,
|
|
5
|
+
RouteId,
|
|
6
|
+
RoutesAdjacency,
|
|
7
|
+
ServiceRouteId,
|
|
8
|
+
ServiceRoutesMap,
|
|
9
|
+
StopsAdjacency,
|
|
10
|
+
StopTimes,
|
|
11
|
+
} from '../timetable/timetable.js';
|
|
12
|
+
import { ServiceIds } from './services.js';
|
|
13
|
+
import { StopIds } from './stops.js';
|
|
14
|
+
import { GtfsTime, toTime } from './time.js';
|
|
15
|
+
import { TransfersMap } from './transfers.js';
|
|
16
|
+
import { hash, parseCsv } from './utils.js';
|
|
17
|
+
|
|
18
|
+
export type TripId = string;
|
|
19
|
+
|
|
20
|
+
export type TripIdsMap = Map<TripId, ServiceRouteId>;
|
|
21
|
+
|
|
22
|
+
type TripEntry = {
|
|
23
|
+
route_id: RouteId;
|
|
24
|
+
service_id: ServiceRouteId;
|
|
25
|
+
trip_id: TripId;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type GtfsPickupDropOffType =
|
|
29
|
+
| '' // Not specified
|
|
30
|
+
| 0 // Regularly scheduled
|
|
31
|
+
| 1 // Not available
|
|
32
|
+
| 2 // Must phone agency
|
|
33
|
+
| 3; // Must coordinate with driver
|
|
34
|
+
|
|
35
|
+
type StopTimeEntry = {
|
|
36
|
+
trip_id: TripId;
|
|
37
|
+
arrival_time?: GtfsTime;
|
|
38
|
+
departure_time?: GtfsTime;
|
|
39
|
+
stop_id: StopId;
|
|
40
|
+
stop_sequence: number;
|
|
41
|
+
pickup_type?: GtfsPickupDropOffType;
|
|
42
|
+
drop_off_type?: GtfsPickupDropOffType;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parses the trips.txt file from a GTFS feed
|
|
47
|
+
*
|
|
48
|
+
* @param tripsStream The readable stream containing the trips data.
|
|
49
|
+
* @param serviceIds A mapping of service IDs to corresponding route IDs.
|
|
50
|
+
* @param routeIds A mapping of route IDs to route details.
|
|
51
|
+
* @returns A mapping of trip IDs to corresponding route IDs.
|
|
52
|
+
*/
|
|
53
|
+
export const parseTrips = async (
|
|
54
|
+
tripsStream: NodeJS.ReadableStream,
|
|
55
|
+
serviceIds: ServiceIds,
|
|
56
|
+
routeIds: ServiceRoutesMap,
|
|
57
|
+
): Promise<TripIdsMap> => {
|
|
58
|
+
const trips: TripIdsMap = new Map();
|
|
59
|
+
for await (const rawLine of parseCsv(tripsStream)) {
|
|
60
|
+
const line = rawLine as TripEntry;
|
|
61
|
+
if (!serviceIds.has(line.service_id)) {
|
|
62
|
+
// The trip doesn't correspond to an active service
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!routeIds.get(line.route_id)) {
|
|
66
|
+
// The trip doesn't correspond to a supported route
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
trips.set(line.trip_id, line.route_id);
|
|
70
|
+
}
|
|
71
|
+
return trips;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const buildStopsAdjacencyStructure = (
|
|
75
|
+
validStops: StopIds,
|
|
76
|
+
routes: RoutesAdjacency,
|
|
77
|
+
transfersMap: TransfersMap,
|
|
78
|
+
): StopsAdjacency => {
|
|
79
|
+
const stopsAdjacency: StopsAdjacency = new Map();
|
|
80
|
+
for (const routeId of routes.keys()) {
|
|
81
|
+
const route = routes.get(routeId) as Route;
|
|
82
|
+
for (const stop of route.stops) {
|
|
83
|
+
if (!stopsAdjacency.get(stop) && validStops.has(stop)) {
|
|
84
|
+
stopsAdjacency.set(stop, { routes: [], transfers: [] });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
stopsAdjacency.get(stop)?.routes.push(routeId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [stop, transfers] of transfersMap) {
|
|
91
|
+
const s = stopsAdjacency.get(stop);
|
|
92
|
+
if (s) {
|
|
93
|
+
for (const transfer of transfers) {
|
|
94
|
+
if (validStops.has(transfer.destination)) {
|
|
95
|
+
s.transfers.push(transfer);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return stopsAdjacency;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parses the stop_times.txt data from a GTFS feed.
|
|
105
|
+
*
|
|
106
|
+
* @param stopTimesStream The readable stream containing the stop times data.
|
|
107
|
+
* @param validTripIds A map of valid trip IDs to corresponding route IDs.
|
|
108
|
+
* @param validStopIds A map of valid stop IDs.
|
|
109
|
+
* @returns A mapping of route IDs to route details. The routes return corresponds to the set of trips from GTFS that share the same stop list.
|
|
110
|
+
*/
|
|
111
|
+
export const parseStopTimes = async (
|
|
112
|
+
stopTimesStream: NodeJS.ReadableStream,
|
|
113
|
+
validTripIds: TripIdsMap,
|
|
114
|
+
validStopIds: StopIds,
|
|
115
|
+
): Promise<RoutesAdjacency> => {
|
|
116
|
+
const addTrip = (currentTripId: TripId) => {
|
|
117
|
+
const gtfsRouteId = validTripIds.get(currentTripId);
|
|
118
|
+
if (!gtfsRouteId) {
|
|
119
|
+
stops = [];
|
|
120
|
+
stopTimes = [];
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const routeId = `${gtfsRouteId}_${hash(stops.join('$'))}`;
|
|
124
|
+
|
|
125
|
+
let route = routes.get(routeId);
|
|
126
|
+
if (!route) {
|
|
127
|
+
route = {
|
|
128
|
+
serviceRouteId: gtfsRouteId,
|
|
129
|
+
stops: [...stops],
|
|
130
|
+
stopIndices: new Map(stops.map((stop, i) => [stop, i])),
|
|
131
|
+
stopTimes: [...stopTimes],
|
|
132
|
+
};
|
|
133
|
+
routes.set(routeId, route);
|
|
134
|
+
for (const stop of stops) {
|
|
135
|
+
validStopIds.add(stop);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const tripFirstStop = stopTimes[0];
|
|
139
|
+
if (!tripFirstStop) {
|
|
140
|
+
throw new Error(`Empty trip ${currentTripId}`);
|
|
141
|
+
}
|
|
142
|
+
// insert the stopTimes at the right position
|
|
143
|
+
let stopTimesIndex = 0;
|
|
144
|
+
for (let i = 0; i < route.stopTimes.length; i += stops.length) {
|
|
145
|
+
const currentDeparture = route.stopTimes[i];
|
|
146
|
+
if (
|
|
147
|
+
currentDeparture &&
|
|
148
|
+
tripFirstStop.departure > currentDeparture.departure
|
|
149
|
+
) {
|
|
150
|
+
stopTimesIndex = i + stops.length;
|
|
151
|
+
} else {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
route.stopTimes.splice(stopTimesIndex, 0, ...stopTimes);
|
|
156
|
+
}
|
|
157
|
+
stops = [];
|
|
158
|
+
stopTimes = [];
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const routes: RoutesAdjacency = new Map();
|
|
162
|
+
|
|
163
|
+
let previousSeq = 0;
|
|
164
|
+
let stops: StopId[] = [];
|
|
165
|
+
let stopTimes: StopTimes[] = [];
|
|
166
|
+
let currentTripId: TripId | undefined = undefined;
|
|
167
|
+
|
|
168
|
+
for await (const rawLine of parseCsv(stopTimesStream)) {
|
|
169
|
+
const line = rawLine as StopTimeEntry;
|
|
170
|
+
if (line.trip_id === currentTripId && line.stop_sequence <= previousSeq) {
|
|
171
|
+
console.warn(`Stop sequences not increasing for trip ${line.trip_id}.`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (!line.arrival_time && !line.departure_time) {
|
|
175
|
+
console.warn(
|
|
176
|
+
`Missing arrival or departure time for ${line.trip_id} at stop ${line.stop_id}.`,
|
|
177
|
+
);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (line.pickup_type === 1 && line.drop_off_type === 1) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
currentTripId &&
|
|
185
|
+
line.trip_id !== currentTripId &&
|
|
186
|
+
stops.length > 0 &&
|
|
187
|
+
stopTimes.length > 0
|
|
188
|
+
) {
|
|
189
|
+
addTrip(currentTripId);
|
|
190
|
+
}
|
|
191
|
+
currentTripId = line.trip_id;
|
|
192
|
+
stops.push(line.stop_id);
|
|
193
|
+
const departure = line.departure_time ?? line.arrival_time;
|
|
194
|
+
const arrival = line.arrival_time ?? line.departure_time;
|
|
195
|
+
stopTimes.push({
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
197
|
+
departure: toTime(departure!),
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
199
|
+
arrival: toTime(arrival!),
|
|
200
|
+
pickUpType: parsePickupDropOffType(line.pickup_type),
|
|
201
|
+
dropOffType: parsePickupDropOffType(line.drop_off_type),
|
|
202
|
+
});
|
|
203
|
+
previousSeq = line.stop_sequence;
|
|
204
|
+
}
|
|
205
|
+
if (currentTripId) {
|
|
206
|
+
addTrip(currentTripId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return routes;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const parsePickupDropOffType = (
|
|
213
|
+
gtfsType?: GtfsPickupDropOffType,
|
|
214
|
+
): PickUpDropOffType => {
|
|
215
|
+
switch (gtfsType) {
|
|
216
|
+
default:
|
|
217
|
+
console.warn(`Unknown pickup/drop-off type ${gtfsType}`);
|
|
218
|
+
return 'REGULAR';
|
|
219
|
+
case 0:
|
|
220
|
+
return 'REGULAR';
|
|
221
|
+
case 1:
|
|
222
|
+
return 'NOT_AVAILABLE';
|
|
223
|
+
case 2:
|
|
224
|
+
return 'MUST_PHONE_AGENCY';
|
|
225
|
+
case 3:
|
|
226
|
+
return 'MUST_COORDINATE_WITH_DRIVER';
|
|
227
|
+
}
|
|
228
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parse, Parser } from 'csv-parse';
|
|
2
|
+
|
|
3
|
+
export type Maybe<T> = T | undefined;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a simple hash from a string.
|
|
7
|
+
*
|
|
8
|
+
* This function computes a hash for a given string by iterating over each
|
|
9
|
+
* character and applying bitwise operations to accumulate a hash value.
|
|
10
|
+
* The final hash is then converted to a base-36 string and padded to
|
|
11
|
+
* ensure a minimum length of 6 characters.
|
|
12
|
+
*
|
|
13
|
+
* @param str - The input string to hash.
|
|
14
|
+
* @returns A hashed string representation of the input.
|
|
15
|
+
*/
|
|
16
|
+
export const hash = (str: string): string => {
|
|
17
|
+
let hash = 0;
|
|
18
|
+
for (let i = 0; i < str.length; i++) {
|
|
19
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
20
|
+
hash &= hash;
|
|
21
|
+
}
|
|
22
|
+
return (hash >>> 0).toString(36).padStart(6, '0');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parses a CSV stream with a sensible configuration for GTFS feeds.
|
|
27
|
+
*
|
|
28
|
+
* @param stream The CSV stream.
|
|
29
|
+
* @returns A parser from the csv-parse library.
|
|
30
|
+
*/
|
|
31
|
+
export const parseCsv = (stream: NodeJS.ReadableStream): Parser => {
|
|
32
|
+
return stream.pipe(
|
|
33
|
+
parse({
|
|
34
|
+
delimiter: ',',
|
|
35
|
+
columns: true,
|
|
36
|
+
cast: true,
|
|
37
|
+
bom: true,
|
|
38
|
+
ignore_last_delimiters: true,
|
|
39
|
+
relax_column_count: true,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* An index containing the full library, including node-dependent modules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { GtfsParser, GtfsProfile } from './gtfs/parser.js';
|
|
6
|
+
import { chGtfsProfile } from './gtfs/profiles/ch.js';
|
|
7
|
+
import { Plotter } from './routing/plotter.js';
|
|
8
|
+
import { Query } from './routing/query.js';
|
|
9
|
+
import { Result } from './routing/result.js';
|
|
10
|
+
import { Route } from './routing/route.js';
|
|
11
|
+
import { Router } from './routing/router.js';
|
|
12
|
+
import { StopsIndex } from './stops/stopsIndex.js';
|
|
13
|
+
import { Time } from './timetable/time.js';
|
|
14
|
+
import { Timetable } from './timetable/timetable.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
chGtfsProfile,
|
|
18
|
+
GtfsParser,
|
|
19
|
+
GtfsProfile,
|
|
20
|
+
Plotter,
|
|
21
|
+
Query,
|
|
22
|
+
Result,
|
|
23
|
+
Route,
|
|
24
|
+
Router,
|
|
25
|
+
StopsIndex,
|
|
26
|
+
Time,
|
|
27
|
+
Timetable,
|
|
28
|
+
};
|