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,445 @@
|
|
|
1
|
+
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
|
2
|
+
// versions:
|
|
3
|
+
// protoc-gen-ts_proto v2.6.1
|
|
4
|
+
// protoc v4.23.4
|
|
5
|
+
// source: src/stops/proto/stops.proto
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
|
|
9
|
+
|
|
10
|
+
export const protobufPackage = "minotor.stops";
|
|
11
|
+
|
|
12
|
+
export enum LocationType {
|
|
13
|
+
SIMPLE_STOP_OR_PLATFORM = 0,
|
|
14
|
+
STATION = 1,
|
|
15
|
+
ENTRANCE_EXIT = 2,
|
|
16
|
+
GENERIC_NODE = 3,
|
|
17
|
+
BOARDING_AREA = 4,
|
|
18
|
+
UNRECOGNIZED = -1,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function locationTypeFromJSON(object: any): LocationType {
|
|
22
|
+
switch (object) {
|
|
23
|
+
case 0:
|
|
24
|
+
case "SIMPLE_STOP_OR_PLATFORM":
|
|
25
|
+
return LocationType.SIMPLE_STOP_OR_PLATFORM;
|
|
26
|
+
case 1:
|
|
27
|
+
case "STATION":
|
|
28
|
+
return LocationType.STATION;
|
|
29
|
+
case 2:
|
|
30
|
+
case "ENTRANCE_EXIT":
|
|
31
|
+
return LocationType.ENTRANCE_EXIT;
|
|
32
|
+
case 3:
|
|
33
|
+
case "GENERIC_NODE":
|
|
34
|
+
return LocationType.GENERIC_NODE;
|
|
35
|
+
case 4:
|
|
36
|
+
case "BOARDING_AREA":
|
|
37
|
+
return LocationType.BOARDING_AREA;
|
|
38
|
+
case -1:
|
|
39
|
+
case "UNRECOGNIZED":
|
|
40
|
+
default:
|
|
41
|
+
return LocationType.UNRECOGNIZED;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function locationTypeToJSON(object: LocationType): string {
|
|
46
|
+
switch (object) {
|
|
47
|
+
case LocationType.SIMPLE_STOP_OR_PLATFORM:
|
|
48
|
+
return "SIMPLE_STOP_OR_PLATFORM";
|
|
49
|
+
case LocationType.STATION:
|
|
50
|
+
return "STATION";
|
|
51
|
+
case LocationType.ENTRANCE_EXIT:
|
|
52
|
+
return "ENTRANCE_EXIT";
|
|
53
|
+
case LocationType.GENERIC_NODE:
|
|
54
|
+
return "GENERIC_NODE";
|
|
55
|
+
case LocationType.BOARDING_AREA:
|
|
56
|
+
return "BOARDING_AREA";
|
|
57
|
+
case LocationType.UNRECOGNIZED:
|
|
58
|
+
default:
|
|
59
|
+
return "UNRECOGNIZED";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Stop {
|
|
64
|
+
name: string;
|
|
65
|
+
lat?: number | undefined;
|
|
66
|
+
lon?: number | undefined;
|
|
67
|
+
children: string[];
|
|
68
|
+
parent?: string | undefined;
|
|
69
|
+
locationType: LocationType;
|
|
70
|
+
platform?: string | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface StopsMap {
|
|
74
|
+
version: string;
|
|
75
|
+
stops: { [key: string]: Stop };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface StopsMap_StopsEntry {
|
|
79
|
+
key: string;
|
|
80
|
+
value: Stop | undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createBaseStop(): Stop {
|
|
84
|
+
return {
|
|
85
|
+
name: "",
|
|
86
|
+
lat: undefined,
|
|
87
|
+
lon: undefined,
|
|
88
|
+
children: [],
|
|
89
|
+
parent: undefined,
|
|
90
|
+
locationType: 0,
|
|
91
|
+
platform: undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const Stop: MessageFns<Stop> = {
|
|
96
|
+
encode(message: Stop, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
|
97
|
+
if (message.name !== "") {
|
|
98
|
+
writer.uint32(10).string(message.name);
|
|
99
|
+
}
|
|
100
|
+
if (message.lat !== undefined) {
|
|
101
|
+
writer.uint32(17).double(message.lat);
|
|
102
|
+
}
|
|
103
|
+
if (message.lon !== undefined) {
|
|
104
|
+
writer.uint32(25).double(message.lon);
|
|
105
|
+
}
|
|
106
|
+
for (const v of message.children) {
|
|
107
|
+
writer.uint32(34).string(v!);
|
|
108
|
+
}
|
|
109
|
+
if (message.parent !== undefined) {
|
|
110
|
+
writer.uint32(42).string(message.parent);
|
|
111
|
+
}
|
|
112
|
+
if (message.locationType !== 0) {
|
|
113
|
+
writer.uint32(48).int32(message.locationType);
|
|
114
|
+
}
|
|
115
|
+
if (message.platform !== undefined) {
|
|
116
|
+
writer.uint32(58).string(message.platform);
|
|
117
|
+
}
|
|
118
|
+
return writer;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
decode(input: BinaryReader | Uint8Array, length?: number): Stop {
|
|
122
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
123
|
+
let end = length === undefined ? reader.len : reader.pos + length;
|
|
124
|
+
const message = createBaseStop();
|
|
125
|
+
while (reader.pos < end) {
|
|
126
|
+
const tag = reader.uint32();
|
|
127
|
+
switch (tag >>> 3) {
|
|
128
|
+
case 1: {
|
|
129
|
+
if (tag !== 10) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
message.name = reader.string();
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
case 2: {
|
|
137
|
+
if (tag !== 17) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
message.lat = reader.double();
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
case 3: {
|
|
145
|
+
if (tag !== 25) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
message.lon = reader.double();
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
case 4: {
|
|
153
|
+
if (tag !== 34) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
message.children.push(reader.string());
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
case 5: {
|
|
161
|
+
if (tag !== 42) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
message.parent = reader.string();
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
case 6: {
|
|
169
|
+
if (tag !== 48) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
message.locationType = reader.int32() as any;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
case 7: {
|
|
177
|
+
if (tag !== 58) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
message.platform = reader.string();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
reader.skip(tag & 7);
|
|
189
|
+
}
|
|
190
|
+
return message;
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
fromJSON(object: any): Stop {
|
|
194
|
+
return {
|
|
195
|
+
name: isSet(object.name) ? globalThis.String(object.name) : "",
|
|
196
|
+
lat: isSet(object.lat) ? globalThis.Number(object.lat) : undefined,
|
|
197
|
+
lon: isSet(object.lon) ? globalThis.Number(object.lon) : undefined,
|
|
198
|
+
children: globalThis.Array.isArray(object?.children) ? object.children.map((e: any) => globalThis.String(e)) : [],
|
|
199
|
+
parent: isSet(object.parent) ? globalThis.String(object.parent) : undefined,
|
|
200
|
+
locationType: isSet(object.locationType) ? locationTypeFromJSON(object.locationType) : 0,
|
|
201
|
+
platform: isSet(object.platform) ? globalThis.String(object.platform) : undefined,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
toJSON(message: Stop): unknown {
|
|
206
|
+
const obj: any = {};
|
|
207
|
+
if (message.name !== "") {
|
|
208
|
+
obj.name = message.name;
|
|
209
|
+
}
|
|
210
|
+
if (message.lat !== undefined) {
|
|
211
|
+
obj.lat = message.lat;
|
|
212
|
+
}
|
|
213
|
+
if (message.lon !== undefined) {
|
|
214
|
+
obj.lon = message.lon;
|
|
215
|
+
}
|
|
216
|
+
if (message.children?.length) {
|
|
217
|
+
obj.children = message.children;
|
|
218
|
+
}
|
|
219
|
+
if (message.parent !== undefined) {
|
|
220
|
+
obj.parent = message.parent;
|
|
221
|
+
}
|
|
222
|
+
if (message.locationType !== 0) {
|
|
223
|
+
obj.locationType = locationTypeToJSON(message.locationType);
|
|
224
|
+
}
|
|
225
|
+
if (message.platform !== undefined) {
|
|
226
|
+
obj.platform = message.platform;
|
|
227
|
+
}
|
|
228
|
+
return obj;
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
create<I extends Exact<DeepPartial<Stop>, I>>(base?: I): Stop {
|
|
232
|
+
return Stop.fromPartial(base ?? ({} as any));
|
|
233
|
+
},
|
|
234
|
+
fromPartial<I extends Exact<DeepPartial<Stop>, I>>(object: I): Stop {
|
|
235
|
+
const message = createBaseStop();
|
|
236
|
+
message.name = object.name ?? "";
|
|
237
|
+
message.lat = object.lat ?? undefined;
|
|
238
|
+
message.lon = object.lon ?? undefined;
|
|
239
|
+
message.children = object.children?.map((e) => e) || [];
|
|
240
|
+
message.parent = object.parent ?? undefined;
|
|
241
|
+
message.locationType = object.locationType ?? 0;
|
|
242
|
+
message.platform = object.platform ?? undefined;
|
|
243
|
+
return message;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
function createBaseStopsMap(): StopsMap {
|
|
248
|
+
return { version: "", stops: {} };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const StopsMap: MessageFns<StopsMap> = {
|
|
252
|
+
encode(message: StopsMap, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
|
253
|
+
if (message.version !== "") {
|
|
254
|
+
writer.uint32(10).string(message.version);
|
|
255
|
+
}
|
|
256
|
+
Object.entries(message.stops).forEach(([key, value]) => {
|
|
257
|
+
StopsMap_StopsEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).join();
|
|
258
|
+
});
|
|
259
|
+
return writer;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
decode(input: BinaryReader | Uint8Array, length?: number): StopsMap {
|
|
263
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
264
|
+
let end = length === undefined ? reader.len : reader.pos + length;
|
|
265
|
+
const message = createBaseStopsMap();
|
|
266
|
+
while (reader.pos < end) {
|
|
267
|
+
const tag = reader.uint32();
|
|
268
|
+
switch (tag >>> 3) {
|
|
269
|
+
case 1: {
|
|
270
|
+
if (tag !== 10) {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
message.version = reader.string();
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
case 2: {
|
|
278
|
+
if (tag !== 18) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const entry2 = StopsMap_StopsEntry.decode(reader, reader.uint32());
|
|
283
|
+
if (entry2.value !== undefined) {
|
|
284
|
+
message.stops[entry2.key] = entry2.value;
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
reader.skip(tag & 7);
|
|
293
|
+
}
|
|
294
|
+
return message;
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
fromJSON(object: any): StopsMap {
|
|
298
|
+
return {
|
|
299
|
+
version: isSet(object.version) ? globalThis.String(object.version) : "",
|
|
300
|
+
stops: isObject(object.stops)
|
|
301
|
+
? Object.entries(object.stops).reduce<{ [key: string]: Stop }>((acc, [key, value]) => {
|
|
302
|
+
acc[key] = Stop.fromJSON(value);
|
|
303
|
+
return acc;
|
|
304
|
+
}, {})
|
|
305
|
+
: {},
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
toJSON(message: StopsMap): unknown {
|
|
310
|
+
const obj: any = {};
|
|
311
|
+
if (message.version !== "") {
|
|
312
|
+
obj.version = message.version;
|
|
313
|
+
}
|
|
314
|
+
if (message.stops) {
|
|
315
|
+
const entries = Object.entries(message.stops);
|
|
316
|
+
if (entries.length > 0) {
|
|
317
|
+
obj.stops = {};
|
|
318
|
+
entries.forEach(([k, v]) => {
|
|
319
|
+
obj.stops[k] = Stop.toJSON(v);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return obj;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
create<I extends Exact<DeepPartial<StopsMap>, I>>(base?: I): StopsMap {
|
|
327
|
+
return StopsMap.fromPartial(base ?? ({} as any));
|
|
328
|
+
},
|
|
329
|
+
fromPartial<I extends Exact<DeepPartial<StopsMap>, I>>(object: I): StopsMap {
|
|
330
|
+
const message = createBaseStopsMap();
|
|
331
|
+
message.version = object.version ?? "";
|
|
332
|
+
message.stops = Object.entries(object.stops ?? {}).reduce<{ [key: string]: Stop }>((acc, [key, value]) => {
|
|
333
|
+
if (value !== undefined) {
|
|
334
|
+
acc[key] = Stop.fromPartial(value);
|
|
335
|
+
}
|
|
336
|
+
return acc;
|
|
337
|
+
}, {});
|
|
338
|
+
return message;
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
function createBaseStopsMap_StopsEntry(): StopsMap_StopsEntry {
|
|
343
|
+
return { key: "", value: undefined };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export const StopsMap_StopsEntry: MessageFns<StopsMap_StopsEntry> = {
|
|
347
|
+
encode(message: StopsMap_StopsEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
|
348
|
+
if (message.key !== "") {
|
|
349
|
+
writer.uint32(10).string(message.key);
|
|
350
|
+
}
|
|
351
|
+
if (message.value !== undefined) {
|
|
352
|
+
Stop.encode(message.value, writer.uint32(18).fork()).join();
|
|
353
|
+
}
|
|
354
|
+
return writer;
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
decode(input: BinaryReader | Uint8Array, length?: number): StopsMap_StopsEntry {
|
|
358
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
359
|
+
let end = length === undefined ? reader.len : reader.pos + length;
|
|
360
|
+
const message = createBaseStopsMap_StopsEntry();
|
|
361
|
+
while (reader.pos < end) {
|
|
362
|
+
const tag = reader.uint32();
|
|
363
|
+
switch (tag >>> 3) {
|
|
364
|
+
case 1: {
|
|
365
|
+
if (tag !== 10) {
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
message.key = reader.string();
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
case 2: {
|
|
373
|
+
if (tag !== 18) {
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
message.value = Stop.decode(reader, reader.uint32());
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
reader.skip(tag & 7);
|
|
385
|
+
}
|
|
386
|
+
return message;
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
fromJSON(object: any): StopsMap_StopsEntry {
|
|
390
|
+
return {
|
|
391
|
+
key: isSet(object.key) ? globalThis.String(object.key) : "",
|
|
392
|
+
value: isSet(object.value) ? Stop.fromJSON(object.value) : undefined,
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
toJSON(message: StopsMap_StopsEntry): unknown {
|
|
397
|
+
const obj: any = {};
|
|
398
|
+
if (message.key !== "") {
|
|
399
|
+
obj.key = message.key;
|
|
400
|
+
}
|
|
401
|
+
if (message.value !== undefined) {
|
|
402
|
+
obj.value = Stop.toJSON(message.value);
|
|
403
|
+
}
|
|
404
|
+
return obj;
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
create<I extends Exact<DeepPartial<StopsMap_StopsEntry>, I>>(base?: I): StopsMap_StopsEntry {
|
|
408
|
+
return StopsMap_StopsEntry.fromPartial(base ?? ({} as any));
|
|
409
|
+
},
|
|
410
|
+
fromPartial<I extends Exact<DeepPartial<StopsMap_StopsEntry>, I>>(object: I): StopsMap_StopsEntry {
|
|
411
|
+
const message = createBaseStopsMap_StopsEntry();
|
|
412
|
+
message.key = object.key ?? "";
|
|
413
|
+
message.value = (object.value !== undefined && object.value !== null) ? Stop.fromPartial(object.value) : undefined;
|
|
414
|
+
return message;
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
|
|
419
|
+
|
|
420
|
+
export type DeepPartial<T> = T extends Builtin ? T
|
|
421
|
+
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
|
|
422
|
+
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
|
423
|
+
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
424
|
+
: Partial<T>;
|
|
425
|
+
|
|
426
|
+
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
|
427
|
+
export type Exact<P, I extends P> = P extends Builtin ? P
|
|
428
|
+
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
|
|
429
|
+
|
|
430
|
+
function isObject(value: any): boolean {
|
|
431
|
+
return typeof value === "object" && value !== null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function isSet(value: any): boolean {
|
|
435
|
+
return value !== null && value !== undefined;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export interface MessageFns<T> {
|
|
439
|
+
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
|
440
|
+
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
|
441
|
+
fromJSON(object: any): T;
|
|
442
|
+
toJSON(message: T): unknown;
|
|
443
|
+
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
|
|
444
|
+
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
|
|
445
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type StopId = string;
|
|
2
|
+
export type Platform = string;
|
|
3
|
+
export type Latitude = number;
|
|
4
|
+
export type Longitude = number;
|
|
5
|
+
|
|
6
|
+
export type LocationType =
|
|
7
|
+
| 'SIMPLE_STOP_OR_PLATFORM'
|
|
8
|
+
| 'STATION'
|
|
9
|
+
| 'ENTRANCE_EXIT'
|
|
10
|
+
| 'GENERIC_NODE'
|
|
11
|
+
| 'BOARDING_AREA';
|
|
12
|
+
|
|
13
|
+
export type Stop = {
|
|
14
|
+
id: StopId;
|
|
15
|
+
name: string;
|
|
16
|
+
lat?: Latitude;
|
|
17
|
+
lon?: Longitude;
|
|
18
|
+
children: StopId[];
|
|
19
|
+
parent?: StopId;
|
|
20
|
+
locationType: LocationType;
|
|
21
|
+
platform?: Platform;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type StopsMap = Map<StopId, Stop>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
|
|
2
|
+
import { around } from 'geokdbush';
|
|
3
|
+
import KDTree from 'kdbush';
|
|
4
|
+
import { addAll, createIndex, search, SearchResult } from 'slimsearch';
|
|
5
|
+
|
|
6
|
+
import { generateAccentVariants } from './i18n.js';
|
|
7
|
+
import { deserializeStopsMap, serializeStopsMap } from './io.js';
|
|
8
|
+
import { StopsMap as ProtoStopsMap } from './proto/stops.js';
|
|
9
|
+
import { Stop, StopId, StopsMap } from './stops.js';
|
|
10
|
+
|
|
11
|
+
type StopPoint = { id: StopId; lat: number; lon: number };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The StopMap class provides functionality to search for public transport stops
|
|
15
|
+
* by name or geographic location. It leverages text search and geospatial indexing
|
|
16
|
+
* to efficiently find stops based on user queries.
|
|
17
|
+
*/
|
|
18
|
+
export class StopsIndex {
|
|
19
|
+
private readonly stopsMap: StopsMap;
|
|
20
|
+
private readonly textIndex;
|
|
21
|
+
private readonly geoIndex: KDTree;
|
|
22
|
+
private readonly stopPoints: StopPoint[];
|
|
23
|
+
|
|
24
|
+
constructor(stopsMap: StopsMap) {
|
|
25
|
+
this.stopsMap = stopsMap;
|
|
26
|
+
this.textIndex = createIndex({
|
|
27
|
+
fields: ['name'],
|
|
28
|
+
storeFields: ['id'],
|
|
29
|
+
searchOptions: { prefix: true, fuzzy: 0.2 },
|
|
30
|
+
processTerm: generateAccentVariants,
|
|
31
|
+
});
|
|
32
|
+
const stopsSet = new Map<string, { id: StopId; name: string }>();
|
|
33
|
+
for (const [id, stop] of stopsMap.entries()) {
|
|
34
|
+
const effectiveStopId = stop.parent ?? id;
|
|
35
|
+
if (!stopsSet.has(effectiveStopId)) {
|
|
36
|
+
stopsSet.set(effectiveStopId, {
|
|
37
|
+
id: effectiveStopId,
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
39
|
+
name: stop.parent ? this.stopsMap.get(stop.parent)!.name : stop.name,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const stopsArray = Array.from(stopsSet.values());
|
|
44
|
+
addAll(this.textIndex, stopsArray);
|
|
45
|
+
|
|
46
|
+
this.stopPoints = Array.from(this.stopsMap.entries())
|
|
47
|
+
.filter(([, stop]) => {
|
|
48
|
+
if (stop.lat && stop.lon) return true;
|
|
49
|
+
return false;
|
|
50
|
+
})
|
|
51
|
+
.map(([id, stop]) => ({
|
|
52
|
+
id: id,
|
|
53
|
+
lat: stop.lat as number,
|
|
54
|
+
lon: stop.lon as number,
|
|
55
|
+
}));
|
|
56
|
+
this.geoIndex = new KDTree(this.stopPoints.length);
|
|
57
|
+
for (const { lat, lon } of this.stopPoints) {
|
|
58
|
+
this.geoIndex.add(lon, lat);
|
|
59
|
+
}
|
|
60
|
+
this.geoIndex.finish();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deserializes a binary representation of the stops.
|
|
65
|
+
*
|
|
66
|
+
* @param {Uint8Array} data - The binary data to deserialize.
|
|
67
|
+
* @returns {StopsMap} - The deserialized StopFinder.
|
|
68
|
+
*/
|
|
69
|
+
static fromData(data: Uint8Array): StopsIndex {
|
|
70
|
+
const reader = new BinaryReader(data);
|
|
71
|
+
const protoStopsMap = ProtoStopsMap.decode(reader);
|
|
72
|
+
|
|
73
|
+
return new StopsIndex(deserializeStopsMap(protoStopsMap));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Serializes the stops into a binary protobuf.
|
|
78
|
+
*
|
|
79
|
+
* @returns {Uint8Array} - The serialized binary data.
|
|
80
|
+
*/
|
|
81
|
+
serialize(): Uint8Array {
|
|
82
|
+
const protoStopsMap: ProtoStopsMap = serializeStopsMap(this.stopsMap);
|
|
83
|
+
|
|
84
|
+
const writer = new BinaryWriter();
|
|
85
|
+
ProtoStopsMap.encode(protoStopsMap, writer);
|
|
86
|
+
return writer.finish();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Finds stops by their name using a text search.
|
|
91
|
+
*
|
|
92
|
+
* @param query - The name or partial name of the stop to search for.
|
|
93
|
+
* @param maxResults - The maximum number of results to return (default is 5).
|
|
94
|
+
* @returns An array of Stop objects that match the search query.
|
|
95
|
+
*/
|
|
96
|
+
findStopsByName(query: string, maxResults = 5): Stop[] {
|
|
97
|
+
const results = search(this.textIndex, query).map(
|
|
98
|
+
(result: SearchResult) => this.stopsMap.get(result.id as string) as Stop,
|
|
99
|
+
);
|
|
100
|
+
return results.slice(0, maxResults);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Finds stops by their geographic location using latitude and longitude.
|
|
105
|
+
*
|
|
106
|
+
* @param lat - The latitude of the location to search near.
|
|
107
|
+
* @param lon - The longitude of the location to search near.
|
|
108
|
+
* @param maxResults - The maximum number of results to return (default is 10).
|
|
109
|
+
* @param radius - The search radius in kilometers (default is 0.5).
|
|
110
|
+
* @returns An array of Stop objects that are closest to the specified location.
|
|
111
|
+
*/
|
|
112
|
+
findStopsByLocation(
|
|
113
|
+
lat: number,
|
|
114
|
+
lon: number,
|
|
115
|
+
maxResults = 5,
|
|
116
|
+
radius = 0.5,
|
|
117
|
+
): Stop[] {
|
|
118
|
+
const nearestStops = around(
|
|
119
|
+
this.geoIndex,
|
|
120
|
+
lon,
|
|
121
|
+
lat,
|
|
122
|
+
maxResults,
|
|
123
|
+
radius,
|
|
124
|
+
).map((id) => {
|
|
125
|
+
const stopPoint = this.stopPoints[id as number] as StopPoint;
|
|
126
|
+
return this.stopsMap.get(stopPoint.id) as Stop;
|
|
127
|
+
});
|
|
128
|
+
return nearestStops;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Finds a stop by its ID.
|
|
133
|
+
*
|
|
134
|
+
* @param id - The ID of the stop to search for.
|
|
135
|
+
* @returns The Stop object that matches the specified ID, or undefined if not found.
|
|
136
|
+
*/
|
|
137
|
+
findStopById(id: StopId): Stop | undefined {
|
|
138
|
+
return this.stopsMap.get(id);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
equivalentStops(id: StopId): StopId[] {
|
|
142
|
+
const stop = this.stopsMap.get(id);
|
|
143
|
+
if (!stop) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const equivalentStops = stop.parent
|
|
147
|
+
? (this.stopsMap.get(stop.parent)?.children ?? [])
|
|
148
|
+
: stop.children;
|
|
149
|
+
return Array.from(new Set([id, ...equivalentStops]));
|
|
150
|
+
}
|
|
151
|
+
}
|