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/dist/cli.mjs
CHANGED
|
@@ -13,58 +13,6 @@ import require$$5, { Transform } from 'stream';
|
|
|
13
13
|
import { performance as performance$1 } from 'perf_hooks';
|
|
14
14
|
import repl from 'node:repl';
|
|
15
15
|
|
|
16
|
-
/******************************************************************************
|
|
17
|
-
Copyright (c) Microsoft Corporation.
|
|
18
|
-
|
|
19
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
20
|
-
purpose with or without fee is hereby granted.
|
|
21
|
-
|
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
23
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
24
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
25
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
26
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
27
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
28
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
29
|
-
***************************************************************************** */
|
|
30
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
34
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
35
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
36
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
37
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
38
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
39
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function __values(o) {
|
|
44
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
45
|
-
if (m) return m.call(o);
|
|
46
|
-
if (o && typeof o.length === "number") return {
|
|
47
|
-
next: function () {
|
|
48
|
-
if (o && i >= o.length) o = void 0;
|
|
49
|
-
return { value: o && o[i++], done: !o };
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function __asyncValues(o) {
|
|
56
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
57
|
-
var m = o[Symbol.asyncIterator], i;
|
|
58
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
59
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
60
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
64
|
-
var e = new Error(message);
|
|
65
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
16
|
function getDefaultExportFromCjs (x) {
|
|
69
17
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
70
18
|
}
|
|
@@ -15864,14 +15812,13 @@ const Stop = {
|
|
|
15864
15812
|
sourceStopId: isSet$1(object.sourceStopId) ? globalThis.String(object.sourceStopId) : "",
|
|
15865
15813
|
lat: isSet$1(object.lat) ? globalThis.Number(object.lat) : undefined,
|
|
15866
15814
|
lon: isSet$1(object.lon) ? globalThis.Number(object.lon) : undefined,
|
|
15867
|
-
children: globalThis.Array.isArray(object
|
|
15815
|
+
children: globalThis.Array.isArray(object?.children) ? object.children.map((e) => globalThis.Number(e)) : [],
|
|
15868
15816
|
parent: isSet$1(object.parent) ? globalThis.Number(object.parent) : undefined,
|
|
15869
15817
|
locationType: isSet$1(object.locationType) ? locationTypeFromJSON(object.locationType) : 0,
|
|
15870
15818
|
platform: isSet$1(object.platform) ? globalThis.String(object.platform) : undefined,
|
|
15871
15819
|
};
|
|
15872
15820
|
},
|
|
15873
15821
|
toJSON(message) {
|
|
15874
|
-
var _a;
|
|
15875
15822
|
const obj = {};
|
|
15876
15823
|
if (message.name !== "") {
|
|
15877
15824
|
obj.name = message.name;
|
|
@@ -15885,7 +15832,7 @@ const Stop = {
|
|
|
15885
15832
|
if (message.lon !== undefined) {
|
|
15886
15833
|
obj.lon = message.lon;
|
|
15887
15834
|
}
|
|
15888
|
-
if (
|
|
15835
|
+
if (message.children?.length) {
|
|
15889
15836
|
obj.children = message.children.map((e) => Math.round(e));
|
|
15890
15837
|
}
|
|
15891
15838
|
if (message.parent !== undefined) {
|
|
@@ -15900,19 +15847,18 @@ const Stop = {
|
|
|
15900
15847
|
return obj;
|
|
15901
15848
|
},
|
|
15902
15849
|
create(base) {
|
|
15903
|
-
return Stop.fromPartial(base
|
|
15850
|
+
return Stop.fromPartial(base ?? {});
|
|
15904
15851
|
},
|
|
15905
15852
|
fromPartial(object) {
|
|
15906
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
15907
15853
|
const message = createBaseStop();
|
|
15908
|
-
message.name =
|
|
15909
|
-
message.sourceStopId =
|
|
15910
|
-
message.lat =
|
|
15911
|
-
message.lon =
|
|
15912
|
-
message.children =
|
|
15913
|
-
message.parent =
|
|
15914
|
-
message.locationType =
|
|
15915
|
-
message.platform =
|
|
15854
|
+
message.name = object.name ?? "";
|
|
15855
|
+
message.sourceStopId = object.sourceStopId ?? "";
|
|
15856
|
+
message.lat = object.lat ?? undefined;
|
|
15857
|
+
message.lon = object.lon ?? undefined;
|
|
15858
|
+
message.children = object.children?.map((e) => e) || [];
|
|
15859
|
+
message.parent = object.parent ?? undefined;
|
|
15860
|
+
message.locationType = object.locationType ?? 0;
|
|
15861
|
+
message.platform = object.platform ?? undefined;
|
|
15916
15862
|
return message;
|
|
15917
15863
|
},
|
|
15918
15864
|
};
|
|
@@ -15961,28 +15907,26 @@ const StopsMap = {
|
|
|
15961
15907
|
fromJSON(object) {
|
|
15962
15908
|
return {
|
|
15963
15909
|
version: isSet$1(object.version) ? globalThis.String(object.version) : "",
|
|
15964
|
-
stops: globalThis.Array.isArray(object
|
|
15910
|
+
stops: globalThis.Array.isArray(object?.stops) ? object.stops.map((e) => Stop.fromJSON(e)) : [],
|
|
15965
15911
|
};
|
|
15966
15912
|
},
|
|
15967
15913
|
toJSON(message) {
|
|
15968
|
-
var _a;
|
|
15969
15914
|
const obj = {};
|
|
15970
15915
|
if (message.version !== "") {
|
|
15971
15916
|
obj.version = message.version;
|
|
15972
15917
|
}
|
|
15973
|
-
if (
|
|
15918
|
+
if (message.stops?.length) {
|
|
15974
15919
|
obj.stops = message.stops.map((e) => Stop.toJSON(e));
|
|
15975
15920
|
}
|
|
15976
15921
|
return obj;
|
|
15977
15922
|
},
|
|
15978
15923
|
create(base) {
|
|
15979
|
-
return StopsMap.fromPartial(base
|
|
15924
|
+
return StopsMap.fromPartial(base ?? {});
|
|
15980
15925
|
},
|
|
15981
15926
|
fromPartial(object) {
|
|
15982
|
-
var _a, _b;
|
|
15983
15927
|
const message = createBaseStopsMap();
|
|
15984
|
-
message.version =
|
|
15985
|
-
message.stops =
|
|
15928
|
+
message.version = object.version ?? "";
|
|
15929
|
+
message.stops = object.stops?.map((e) => Stop.fromPartial(e)) || [];
|
|
15986
15930
|
return message;
|
|
15987
15931
|
},
|
|
15988
15932
|
};
|
|
@@ -16066,8 +16010,12 @@ const serializeLocationType = (locationType) => {
|
|
|
16066
16010
|
* to efficiently find stops based on user queries.
|
|
16067
16011
|
*/
|
|
16068
16012
|
class StopsIndex {
|
|
16013
|
+
stops;
|
|
16014
|
+
sourceStopsMap;
|
|
16015
|
+
textIndex;
|
|
16016
|
+
geoIndex;
|
|
16017
|
+
stopPoints;
|
|
16069
16018
|
constructor(stops) {
|
|
16070
|
-
var _a;
|
|
16071
16019
|
this.stops = stops;
|
|
16072
16020
|
this.sourceStopsMap = new Map();
|
|
16073
16021
|
const stopsSet = new Map();
|
|
@@ -16076,7 +16024,7 @@ class StopsIndex {
|
|
|
16076
16024
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
16077
16025
|
const stop = stops[id];
|
|
16078
16026
|
this.sourceStopsMap.set(stop.sourceStopId, id);
|
|
16079
|
-
const effectiveStopId =
|
|
16027
|
+
const effectiveStopId = stop.parent ?? id;
|
|
16080
16028
|
if (!stopsSet.has(effectiveStopId)) {
|
|
16081
16029
|
stopsSet.set(effectiveStopId, {
|
|
16082
16030
|
id: effectiveStopId,
|
|
@@ -16191,7 +16139,6 @@ class StopsIndex {
|
|
|
16191
16139
|
* Find ids of all sibling stops.
|
|
16192
16140
|
*/
|
|
16193
16141
|
equivalentStops(sourceId) {
|
|
16194
|
-
var _a, _b;
|
|
16195
16142
|
const id = this.sourceStopsMap.get(sourceId);
|
|
16196
16143
|
if (id === undefined) {
|
|
16197
16144
|
return [];
|
|
@@ -16201,13 +16148,14 @@ class StopsIndex {
|
|
|
16201
16148
|
return [];
|
|
16202
16149
|
}
|
|
16203
16150
|
const equivalentStops = stop.parent
|
|
16204
|
-
? (
|
|
16151
|
+
? (this.stops[stop.parent]?.children ?? [])
|
|
16205
16152
|
: stop.children;
|
|
16206
16153
|
return Array.from(new Set([id, ...equivalentStops])).map((stopId) => this.stops[stopId]);
|
|
16207
16154
|
}
|
|
16208
16155
|
}
|
|
16209
16156
|
|
|
16210
16157
|
class Duration {
|
|
16158
|
+
totalSeconds;
|
|
16211
16159
|
constructor(totalSeconds) {
|
|
16212
16160
|
this.totalSeconds = totalSeconds;
|
|
16213
16161
|
}
|
|
@@ -16510,15 +16458,14 @@ const Route$2 = {
|
|
|
16510
16458
|
return obj;
|
|
16511
16459
|
},
|
|
16512
16460
|
create(base) {
|
|
16513
|
-
return Route$2.fromPartial(base
|
|
16461
|
+
return Route$2.fromPartial(base ?? {});
|
|
16514
16462
|
},
|
|
16515
16463
|
fromPartial(object) {
|
|
16516
|
-
var _a, _b, _c, _d;
|
|
16517
16464
|
const message = createBaseRoute();
|
|
16518
|
-
message.stopTimes =
|
|
16519
|
-
message.pickUpDropOffTypes =
|
|
16520
|
-
message.stops =
|
|
16521
|
-
message.serviceRouteId =
|
|
16465
|
+
message.stopTimes = object.stopTimes ?? new Uint8Array(0);
|
|
16466
|
+
message.pickUpDropOffTypes = object.pickUpDropOffTypes ?? new Uint8Array(0);
|
|
16467
|
+
message.stops = object.stops ?? new Uint8Array(0);
|
|
16468
|
+
message.serviceRouteId = object.serviceRouteId ?? 0;
|
|
16522
16469
|
return message;
|
|
16523
16470
|
},
|
|
16524
16471
|
};
|
|
@@ -16595,52 +16542,227 @@ const Transfer = {
|
|
|
16595
16542
|
return obj;
|
|
16596
16543
|
},
|
|
16597
16544
|
create(base) {
|
|
16598
|
-
return Transfer.fromPartial(base
|
|
16545
|
+
return Transfer.fromPartial(base ?? {});
|
|
16599
16546
|
},
|
|
16600
16547
|
fromPartial(object) {
|
|
16601
|
-
var _a, _b, _c;
|
|
16602
16548
|
const message = createBaseTransfer();
|
|
16603
|
-
message.destination =
|
|
16604
|
-
message.type =
|
|
16605
|
-
message.minTransferTime =
|
|
16549
|
+
message.destination = object.destination ?? 0;
|
|
16550
|
+
message.type = object.type ?? 0;
|
|
16551
|
+
message.minTransferTime = object.minTransferTime ?? undefined;
|
|
16606
16552
|
return message;
|
|
16607
16553
|
},
|
|
16608
16554
|
};
|
|
16609
|
-
function
|
|
16610
|
-
return {
|
|
16555
|
+
function createBaseTripBoarding() {
|
|
16556
|
+
return { hopOnStopIndex: 0, routeId: 0, tripIndex: 0 };
|
|
16611
16557
|
}
|
|
16612
|
-
const
|
|
16558
|
+
const TripBoarding = {
|
|
16613
16559
|
encode(message, writer = new BinaryWriter()) {
|
|
16614
|
-
|
|
16615
|
-
|
|
16560
|
+
if (message.hopOnStopIndex !== 0) {
|
|
16561
|
+
writer.uint32(8).uint32(message.hopOnStopIndex);
|
|
16616
16562
|
}
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16563
|
+
if (message.routeId !== 0) {
|
|
16564
|
+
writer.uint32(16).uint32(message.routeId);
|
|
16565
|
+
}
|
|
16566
|
+
if (message.tripIndex !== 0) {
|
|
16567
|
+
writer.uint32(24).uint32(message.tripIndex);
|
|
16620
16568
|
}
|
|
16621
|
-
writer.join();
|
|
16622
16569
|
return writer;
|
|
16623
16570
|
},
|
|
16624
16571
|
decode(input, length) {
|
|
16625
16572
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16626
16573
|
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16627
|
-
const message =
|
|
16574
|
+
const message = createBaseTripBoarding();
|
|
16628
16575
|
while (reader.pos < end) {
|
|
16629
16576
|
const tag = reader.uint32();
|
|
16630
16577
|
switch (tag >>> 3) {
|
|
16631
16578
|
case 1: {
|
|
16632
|
-
if (tag !==
|
|
16579
|
+
if (tag !== 8) {
|
|
16633
16580
|
break;
|
|
16634
16581
|
}
|
|
16635
|
-
message.
|
|
16582
|
+
message.hopOnStopIndex = reader.uint32();
|
|
16583
|
+
continue;
|
|
16584
|
+
}
|
|
16585
|
+
case 2: {
|
|
16586
|
+
if (tag !== 16) {
|
|
16587
|
+
break;
|
|
16588
|
+
}
|
|
16589
|
+
message.routeId = reader.uint32();
|
|
16590
|
+
continue;
|
|
16591
|
+
}
|
|
16592
|
+
case 3: {
|
|
16593
|
+
if (tag !== 24) {
|
|
16594
|
+
break;
|
|
16595
|
+
}
|
|
16596
|
+
message.tripIndex = reader.uint32();
|
|
16597
|
+
continue;
|
|
16598
|
+
}
|
|
16599
|
+
}
|
|
16600
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
16601
|
+
break;
|
|
16602
|
+
}
|
|
16603
|
+
reader.skip(tag & 7);
|
|
16604
|
+
}
|
|
16605
|
+
return message;
|
|
16606
|
+
},
|
|
16607
|
+
fromJSON(object) {
|
|
16608
|
+
return {
|
|
16609
|
+
hopOnStopIndex: isSet(object.hopOnStopIndex) ? globalThis.Number(object.hopOnStopIndex) : 0,
|
|
16610
|
+
routeId: isSet(object.routeId) ? globalThis.Number(object.routeId) : 0,
|
|
16611
|
+
tripIndex: isSet(object.tripIndex) ? globalThis.Number(object.tripIndex) : 0,
|
|
16612
|
+
};
|
|
16613
|
+
},
|
|
16614
|
+
toJSON(message) {
|
|
16615
|
+
const obj = {};
|
|
16616
|
+
if (message.hopOnStopIndex !== 0) {
|
|
16617
|
+
obj.hopOnStopIndex = Math.round(message.hopOnStopIndex);
|
|
16618
|
+
}
|
|
16619
|
+
if (message.routeId !== 0) {
|
|
16620
|
+
obj.routeId = Math.round(message.routeId);
|
|
16621
|
+
}
|
|
16622
|
+
if (message.tripIndex !== 0) {
|
|
16623
|
+
obj.tripIndex = Math.round(message.tripIndex);
|
|
16624
|
+
}
|
|
16625
|
+
return obj;
|
|
16626
|
+
},
|
|
16627
|
+
create(base) {
|
|
16628
|
+
return TripBoarding.fromPartial(base ?? {});
|
|
16629
|
+
},
|
|
16630
|
+
fromPartial(object) {
|
|
16631
|
+
const message = createBaseTripBoarding();
|
|
16632
|
+
message.hopOnStopIndex = object.hopOnStopIndex ?? 0;
|
|
16633
|
+
message.routeId = object.routeId ?? 0;
|
|
16634
|
+
message.tripIndex = object.tripIndex ?? 0;
|
|
16635
|
+
return message;
|
|
16636
|
+
},
|
|
16637
|
+
};
|
|
16638
|
+
function createBaseTripContinuationEntry() {
|
|
16639
|
+
return { originStopIndex: 0, originRouteId: 0, originTripIndex: 0, continuations: [] };
|
|
16640
|
+
}
|
|
16641
|
+
const TripContinuationEntry = {
|
|
16642
|
+
encode(message, writer = new BinaryWriter()) {
|
|
16643
|
+
if (message.originStopIndex !== 0) {
|
|
16644
|
+
writer.uint32(8).uint32(message.originStopIndex);
|
|
16645
|
+
}
|
|
16646
|
+
if (message.originRouteId !== 0) {
|
|
16647
|
+
writer.uint32(16).uint32(message.originRouteId);
|
|
16648
|
+
}
|
|
16649
|
+
if (message.originTripIndex !== 0) {
|
|
16650
|
+
writer.uint32(24).uint32(message.originTripIndex);
|
|
16651
|
+
}
|
|
16652
|
+
for (const v of message.continuations) {
|
|
16653
|
+
TripBoarding.encode(v, writer.uint32(34).fork()).join();
|
|
16654
|
+
}
|
|
16655
|
+
return writer;
|
|
16656
|
+
},
|
|
16657
|
+
decode(input, length) {
|
|
16658
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16659
|
+
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16660
|
+
const message = createBaseTripContinuationEntry();
|
|
16661
|
+
while (reader.pos < end) {
|
|
16662
|
+
const tag = reader.uint32();
|
|
16663
|
+
switch (tag >>> 3) {
|
|
16664
|
+
case 1: {
|
|
16665
|
+
if (tag !== 8) {
|
|
16666
|
+
break;
|
|
16667
|
+
}
|
|
16668
|
+
message.originStopIndex = reader.uint32();
|
|
16636
16669
|
continue;
|
|
16637
16670
|
}
|
|
16638
16671
|
case 2: {
|
|
16639
|
-
if (tag
|
|
16672
|
+
if (tag !== 16) {
|
|
16673
|
+
break;
|
|
16674
|
+
}
|
|
16675
|
+
message.originRouteId = reader.uint32();
|
|
16676
|
+
continue;
|
|
16677
|
+
}
|
|
16678
|
+
case 3: {
|
|
16679
|
+
if (tag !== 24) {
|
|
16680
|
+
break;
|
|
16681
|
+
}
|
|
16682
|
+
message.originTripIndex = reader.uint32();
|
|
16683
|
+
continue;
|
|
16684
|
+
}
|
|
16685
|
+
case 4: {
|
|
16686
|
+
if (tag !== 34) {
|
|
16687
|
+
break;
|
|
16688
|
+
}
|
|
16689
|
+
message.continuations.push(TripBoarding.decode(reader, reader.uint32()));
|
|
16690
|
+
continue;
|
|
16691
|
+
}
|
|
16692
|
+
}
|
|
16693
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
16694
|
+
break;
|
|
16695
|
+
}
|
|
16696
|
+
reader.skip(tag & 7);
|
|
16697
|
+
}
|
|
16698
|
+
return message;
|
|
16699
|
+
},
|
|
16700
|
+
fromJSON(object) {
|
|
16701
|
+
return {
|
|
16702
|
+
originStopIndex: isSet(object.originStopIndex) ? globalThis.Number(object.originStopIndex) : 0,
|
|
16703
|
+
originRouteId: isSet(object.originRouteId) ? globalThis.Number(object.originRouteId) : 0,
|
|
16704
|
+
originTripIndex: isSet(object.originTripIndex) ? globalThis.Number(object.originTripIndex) : 0,
|
|
16705
|
+
continuations: globalThis.Array.isArray(object?.continuations)
|
|
16706
|
+
? object.continuations.map((e) => TripBoarding.fromJSON(e))
|
|
16707
|
+
: [],
|
|
16708
|
+
};
|
|
16709
|
+
},
|
|
16710
|
+
toJSON(message) {
|
|
16711
|
+
const obj = {};
|
|
16712
|
+
if (message.originStopIndex !== 0) {
|
|
16713
|
+
obj.originStopIndex = Math.round(message.originStopIndex);
|
|
16714
|
+
}
|
|
16715
|
+
if (message.originRouteId !== 0) {
|
|
16716
|
+
obj.originRouteId = Math.round(message.originRouteId);
|
|
16717
|
+
}
|
|
16718
|
+
if (message.originTripIndex !== 0) {
|
|
16719
|
+
obj.originTripIndex = Math.round(message.originTripIndex);
|
|
16720
|
+
}
|
|
16721
|
+
if (message.continuations?.length) {
|
|
16722
|
+
obj.continuations = message.continuations.map((e) => TripBoarding.toJSON(e));
|
|
16723
|
+
}
|
|
16724
|
+
return obj;
|
|
16725
|
+
},
|
|
16726
|
+
create(base) {
|
|
16727
|
+
return TripContinuationEntry.fromPartial(base ?? {});
|
|
16728
|
+
},
|
|
16729
|
+
fromPartial(object) {
|
|
16730
|
+
const message = createBaseTripContinuationEntry();
|
|
16731
|
+
message.originStopIndex = object.originStopIndex ?? 0;
|
|
16732
|
+
message.originRouteId = object.originRouteId ?? 0;
|
|
16733
|
+
message.originTripIndex = object.originTripIndex ?? 0;
|
|
16734
|
+
message.continuations = object.continuations?.map((e) => TripBoarding.fromPartial(e)) || [];
|
|
16735
|
+
return message;
|
|
16736
|
+
},
|
|
16737
|
+
};
|
|
16738
|
+
function createBaseStopAdjacency() {
|
|
16739
|
+
return { routes: [], transfers: [] };
|
|
16740
|
+
}
|
|
16741
|
+
const StopAdjacency = {
|
|
16742
|
+
encode(message, writer = new BinaryWriter()) {
|
|
16743
|
+
writer.uint32(10).fork();
|
|
16744
|
+
for (const v of message.routes) {
|
|
16745
|
+
writer.uint32(v);
|
|
16746
|
+
}
|
|
16747
|
+
writer.join();
|
|
16748
|
+
for (const v of message.transfers) {
|
|
16749
|
+
Transfer.encode(v, writer.uint32(18).fork()).join();
|
|
16750
|
+
}
|
|
16751
|
+
return writer;
|
|
16752
|
+
},
|
|
16753
|
+
decode(input, length) {
|
|
16754
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
16755
|
+
const end = length === undefined ? reader.len : reader.pos + length;
|
|
16756
|
+
const message = createBaseStopAdjacency();
|
|
16757
|
+
while (reader.pos < end) {
|
|
16758
|
+
const tag = reader.uint32();
|
|
16759
|
+
switch (tag >>> 3) {
|
|
16760
|
+
case 1: {
|
|
16761
|
+
if (tag === 8) {
|
|
16640
16762
|
message.routes.push(reader.uint32());
|
|
16641
16763
|
continue;
|
|
16642
16764
|
}
|
|
16643
|
-
if (tag ===
|
|
16765
|
+
if (tag === 10) {
|
|
16644
16766
|
const end2 = reader.uint32() + reader.pos;
|
|
16645
16767
|
while (reader.pos < end2) {
|
|
16646
16768
|
message.routes.push(reader.uint32());
|
|
@@ -16649,6 +16771,13 @@ const StopAdjacency = {
|
|
|
16649
16771
|
}
|
|
16650
16772
|
break;
|
|
16651
16773
|
}
|
|
16774
|
+
case 2: {
|
|
16775
|
+
if (tag !== 18) {
|
|
16776
|
+
break;
|
|
16777
|
+
}
|
|
16778
|
+
message.transfers.push(Transfer.decode(reader, reader.uint32()));
|
|
16779
|
+
continue;
|
|
16780
|
+
}
|
|
16652
16781
|
}
|
|
16653
16782
|
if ((tag & 7) === 4 || tag === 0) {
|
|
16654
16783
|
break;
|
|
@@ -16659,31 +16788,29 @@ const StopAdjacency = {
|
|
|
16659
16788
|
},
|
|
16660
16789
|
fromJSON(object) {
|
|
16661
16790
|
return {
|
|
16662
|
-
|
|
16791
|
+
routes: globalThis.Array.isArray(object?.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
|
|
16792
|
+
transfers: globalThis.Array.isArray(object?.transfers)
|
|
16663
16793
|
? object.transfers.map((e) => Transfer.fromJSON(e))
|
|
16664
16794
|
: [],
|
|
16665
|
-
routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
|
|
16666
16795
|
};
|
|
16667
16796
|
},
|
|
16668
16797
|
toJSON(message) {
|
|
16669
|
-
var _a, _b;
|
|
16670
16798
|
const obj = {};
|
|
16671
|
-
if (
|
|
16672
|
-
obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
|
|
16673
|
-
}
|
|
16674
|
-
if ((_b = message.routes) === null || _b === void 0 ? void 0 : _b.length) {
|
|
16799
|
+
if (message.routes?.length) {
|
|
16675
16800
|
obj.routes = message.routes.map((e) => Math.round(e));
|
|
16676
16801
|
}
|
|
16802
|
+
if (message.transfers?.length) {
|
|
16803
|
+
obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
|
|
16804
|
+
}
|
|
16677
16805
|
return obj;
|
|
16678
16806
|
},
|
|
16679
16807
|
create(base) {
|
|
16680
|
-
return StopAdjacency.fromPartial(base
|
|
16808
|
+
return StopAdjacency.fromPartial(base ?? {});
|
|
16681
16809
|
},
|
|
16682
16810
|
fromPartial(object) {
|
|
16683
|
-
var _a, _b;
|
|
16684
16811
|
const message = createBaseStopAdjacency();
|
|
16685
|
-
message.
|
|
16686
|
-
message.
|
|
16812
|
+
message.routes = object.routes?.map((e) => e) || [];
|
|
16813
|
+
message.transfers = object.transfers?.map((e) => Transfer.fromPartial(e)) || [];
|
|
16687
16814
|
return message;
|
|
16688
16815
|
},
|
|
16689
16816
|
};
|
|
@@ -16752,11 +16879,10 @@ const ServiceRoute = {
|
|
|
16752
16879
|
return {
|
|
16753
16880
|
type: isSet(object.type) ? routeTypeFromJSON(object.type) : 0,
|
|
16754
16881
|
name: isSet(object.name) ? globalThis.String(object.name) : "",
|
|
16755
|
-
routes: globalThis.Array.isArray(object
|
|
16882
|
+
routes: globalThis.Array.isArray(object?.routes) ? object.routes.map((e) => globalThis.Number(e)) : [],
|
|
16756
16883
|
};
|
|
16757
16884
|
},
|
|
16758
16885
|
toJSON(message) {
|
|
16759
|
-
var _a;
|
|
16760
16886
|
const obj = {};
|
|
16761
16887
|
if (message.type !== 0) {
|
|
16762
16888
|
obj.type = routeTypeToJSON(message.type);
|
|
@@ -16764,25 +16890,24 @@ const ServiceRoute = {
|
|
|
16764
16890
|
if (message.name !== "") {
|
|
16765
16891
|
obj.name = message.name;
|
|
16766
16892
|
}
|
|
16767
|
-
if (
|
|
16893
|
+
if (message.routes?.length) {
|
|
16768
16894
|
obj.routes = message.routes.map((e) => Math.round(e));
|
|
16769
16895
|
}
|
|
16770
16896
|
return obj;
|
|
16771
16897
|
},
|
|
16772
16898
|
create(base) {
|
|
16773
|
-
return ServiceRoute.fromPartial(base
|
|
16899
|
+
return ServiceRoute.fromPartial(base ?? {});
|
|
16774
16900
|
},
|
|
16775
16901
|
fromPartial(object) {
|
|
16776
|
-
var _a, _b, _c;
|
|
16777
16902
|
const message = createBaseServiceRoute();
|
|
16778
|
-
message.type =
|
|
16779
|
-
message.name =
|
|
16780
|
-
message.routes =
|
|
16903
|
+
message.type = object.type ?? 0;
|
|
16904
|
+
message.name = object.name ?? "";
|
|
16905
|
+
message.routes = object.routes?.map((e) => e) || [];
|
|
16781
16906
|
return message;
|
|
16782
16907
|
},
|
|
16783
16908
|
};
|
|
16784
16909
|
function createBaseTimetable() {
|
|
16785
|
-
return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [] };
|
|
16910
|
+
return { version: "", stopsAdjacency: [], routesAdjacency: [], serviceRoutes: [], tripContinuations: [] };
|
|
16786
16911
|
}
|
|
16787
16912
|
const Timetable$1 = {
|
|
16788
16913
|
encode(message, writer = new BinaryWriter()) {
|
|
@@ -16798,6 +16923,9 @@ const Timetable$1 = {
|
|
|
16798
16923
|
for (const v of message.serviceRoutes) {
|
|
16799
16924
|
ServiceRoute.encode(v, writer.uint32(34).fork()).join();
|
|
16800
16925
|
}
|
|
16926
|
+
for (const v of message.tripContinuations) {
|
|
16927
|
+
TripContinuationEntry.encode(v, writer.uint32(42).fork()).join();
|
|
16928
|
+
}
|
|
16801
16929
|
return writer;
|
|
16802
16930
|
},
|
|
16803
16931
|
decode(input, length) {
|
|
@@ -16835,6 +16963,13 @@ const Timetable$1 = {
|
|
|
16835
16963
|
message.serviceRoutes.push(ServiceRoute.decode(reader, reader.uint32()));
|
|
16836
16964
|
continue;
|
|
16837
16965
|
}
|
|
16966
|
+
case 5: {
|
|
16967
|
+
if (tag !== 42) {
|
|
16968
|
+
break;
|
|
16969
|
+
}
|
|
16970
|
+
message.tripContinuations.push(TripContinuationEntry.decode(reader, reader.uint32()));
|
|
16971
|
+
continue;
|
|
16972
|
+
}
|
|
16838
16973
|
}
|
|
16839
16974
|
if ((tag & 7) === 4 || tag === 0) {
|
|
16840
16975
|
break;
|
|
@@ -16846,44 +16981,49 @@ const Timetable$1 = {
|
|
|
16846
16981
|
fromJSON(object) {
|
|
16847
16982
|
return {
|
|
16848
16983
|
version: isSet(object.version) ? globalThis.String(object.version) : "",
|
|
16849
|
-
stopsAdjacency: globalThis.Array.isArray(object
|
|
16984
|
+
stopsAdjacency: globalThis.Array.isArray(object?.stopsAdjacency)
|
|
16850
16985
|
? object.stopsAdjacency.map((e) => StopAdjacency.fromJSON(e))
|
|
16851
16986
|
: [],
|
|
16852
|
-
routesAdjacency: globalThis.Array.isArray(object
|
|
16987
|
+
routesAdjacency: globalThis.Array.isArray(object?.routesAdjacency)
|
|
16853
16988
|
? object.routesAdjacency.map((e) => Route$2.fromJSON(e))
|
|
16854
16989
|
: [],
|
|
16855
|
-
serviceRoutes: globalThis.Array.isArray(object
|
|
16990
|
+
serviceRoutes: globalThis.Array.isArray(object?.serviceRoutes)
|
|
16856
16991
|
? object.serviceRoutes.map((e) => ServiceRoute.fromJSON(e))
|
|
16857
16992
|
: [],
|
|
16993
|
+
tripContinuations: globalThis.Array.isArray(object?.tripContinuations)
|
|
16994
|
+
? object.tripContinuations.map((e) => TripContinuationEntry.fromJSON(e))
|
|
16995
|
+
: [],
|
|
16858
16996
|
};
|
|
16859
16997
|
},
|
|
16860
16998
|
toJSON(message) {
|
|
16861
|
-
var _a, _b, _c;
|
|
16862
16999
|
const obj = {};
|
|
16863
17000
|
if (message.version !== "") {
|
|
16864
17001
|
obj.version = message.version;
|
|
16865
17002
|
}
|
|
16866
|
-
if (
|
|
17003
|
+
if (message.stopsAdjacency?.length) {
|
|
16867
17004
|
obj.stopsAdjacency = message.stopsAdjacency.map((e) => StopAdjacency.toJSON(e));
|
|
16868
17005
|
}
|
|
16869
|
-
if (
|
|
17006
|
+
if (message.routesAdjacency?.length) {
|
|
16870
17007
|
obj.routesAdjacency = message.routesAdjacency.map((e) => Route$2.toJSON(e));
|
|
16871
17008
|
}
|
|
16872
|
-
if (
|
|
17009
|
+
if (message.serviceRoutes?.length) {
|
|
16873
17010
|
obj.serviceRoutes = message.serviceRoutes.map((e) => ServiceRoute.toJSON(e));
|
|
16874
17011
|
}
|
|
17012
|
+
if (message.tripContinuations?.length) {
|
|
17013
|
+
obj.tripContinuations = message.tripContinuations.map((e) => TripContinuationEntry.toJSON(e));
|
|
17014
|
+
}
|
|
16875
17015
|
return obj;
|
|
16876
17016
|
},
|
|
16877
17017
|
create(base) {
|
|
16878
|
-
return Timetable$1.fromPartial(base
|
|
17018
|
+
return Timetable$1.fromPartial(base ?? {});
|
|
16879
17019
|
},
|
|
16880
17020
|
fromPartial(object) {
|
|
16881
|
-
var _a, _b, _c, _d;
|
|
16882
17021
|
const message = createBaseTimetable();
|
|
16883
|
-
message.version =
|
|
16884
|
-
message.stopsAdjacency =
|
|
16885
|
-
message.routesAdjacency =
|
|
16886
|
-
message.serviceRoutes =
|
|
17022
|
+
message.version = object.version ?? "";
|
|
17023
|
+
message.stopsAdjacency = object.stopsAdjacency?.map((e) => StopAdjacency.fromPartial(e)) || [];
|
|
17024
|
+
message.routesAdjacency = object.routesAdjacency?.map((e) => Route$2.fromPartial(e)) || [];
|
|
17025
|
+
message.serviceRoutes = object.serviceRoutes?.map((e) => ServiceRoute.fromPartial(e)) || [];
|
|
17026
|
+
message.tripContinuations = object.tripContinuations?.map((e) => TripContinuationEntry.fromPartial(e)) || [];
|
|
16887
17027
|
return message;
|
|
16888
17028
|
},
|
|
16889
17029
|
};
|
|
@@ -16920,6 +17060,11 @@ function isSet(value) {
|
|
|
16920
17060
|
* A class representing a time as minutes since midnight.
|
|
16921
17061
|
*/
|
|
16922
17062
|
class Time {
|
|
17063
|
+
/*
|
|
17064
|
+
* Number of minutes since midnight.
|
|
17065
|
+
Note that this value can go beyond 3600 to model services overlapping with the next day.
|
|
17066
|
+
*/
|
|
17067
|
+
minutesSinceMidnight;
|
|
16923
17068
|
/**
|
|
16924
17069
|
* Gets the infinity time as a Time instance.
|
|
16925
17070
|
* This represents a time that is conceptually beyond any real possible time.
|
|
@@ -17172,7 +17317,53 @@ const toPickupDropOffType = (numericalType) => {
|
|
|
17172
17317
|
* A route identifies all trips of a given service route sharing the same list of stops.
|
|
17173
17318
|
*/
|
|
17174
17319
|
let Route$1 = class Route {
|
|
17175
|
-
|
|
17320
|
+
id;
|
|
17321
|
+
/**
|
|
17322
|
+
* Arrivals and departures encoded as minutes from midnight.
|
|
17323
|
+
* Format: [arrival1, departure1, arrival2, departure2, etc.]
|
|
17324
|
+
*/
|
|
17325
|
+
stopTimes;
|
|
17326
|
+
/**
|
|
17327
|
+
* PickUp and DropOff types represented as a 2-bit encoded Uint8Array.
|
|
17328
|
+
* Values (2 bits each):
|
|
17329
|
+
* 0: REGULAR
|
|
17330
|
+
* 1: NOT_AVAILABLE
|
|
17331
|
+
* 2: MUST_PHONE_AGENCY
|
|
17332
|
+
* 3: MUST_COORDINATE_WITH_DRIVER
|
|
17333
|
+
*
|
|
17334
|
+
* Encoding format: Each byte contains 2 pickup/drop-off pairs (4 bits each)
|
|
17335
|
+
* Bit layout per byte: [pickup_1 (2 bits)][drop_off_1 (2 bits)][pickup_0 (2 bits)][drop_off_0 (2 bits)]
|
|
17336
|
+
* Example: For stops 0 and 1 in a trip, one byte encodes all 4 values
|
|
17337
|
+
*/
|
|
17338
|
+
pickUpDropOffTypes;
|
|
17339
|
+
/**
|
|
17340
|
+
* A binary array of stopIds in the route.
|
|
17341
|
+
* [stop1, stop2, stop3,...]
|
|
17342
|
+
*/
|
|
17343
|
+
stops;
|
|
17344
|
+
/**
|
|
17345
|
+
* A reverse mapping of each stop with their index in the route:
|
|
17346
|
+
* {
|
|
17347
|
+
* 4: 0,
|
|
17348
|
+
* 5: 1,
|
|
17349
|
+
* ...
|
|
17350
|
+
* }
|
|
17351
|
+
*/
|
|
17352
|
+
stopIndices;
|
|
17353
|
+
/**
|
|
17354
|
+
* The identifier of the route as a service shown to users.
|
|
17355
|
+
*/
|
|
17356
|
+
serviceRouteId;
|
|
17357
|
+
/**
|
|
17358
|
+
* The total number of stops in the route.
|
|
17359
|
+
*/
|
|
17360
|
+
nbStops;
|
|
17361
|
+
/**
|
|
17362
|
+
* The total number of trips in the route.
|
|
17363
|
+
*/
|
|
17364
|
+
nbTrips;
|
|
17365
|
+
constructor(id, stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
|
|
17366
|
+
this.id = id;
|
|
17176
17367
|
this.stopTimes = stopTimes;
|
|
17177
17368
|
this.pickUpDropOffTypes = pickUpDropOffTypes;
|
|
17178
17369
|
this.stops = stops;
|
|
@@ -17182,8 +17373,86 @@ let Route$1 = class Route {
|
|
|
17182
17373
|
this.stopIndices = new Map();
|
|
17183
17374
|
for (let i = 0; i < stops.length; i++) {
|
|
17184
17375
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17185
|
-
|
|
17376
|
+
const stopId = stops[i];
|
|
17377
|
+
const existingIndices = this.stopIndices.get(stopId);
|
|
17378
|
+
if (existingIndices) {
|
|
17379
|
+
existingIndices.push(i);
|
|
17380
|
+
}
|
|
17381
|
+
else {
|
|
17382
|
+
this.stopIndices.set(stopId, [i]);
|
|
17383
|
+
}
|
|
17384
|
+
}
|
|
17385
|
+
}
|
|
17386
|
+
/**
|
|
17387
|
+
* Creates a new route from multiple trips with their stops.
|
|
17388
|
+
*
|
|
17389
|
+
* @param params The route parameters including ID, service route ID, and trips.
|
|
17390
|
+
* @returns The new route.
|
|
17391
|
+
*/
|
|
17392
|
+
static of(params) {
|
|
17393
|
+
const { id, serviceRouteId, trips } = params;
|
|
17394
|
+
if (trips.length === 0) {
|
|
17395
|
+
throw new Error('At least one trip must be provided');
|
|
17396
|
+
}
|
|
17397
|
+
// All trips must have the same stops in the same order
|
|
17398
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17399
|
+
const firstTrip = trips[0];
|
|
17400
|
+
const stopIds = new Uint32Array(firstTrip.stops.map((stop) => stop.id));
|
|
17401
|
+
const numStops = stopIds.length;
|
|
17402
|
+
// Validate all trips have the same stops
|
|
17403
|
+
for (let tripIndex = 1; tripIndex < trips.length; tripIndex++) {
|
|
17404
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17405
|
+
const trip = trips[tripIndex];
|
|
17406
|
+
if (trip.stops.length !== numStops) {
|
|
17407
|
+
throw new Error(`Trip ${tripIndex} has ${trip.stops.length} stops, expected ${numStops}`);
|
|
17408
|
+
}
|
|
17409
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17410
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17411
|
+
if (trip.stops[stopIndex].id !== stopIds[stopIndex]) {
|
|
17412
|
+
throw new Error(`Trip ${tripIndex} has different stop at index ${stopIndex}`);
|
|
17413
|
+
}
|
|
17414
|
+
}
|
|
17415
|
+
}
|
|
17416
|
+
// Create stopTimes array with arrivals and departures for all trips
|
|
17417
|
+
const stopTimes = new Uint16Array(trips.length * numStops * 2);
|
|
17418
|
+
for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
|
|
17419
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17420
|
+
const trip = trips[tripIndex];
|
|
17421
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17422
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17423
|
+
const stop = trip.stops[stopIndex];
|
|
17424
|
+
const baseIndex = (tripIndex * numStops + stopIndex) * 2;
|
|
17425
|
+
stopTimes[baseIndex] = stop.arrivalTime.toMinutes();
|
|
17426
|
+
stopTimes[baseIndex + 1] = stop.departureTime.toMinutes();
|
|
17427
|
+
}
|
|
17428
|
+
}
|
|
17429
|
+
// Create pickUpDropOffTypes array (2-bit encoded) for all trips
|
|
17430
|
+
const totalStopEntries = trips.length * numStops;
|
|
17431
|
+
const pickUpDropOffTypes = new Uint8Array(Math.ceil(totalStopEntries / 2));
|
|
17432
|
+
for (let tripIndex = 0; tripIndex < trips.length; tripIndex++) {
|
|
17433
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17434
|
+
const trip = trips[tripIndex];
|
|
17435
|
+
for (let stopIndex = 0; stopIndex < numStops; stopIndex++) {
|
|
17436
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17437
|
+
const stop = trip.stops[stopIndex];
|
|
17438
|
+
const globalIndex = tripIndex * numStops + stopIndex;
|
|
17439
|
+
const pickUp = stop.pickUpType ?? REGULAR;
|
|
17440
|
+
const dropOff = stop.dropOffType ?? REGULAR;
|
|
17441
|
+
const byteIndex = Math.floor(globalIndex / 2);
|
|
17442
|
+
const isSecondPair = globalIndex % 2 === 1;
|
|
17443
|
+
if (isSecondPair) {
|
|
17444
|
+
// Second pair: pickup in upper 2 bits, dropOff in bits 4-5
|
|
17445
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17446
|
+
pickUpDropOffTypes[byteIndex] |= (pickUp << 6) | (dropOff << 4);
|
|
17447
|
+
}
|
|
17448
|
+
else {
|
|
17449
|
+
// First pair: pickup in bits 2-3, dropOff in lower 2 bits
|
|
17450
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17451
|
+
pickUpDropOffTypes[byteIndex] |= (pickUp << 2) | dropOff;
|
|
17452
|
+
}
|
|
17453
|
+
}
|
|
17186
17454
|
}
|
|
17455
|
+
return new Route(id, stopTimes, pickUpDropOffTypes, stopIds, serviceRouteId);
|
|
17187
17456
|
}
|
|
17188
17457
|
/**
|
|
17189
17458
|
* Serializes the Route into binary arrays.
|
|
@@ -17198,24 +17467,6 @@ let Route$1 = class Route {
|
|
|
17198
17467
|
serviceRouteId: this.serviceRouteId,
|
|
17199
17468
|
};
|
|
17200
17469
|
}
|
|
17201
|
-
/**
|
|
17202
|
-
* Checks if stop A is before stop B in the route.
|
|
17203
|
-
*
|
|
17204
|
-
* @param stopA - The StopId of the first stop.
|
|
17205
|
-
* @param stopB - The StopId of the second stop.
|
|
17206
|
-
* @returns True if stop A is before stop B, false otherwise.
|
|
17207
|
-
*/
|
|
17208
|
-
isBefore(stopA, stopB) {
|
|
17209
|
-
const stopAIndex = this.stopIndices.get(stopA);
|
|
17210
|
-
if (stopAIndex === undefined) {
|
|
17211
|
-
throw new Error(`Stop index ${stopAIndex} not found in route ${this.serviceRouteId}`);
|
|
17212
|
-
}
|
|
17213
|
-
const stopBIndex = this.stopIndices.get(stopB);
|
|
17214
|
-
if (stopBIndex === undefined) {
|
|
17215
|
-
throw new Error(`Stop index ${stopBIndex} not found in route ${this.serviceRouteId}`);
|
|
17216
|
-
}
|
|
17217
|
-
return stopAIndex < stopBIndex;
|
|
17218
|
-
}
|
|
17219
17470
|
/**
|
|
17220
17471
|
* Retrieves the number of stops in the route.
|
|
17221
17472
|
*
|
|
@@ -17224,6 +17475,14 @@ let Route$1 = class Route {
|
|
|
17224
17475
|
getNbStops() {
|
|
17225
17476
|
return this.nbStops;
|
|
17226
17477
|
}
|
|
17478
|
+
/**
|
|
17479
|
+
* Retrieves the number of trips in the route.
|
|
17480
|
+
*
|
|
17481
|
+
* @returns The total number of trips in the route.
|
|
17482
|
+
*/
|
|
17483
|
+
getNbTrips() {
|
|
17484
|
+
return this.nbTrips;
|
|
17485
|
+
}
|
|
17227
17486
|
/**
|
|
17228
17487
|
* Finds the ServiceRouteId of the route. It corresponds the identifier
|
|
17229
17488
|
* of the service shown to the end user as a route.
|
|
@@ -17236,47 +17495,47 @@ let Route$1 = class Route {
|
|
|
17236
17495
|
/**
|
|
17237
17496
|
* Retrieves the arrival time at a specific stop for a given trip.
|
|
17238
17497
|
*
|
|
17239
|
-
* @param
|
|
17498
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17240
17499
|
* @param tripIndex - The index of the trip.
|
|
17241
17500
|
* @returns The arrival time at the specified stop and trip as a Time object.
|
|
17242
17501
|
*/
|
|
17243
|
-
arrivalAt(
|
|
17244
|
-
const arrivalIndex = (tripIndex * this.stops.length +
|
|
17502
|
+
arrivalAt(stopIndex, tripIndex) {
|
|
17503
|
+
const arrivalIndex = (tripIndex * this.stops.length + stopIndex) * 2;
|
|
17245
17504
|
const arrival = this.stopTimes[arrivalIndex];
|
|
17246
17505
|
if (arrival === undefined) {
|
|
17247
|
-
throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17506
|
+
throw new Error(`Arrival time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17248
17507
|
}
|
|
17249
17508
|
return Time.fromMinutes(arrival);
|
|
17250
17509
|
}
|
|
17251
17510
|
/**
|
|
17252
17511
|
* Retrieves the departure time at a specific stop for a given trip.
|
|
17253
17512
|
*
|
|
17254
|
-
* @param
|
|
17513
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17255
17514
|
* @param tripIndex - The index of the trip.
|
|
17256
17515
|
* @returns The departure time at the specified stop and trip as a Time object.
|
|
17257
17516
|
*/
|
|
17258
|
-
departureFrom(
|
|
17259
|
-
const departureIndex = (tripIndex * this.stops.length +
|
|
17517
|
+
departureFrom(stopIndex, tripIndex) {
|
|
17518
|
+
const departureIndex = (tripIndex * this.stops.length + stopIndex) * 2 + 1;
|
|
17260
17519
|
const departure = this.stopTimes[departureIndex];
|
|
17261
17520
|
if (departure === undefined) {
|
|
17262
|
-
throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17521
|
+
throw new Error(`Departure time not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17263
17522
|
}
|
|
17264
17523
|
return Time.fromMinutes(departure);
|
|
17265
17524
|
}
|
|
17266
17525
|
/**
|
|
17267
17526
|
* Retrieves the pick-up type for a specific stop and trip.
|
|
17268
17527
|
*
|
|
17269
|
-
* @param
|
|
17528
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17270
17529
|
* @param tripIndex - The index of the trip.
|
|
17271
17530
|
* @returns The pick-up type at the specified stop and trip.
|
|
17272
17531
|
*/
|
|
17273
|
-
pickUpTypeFrom(
|
|
17274
|
-
const globalIndex = tripIndex * this.stops.length +
|
|
17532
|
+
pickUpTypeFrom(stopIndex, tripIndex) {
|
|
17533
|
+
const globalIndex = tripIndex * this.stops.length + stopIndex;
|
|
17275
17534
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17276
17535
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17277
17536
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
17278
17537
|
if (byte === undefined) {
|
|
17279
|
-
throw new Error(`Pick up type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17538
|
+
throw new Error(`Pick up type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17280
17539
|
}
|
|
17281
17540
|
const pickUpValue = isSecondPair
|
|
17282
17541
|
? (byte >> 6) & 0x03 // Upper 2 bits for second pair
|
|
@@ -17286,17 +17545,17 @@ let Route$1 = class Route {
|
|
|
17286
17545
|
/**
|
|
17287
17546
|
* Retrieves the drop-off type for a specific stop and trip.
|
|
17288
17547
|
*
|
|
17289
|
-
* @param
|
|
17548
|
+
* @param stopIndex - The index of the stop in the route.
|
|
17290
17549
|
* @param tripIndex - The index of the trip.
|
|
17291
17550
|
* @returns The drop-off type at the specified stop and trip.
|
|
17292
17551
|
*/
|
|
17293
|
-
dropOffTypeAt(
|
|
17294
|
-
const globalIndex = tripIndex * this.stops.length +
|
|
17552
|
+
dropOffTypeAt(stopIndex, tripIndex) {
|
|
17553
|
+
const globalIndex = tripIndex * this.stops.length + stopIndex;
|
|
17295
17554
|
const byteIndex = Math.floor(globalIndex / 2);
|
|
17296
17555
|
const isSecondPair = globalIndex % 2 === 1;
|
|
17297
17556
|
const byte = this.pickUpDropOffTypes[byteIndex];
|
|
17298
17557
|
if (byte === undefined) {
|
|
17299
|
-
throw new Error(`Drop off type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17558
|
+
throw new Error(`Drop off type not found for stop ${this.stopId(stopIndex)} (${stopIndex}) at trip index ${tripIndex} in route ${this.serviceRouteId}`);
|
|
17300
17559
|
}
|
|
17301
17560
|
const dropOffValue = isSecondPair
|
|
17302
17561
|
? (byte >> 4) & 0x03 // Bits 4-5 for second pair
|
|
@@ -17308,14 +17567,14 @@ let Route$1 = class Route {
|
|
|
17308
17567
|
* optionally constrained by a latest trip index and a time before which the trip
|
|
17309
17568
|
* should not depart.
|
|
17310
17569
|
* *
|
|
17311
|
-
* @param
|
|
17570
|
+
* @param stopIndex - The route index of the stop where the trip should be found.
|
|
17312
17571
|
* @param [after=Time.origin()] - The earliest time after which the trip should depart.
|
|
17313
17572
|
* If not provided, searches all available trips.
|
|
17314
17573
|
* @param [beforeTrip] - (Optional) The index of the trip before which the search should be constrained.
|
|
17315
17574
|
* If not provided, searches all available trips.
|
|
17316
17575
|
* @returns The index of the earliest trip meeting the criteria, or undefined if no such trip is found.
|
|
17317
17576
|
*/
|
|
17318
|
-
findEarliestTrip(
|
|
17577
|
+
findEarliestTrip(stopIndex, after = Time.origin(), beforeTrip) {
|
|
17319
17578
|
if (this.nbTrips <= 0)
|
|
17320
17579
|
return undefined;
|
|
17321
17580
|
let hi = this.nbTrips - 1;
|
|
@@ -17327,7 +17586,7 @@ let Route$1 = class Route {
|
|
|
17327
17586
|
let lb = -1;
|
|
17328
17587
|
while (lo <= hi) {
|
|
17329
17588
|
const mid = (lo + hi) >>> 1;
|
|
17330
|
-
const depMid = this.departureFrom(
|
|
17589
|
+
const depMid = this.departureFrom(stopIndex, mid);
|
|
17331
17590
|
if (depMid.isBefore(after)) {
|
|
17332
17591
|
lo = mid + 1;
|
|
17333
17592
|
}
|
|
@@ -17338,8 +17597,8 @@ let Route$1 = class Route {
|
|
|
17338
17597
|
}
|
|
17339
17598
|
if (lb === -1)
|
|
17340
17599
|
return undefined;
|
|
17341
|
-
for (let t = lb; t < (beforeTrip
|
|
17342
|
-
const pickup = this.pickUpTypeFrom(
|
|
17600
|
+
for (let t = lb; t < (beforeTrip ?? this.nbTrips); t++) {
|
|
17601
|
+
const pickup = this.pickUpTypeFrom(stopIndex, t);
|
|
17343
17602
|
if (pickup !== 'NOT_AVAILABLE') {
|
|
17344
17603
|
return t;
|
|
17345
17604
|
}
|
|
@@ -17347,17 +17606,74 @@ let Route$1 = class Route {
|
|
|
17347
17606
|
return undefined;
|
|
17348
17607
|
}
|
|
17349
17608
|
/**
|
|
17350
|
-
* Retrieves the
|
|
17609
|
+
* Retrieves the indices of a stop within the route.
|
|
17351
17610
|
* @param stopId The StopId of the stop to locate in the route.
|
|
17352
|
-
* @returns
|
|
17611
|
+
* @returns An array of indices where the stop appears in the route, or an empty array if the stop is not found.
|
|
17353
17612
|
*/
|
|
17354
|
-
|
|
17613
|
+
stopRouteIndices(stopId) {
|
|
17355
17614
|
const stopIndex = this.stopIndices.get(stopId);
|
|
17356
17615
|
if (stopIndex === undefined) {
|
|
17357
|
-
|
|
17616
|
+
return [];
|
|
17358
17617
|
}
|
|
17359
17618
|
return stopIndex;
|
|
17360
17619
|
}
|
|
17620
|
+
/**
|
|
17621
|
+
* Retrieves the id of a stop at a given index in a route.
|
|
17622
|
+
* @param stopRouteIndex The route index of the stop.
|
|
17623
|
+
* @returns The id of the stop at the given index in the route.
|
|
17624
|
+
*/
|
|
17625
|
+
stopId(stopRouteIndex) {
|
|
17626
|
+
const stopId = this.stops[stopRouteIndex];
|
|
17627
|
+
if (stopId === undefined) {
|
|
17628
|
+
throw new Error(`StopId for stop at index ${stopRouteIndex} not found in route ${this.serviceRouteId}`);
|
|
17629
|
+
}
|
|
17630
|
+
return stopId;
|
|
17631
|
+
}
|
|
17632
|
+
};
|
|
17633
|
+
|
|
17634
|
+
// Each value uses 20 bits, allowing values from 0 to 1,048,575 (2^20 - 1)
|
|
17635
|
+
const VALUE_MASK = (1n << 20n) - 1n; // 0xFFFFF
|
|
17636
|
+
const MAX_VALUE = 1_048_575; // 2^20 - 1
|
|
17637
|
+
// Bit positions for each value in the 60-bit bigint
|
|
17638
|
+
const TRIP_INDEX_SHIFT = 0n;
|
|
17639
|
+
const ROUTE_ID_SHIFT = 20n;
|
|
17640
|
+
const STOP_INDEX_SHIFT = 40n;
|
|
17641
|
+
/**
|
|
17642
|
+
* Validates that a value fits within 20 bits (0 to 1,048,575)
|
|
17643
|
+
* @param value - The value to validate
|
|
17644
|
+
* @param name - The name of the value for error reporting
|
|
17645
|
+
* @throws Error if the value is out of range
|
|
17646
|
+
*/
|
|
17647
|
+
const validateValue = (value, name) => {
|
|
17648
|
+
if (value < 0 || value > MAX_VALUE) {
|
|
17649
|
+
throw new Error(`${name} must be between 0 and ${MAX_VALUE}, got ${value}`);
|
|
17650
|
+
}
|
|
17651
|
+
};
|
|
17652
|
+
/**
|
|
17653
|
+
* Encodes a stop index, route ID, and trip index into a single trip boarding ID.
|
|
17654
|
+
* @param stopIndex - The index of the stop within the route (0 to 1,048,575)
|
|
17655
|
+
* @param routeId - The route identifier (0 to 1,048,575)
|
|
17656
|
+
* @param tripIndex - The index of the trip within the route (0 to 1,048,575)
|
|
17657
|
+
* @returns The encoded trip ID as a bigint
|
|
17658
|
+
*/
|
|
17659
|
+
const encode = (stopIndex, routeId, tripIndex) => {
|
|
17660
|
+
validateValue(stopIndex, 'stopIndex');
|
|
17661
|
+
validateValue(routeId, 'routeId');
|
|
17662
|
+
validateValue(tripIndex, 'tripIndex');
|
|
17663
|
+
return ((BigInt(stopIndex) << STOP_INDEX_SHIFT) |
|
|
17664
|
+
(BigInt(routeId) << ROUTE_ID_SHIFT) |
|
|
17665
|
+
(BigInt(tripIndex) << TRIP_INDEX_SHIFT));
|
|
17666
|
+
};
|
|
17667
|
+
/**
|
|
17668
|
+
* Decodes a trip boarding ID back into its constituent stop index, route ID, and trip index.
|
|
17669
|
+
* @param tripBoardingId - The encoded trip ID
|
|
17670
|
+
* @returns A tuple containing [stopIndex, routeId, tripIndex]
|
|
17671
|
+
*/
|
|
17672
|
+
const decode = (tripBoardingId) => {
|
|
17673
|
+
const stopIndex = Number((tripBoardingId >> STOP_INDEX_SHIFT) & VALUE_MASK);
|
|
17674
|
+
const routeId = Number((tripBoardingId >> ROUTE_ID_SHIFT) & VALUE_MASK);
|
|
17675
|
+
const tripIndex = Number((tripBoardingId >> TRIP_INDEX_SHIFT) & VALUE_MASK);
|
|
17676
|
+
return [stopIndex, routeId, tripIndex];
|
|
17361
17677
|
};
|
|
17362
17678
|
|
|
17363
17679
|
const isLittleEndian = (() => {
|
|
@@ -17428,9 +17744,15 @@ const bytesToUint16Array = (bytes) => {
|
|
|
17428
17744
|
const serializeStopsAdjacency = (stopsAdjacency) => {
|
|
17429
17745
|
return stopsAdjacency.map((value) => {
|
|
17430
17746
|
return {
|
|
17431
|
-
transfers: value.transfers
|
|
17432
|
-
|
|
17433
|
-
|
|
17747
|
+
transfers: value.transfers
|
|
17748
|
+
? value.transfers.map((transfer) => ({
|
|
17749
|
+
destination: transfer.destination,
|
|
17750
|
+
type: serializeTransferType(transfer.type),
|
|
17751
|
+
...(transfer.minTransferTime !== undefined && {
|
|
17752
|
+
minTransferTime: transfer.minTransferTime.toSeconds(),
|
|
17753
|
+
}),
|
|
17754
|
+
}))
|
|
17755
|
+
: [],
|
|
17434
17756
|
routes: value.routes,
|
|
17435
17757
|
};
|
|
17436
17758
|
});
|
|
@@ -17466,15 +17788,22 @@ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
|
|
|
17466
17788
|
for (let j = 0; j < value.transfers.length; j++) {
|
|
17467
17789
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17468
17790
|
const transfer = value.transfers[j];
|
|
17469
|
-
const newTransfer =
|
|
17470
|
-
|
|
17471
|
-
|
|
17791
|
+
const newTransfer = {
|
|
17792
|
+
destination: transfer.destination,
|
|
17793
|
+
type: parseTransferType(transfer.type),
|
|
17794
|
+
...(transfer.minTransferTime !== undefined && {
|
|
17795
|
+
minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
|
|
17796
|
+
}),
|
|
17797
|
+
};
|
|
17472
17798
|
transfers.push(newTransfer);
|
|
17473
17799
|
}
|
|
17474
|
-
|
|
17475
|
-
transfers: transfers,
|
|
17800
|
+
const stopAdjacency = {
|
|
17476
17801
|
routes: value.routes,
|
|
17477
|
-
}
|
|
17802
|
+
};
|
|
17803
|
+
if (transfers.length > 0) {
|
|
17804
|
+
stopAdjacency.transfers = transfers;
|
|
17805
|
+
}
|
|
17806
|
+
result.push(stopAdjacency);
|
|
17478
17807
|
}
|
|
17479
17808
|
return result;
|
|
17480
17809
|
};
|
|
@@ -17484,7 +17813,7 @@ const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
|
|
|
17484
17813
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17485
17814
|
const value = protoRoutesAdjacency[i];
|
|
17486
17815
|
const stops = bytesToUint32Array(value.stops);
|
|
17487
|
-
routesAdjacency.push(new Route$1(bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
|
|
17816
|
+
routesAdjacency.push(new Route$1(i, bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
|
|
17488
17817
|
}
|
|
17489
17818
|
return routesAdjacency;
|
|
17490
17819
|
};
|
|
@@ -17578,6 +17907,38 @@ const serializeRouteType = (type) => {
|
|
|
17578
17907
|
return RouteType.MONORAIL;
|
|
17579
17908
|
}
|
|
17580
17909
|
};
|
|
17910
|
+
const serializeTripContinuations = (tripContinuations) => {
|
|
17911
|
+
const result = [];
|
|
17912
|
+
for (const [tripBoardingId, boardings] of tripContinuations.entries()) {
|
|
17913
|
+
const [originStopIndex, originRouteId, originTripIndex] = decode(tripBoardingId);
|
|
17914
|
+
result.push({
|
|
17915
|
+
originStopIndex,
|
|
17916
|
+
originRouteId,
|
|
17917
|
+
originTripIndex,
|
|
17918
|
+
continuations: boardings.map((tripBoarding) => ({
|
|
17919
|
+
hopOnStopIndex: tripBoarding.hopOnStopIndex,
|
|
17920
|
+
routeId: tripBoarding.routeId,
|
|
17921
|
+
tripIndex: tripBoarding.tripIndex,
|
|
17922
|
+
})),
|
|
17923
|
+
});
|
|
17924
|
+
}
|
|
17925
|
+
return result;
|
|
17926
|
+
};
|
|
17927
|
+
const deserializeTripContinuations = (protoTripContinuations) => {
|
|
17928
|
+
const result = new Map();
|
|
17929
|
+
for (let i = 0; i < protoTripContinuations.length; i++) {
|
|
17930
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17931
|
+
const entry = protoTripContinuations[i];
|
|
17932
|
+
const tripBoardingId = encode(entry.originStopIndex, entry.originRouteId, entry.originTripIndex);
|
|
17933
|
+
const tripBoardings = entry.continuations.map((protoTripBoarding) => ({
|
|
17934
|
+
hopOnStopIndex: protoTripBoarding.hopOnStopIndex,
|
|
17935
|
+
routeId: protoTripBoarding.routeId,
|
|
17936
|
+
tripIndex: protoTripBoarding.tripIndex,
|
|
17937
|
+
}));
|
|
17938
|
+
result.set(tripBoardingId, tripBoardings);
|
|
17939
|
+
}
|
|
17940
|
+
return result;
|
|
17941
|
+
};
|
|
17581
17942
|
|
|
17582
17943
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
17583
17944
|
const ALL_TRANSPORT_MODES = new Set([
|
|
@@ -17592,20 +17953,27 @@ const ALL_TRANSPORT_MODES = new Set([
|
|
|
17592
17953
|
'TROLLEYBUS',
|
|
17593
17954
|
'MONORAIL',
|
|
17594
17955
|
]);
|
|
17595
|
-
const
|
|
17956
|
+
const EMPTY_TRIP_CONTINUATIONS = [];
|
|
17957
|
+
const CURRENT_VERSION = '0.0.9';
|
|
17596
17958
|
/**
|
|
17597
17959
|
* The internal transit timetable format.
|
|
17598
17960
|
*/
|
|
17599
17961
|
class Timetable {
|
|
17600
|
-
|
|
17962
|
+
stopsAdjacency;
|
|
17963
|
+
routesAdjacency;
|
|
17964
|
+
serviceRoutes;
|
|
17965
|
+
tripContinuations;
|
|
17966
|
+
activeStops;
|
|
17967
|
+
constructor(stopsAdjacency, routesAdjacency, routes, tripContinuations) {
|
|
17601
17968
|
this.stopsAdjacency = stopsAdjacency;
|
|
17602
17969
|
this.routesAdjacency = routesAdjacency;
|
|
17603
17970
|
this.serviceRoutes = routes;
|
|
17971
|
+
this.tripContinuations = tripContinuations;
|
|
17604
17972
|
this.activeStops = new Set();
|
|
17605
17973
|
for (let i = 0; i < stopsAdjacency.length; i++) {
|
|
17606
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17607
17974
|
const stop = stopsAdjacency[i];
|
|
17608
|
-
if (stop.routes.length > 0 ||
|
|
17975
|
+
if (stop.routes.length > 0 ||
|
|
17976
|
+
(stop.transfers && stop.transfers.length > 0)) {
|
|
17609
17977
|
this.activeStops.add(i);
|
|
17610
17978
|
}
|
|
17611
17979
|
}
|
|
@@ -17621,6 +17989,7 @@ class Timetable {
|
|
|
17621
17989
|
stopsAdjacency: serializeStopsAdjacency(this.stopsAdjacency),
|
|
17622
17990
|
routesAdjacency: serializeRoutesAdjacency(this.routesAdjacency),
|
|
17623
17991
|
serviceRoutes: serializeServiceRoutesMap(this.serviceRoutes),
|
|
17992
|
+
tripContinuations: serializeTripContinuations(this.tripContinuations || new Map()),
|
|
17624
17993
|
};
|
|
17625
17994
|
const writer = new BinaryWriter();
|
|
17626
17995
|
Timetable$1.encode(protoTimetable, writer);
|
|
@@ -17638,7 +18007,7 @@ class Timetable {
|
|
|
17638
18007
|
if (protoTimetable.version !== CURRENT_VERSION) {
|
|
17639
18008
|
throw new Error(`Unsupported timetable version ${protoTimetable.version}`);
|
|
17640
18009
|
}
|
|
17641
|
-
return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes));
|
|
18010
|
+
return new Timetable(deserializeStopsAdjacency(protoTimetable.stopsAdjacency), deserializeRoutesAdjacency(protoTimetable.routesAdjacency), deserializeServiceRoutesMap(protoTimetable.serviceRoutes), deserializeTripContinuations(protoTimetable.tripContinuations));
|
|
17642
18011
|
}
|
|
17643
18012
|
/**
|
|
17644
18013
|
* Checks if the given stop is active on the timetable.
|
|
@@ -17668,8 +18037,26 @@ class Timetable {
|
|
|
17668
18037
|
* @returns An array of transfer options available at the stop.
|
|
17669
18038
|
*/
|
|
17670
18039
|
getTransfers(stopId) {
|
|
17671
|
-
|
|
17672
|
-
|
|
18040
|
+
const stopAdjacency = this.stopsAdjacency[stopId];
|
|
18041
|
+
if (!stopAdjacency) {
|
|
18042
|
+
throw new Error(`Stop ID ${stopId} not found`);
|
|
18043
|
+
}
|
|
18044
|
+
return stopAdjacency.transfers || [];
|
|
18045
|
+
}
|
|
18046
|
+
/**
|
|
18047
|
+
* Retrieves all trip continuation options available at the specified stop for a given trip.
|
|
18048
|
+
*
|
|
18049
|
+
* @param stopIndex - The index in the route of the stop to get trip continuations for.
|
|
18050
|
+
* @param routeId - The ID of the route to get continuations for.
|
|
18051
|
+
* @param tripIndex - The index of the trip to get continuations for.
|
|
18052
|
+
* @returns An array of trip continuation options available at the stop for the specified trip.
|
|
18053
|
+
*/
|
|
18054
|
+
getContinuousTrips(stopIndex, routeId, tripIndex) {
|
|
18055
|
+
const tripContinuations = this.tripContinuations?.get(encode(stopIndex, routeId, tripIndex));
|
|
18056
|
+
if (!tripContinuations) {
|
|
18057
|
+
return EMPTY_TRIP_CONTINUATIONS;
|
|
18058
|
+
}
|
|
18059
|
+
return tripContinuations;
|
|
17673
18060
|
}
|
|
17674
18061
|
/**
|
|
17675
18062
|
* Retrieves the service route associated with the given route.
|
|
@@ -17709,12 +18096,12 @@ class Timetable {
|
|
|
17709
18096
|
}
|
|
17710
18097
|
/**
|
|
17711
18098
|
* Finds routes that are reachable from a set of stop IDs.
|
|
17712
|
-
* Also identifies the first stop available to hop on each route among
|
|
18099
|
+
* Also identifies the first stop index available to hop on each route among
|
|
17713
18100
|
* the input stops.
|
|
17714
18101
|
*
|
|
17715
18102
|
* @param fromStops - The set of stop IDs to find reachable routes from.
|
|
17716
18103
|
* @param transportModes - The set of transport modes to consider for reachable routes.
|
|
17717
|
-
* @returns A map of reachable routes to the first stop available to hop on each route.
|
|
18104
|
+
* @returns A map of reachable routes to the first stop index available to hop on each route.
|
|
17718
18105
|
*/
|
|
17719
18106
|
findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
|
|
17720
18107
|
const reachableRoutes = new Map();
|
|
@@ -17727,15 +18114,17 @@ class Timetable {
|
|
|
17727
18114
|
});
|
|
17728
18115
|
for (let j = 0; j < validRoutes.length; j++) {
|
|
17729
18116
|
const route = validRoutes[j];
|
|
17730
|
-
const
|
|
17731
|
-
|
|
17732
|
-
|
|
18117
|
+
const originStopIndices = route.stopRouteIndices(originStop);
|
|
18118
|
+
const originStopIndex = originStopIndices[0];
|
|
18119
|
+
const existingHopOnStopIndex = reachableRoutes.get(route);
|
|
18120
|
+
if (existingHopOnStopIndex !== undefined) {
|
|
18121
|
+
if (originStopIndex < existingHopOnStopIndex) {
|
|
17733
18122
|
// if the current stop is before the existing hop on stop, replace it
|
|
17734
|
-
reachableRoutes.set(route,
|
|
18123
|
+
reachableRoutes.set(route, originStopIndex);
|
|
17735
18124
|
}
|
|
17736
18125
|
}
|
|
17737
18126
|
else {
|
|
17738
|
-
reachableRoutes.set(route,
|
|
18127
|
+
reachableRoutes.set(route, originStopIndex);
|
|
17739
18128
|
}
|
|
17740
18129
|
}
|
|
17741
18130
|
}
|
|
@@ -19711,35 +20100,22 @@ const parseCsv = (stream, numericColumns = []) => {
|
|
|
19711
20100
|
* @param profile A configuration object defining the specificities of the GTFS feed.
|
|
19712
20101
|
* @returns A map of all the valid routes.
|
|
19713
20102
|
*/
|
|
19714
|
-
const parseRoutes =
|
|
19715
|
-
var _a, e_1, _b, _c;
|
|
20103
|
+
const parseRoutes = async (routesStream, profile = standardProfile) => {
|
|
19716
20104
|
const routes = new Map();
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
const routeType = profile.routeTypeParser(line.route_type);
|
|
19724
|
-
if (routeType === undefined) {
|
|
19725
|
-
log.info(`Unsupported route type ${line.route_type} for route ${line.route_id}.`);
|
|
19726
|
-
continue;
|
|
19727
|
-
}
|
|
19728
|
-
routes.set(line.route_id, {
|
|
19729
|
-
name: line.route_short_name,
|
|
19730
|
-
type: routeType,
|
|
19731
|
-
});
|
|
19732
|
-
}
|
|
19733
|
-
}
|
|
19734
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
19735
|
-
finally {
|
|
19736
|
-
try {
|
|
19737
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
20105
|
+
for await (const rawLine of parseCsv(routesStream, ['route_type'])) {
|
|
20106
|
+
const line = rawLine;
|
|
20107
|
+
const routeType = profile.routeTypeParser(line.route_type);
|
|
20108
|
+
if (routeType === undefined) {
|
|
20109
|
+
log.info(`Unsupported route type ${line.route_type} for route ${line.route_id}.`);
|
|
20110
|
+
continue;
|
|
19738
20111
|
}
|
|
19739
|
-
|
|
20112
|
+
routes.set(line.route_id, {
|
|
20113
|
+
name: line.route_short_name,
|
|
20114
|
+
type: routeType,
|
|
20115
|
+
});
|
|
19740
20116
|
}
|
|
19741
20117
|
return routes;
|
|
19742
|
-
}
|
|
20118
|
+
};
|
|
19743
20119
|
/**
|
|
19744
20120
|
* Creates an array of ServiceRoute objects by combining GTFS route data with service route mappings.
|
|
19745
20121
|
*
|
|
@@ -19802,46 +20178,33 @@ const weekdays = {
|
|
|
19802
20178
|
* @param date The active date.
|
|
19803
20179
|
* @param calendarStream A readable stream for the GTFS calendar.txt file.
|
|
19804
20180
|
*/
|
|
19805
|
-
const parseCalendar = (calendarStream, serviceIds, date) =>
|
|
19806
|
-
var _a, e_1, _b, _c;
|
|
20181
|
+
const parseCalendar = async (calendarStream, serviceIds, date) => {
|
|
19807
20182
|
const activeDate = toGtfsDate(date);
|
|
19808
20183
|
const weekday = date.weekday;
|
|
19809
20184
|
const weekdayIndex = weekdays[weekday];
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
const line = rawLine;
|
|
19826
|
-
if (activeDate < line.start_date || activeDate > line.end_date) {
|
|
19827
|
-
// If the service is not valid on this date
|
|
19828
|
-
continue;
|
|
19829
|
-
}
|
|
19830
|
-
if (line[weekdayIndex] !== 1) {
|
|
19831
|
-
// If the service is not valid on this week day
|
|
19832
|
-
continue;
|
|
19833
|
-
}
|
|
19834
|
-
serviceIds.add(line['service_id']);
|
|
20185
|
+
for await (const rawLine of parseCsv(calendarStream, [
|
|
20186
|
+
'monday',
|
|
20187
|
+
'tuesday',
|
|
20188
|
+
'wednesday',
|
|
20189
|
+
'thursday',
|
|
20190
|
+
'friday',
|
|
20191
|
+
'saturday',
|
|
20192
|
+
'sunday',
|
|
20193
|
+
'start_date',
|
|
20194
|
+
'end_date',
|
|
20195
|
+
])) {
|
|
20196
|
+
const line = rawLine;
|
|
20197
|
+
if (activeDate < line.start_date || activeDate > line.end_date) {
|
|
20198
|
+
// If the service is not valid on this date
|
|
20199
|
+
continue;
|
|
19835
20200
|
}
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
try {
|
|
19840
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
20201
|
+
if (line[weekdayIndex] !== 1) {
|
|
20202
|
+
// If the service is not valid on this week day
|
|
20203
|
+
continue;
|
|
19841
20204
|
}
|
|
19842
|
-
|
|
20205
|
+
serviceIds.add(line['service_id']);
|
|
19843
20206
|
}
|
|
19844
|
-
}
|
|
20207
|
+
};
|
|
19845
20208
|
/**
|
|
19846
20209
|
* Parses a gtfs calendar_dates.txt file and finds the service ids valid at a given date.
|
|
19847
20210
|
*
|
|
@@ -19849,39 +20212,24 @@ const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(void 0, vo
|
|
|
19849
20212
|
* @param date The active date, in the format "YYYYMMDD".
|
|
19850
20213
|
* @param calendarDatesStream A readable stream for the GTFS calendar_dates.txt file.
|
|
19851
20214
|
*/
|
|
19852
|
-
const parseCalendarDates = (calendarDatesStream, serviceIds, date) =>
|
|
19853
|
-
var _a, e_2, _b, _c;
|
|
20215
|
+
const parseCalendarDates = async (calendarDatesStream, serviceIds, date) => {
|
|
19854
20216
|
const activeDate = toGtfsDate(date);
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
|
|
19860
|
-
|
|
19861
|
-
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
if (line.date !== activeDate) {
|
|
19865
|
-
// No rule on the active date
|
|
19866
|
-
}
|
|
19867
|
-
else if (line.exception_type === 2 && serviceIds.has(line.service_id)) {
|
|
19868
|
-
// Service has been removed for the specified date.
|
|
19869
|
-
serviceIds.delete(line.service_id);
|
|
19870
|
-
}
|
|
19871
|
-
else if (line.exception_type === 1) {
|
|
19872
|
-
// Service is present on the active date
|
|
19873
|
-
serviceIds.add(line.service_id);
|
|
19874
|
-
}
|
|
20217
|
+
for await (const rawLine of parseCsv(calendarDatesStream, [
|
|
20218
|
+
'date',
|
|
20219
|
+
'exception_type',
|
|
20220
|
+
])) {
|
|
20221
|
+
const line = rawLine;
|
|
20222
|
+
if (line.date !== activeDate) ;
|
|
20223
|
+
else if (line.exception_type === 2 && serviceIds.has(line.service_id)) {
|
|
20224
|
+
// Service has been removed for the specified date.
|
|
20225
|
+
serviceIds.delete(line.service_id);
|
|
19875
20226
|
}
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
try {
|
|
19880
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
20227
|
+
else if (line.exception_type === 1) {
|
|
20228
|
+
// Service is present on the active date
|
|
20229
|
+
serviceIds.add(line.service_id);
|
|
19881
20230
|
}
|
|
19882
|
-
finally { if (e_2) throw e_2.error; }
|
|
19883
20231
|
}
|
|
19884
|
-
}
|
|
20232
|
+
};
|
|
19885
20233
|
|
|
19886
20234
|
/**
|
|
19887
20235
|
* Parses the stops.txt file from a GTFS feed.
|
|
@@ -19889,33 +20237,30 @@ const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(
|
|
|
19889
20237
|
* @param stopsStream The readable stream containing the stops data.
|
|
19890
20238
|
* @return A mapping of stop IDs to corresponding stop details.
|
|
19891
20239
|
*/
|
|
19892
|
-
const parseStops = (stopsStream) =>
|
|
19893
|
-
var _a, e_1, _b, _c;
|
|
20240
|
+
const parseStops = async (stopsStream) => {
|
|
19894
20241
|
const parsedStops = new Map();
|
|
19895
20242
|
let i = 0;
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
|
|
19900
|
-
|
|
19901
|
-
|
|
19902
|
-
|
|
19903
|
-
|
|
19904
|
-
|
|
19905
|
-
|
|
19906
|
-
|
|
19907
|
-
|
|
19908
|
-
|
|
19909
|
-
|
|
19910
|
-
|
|
19911
|
-
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
}
|
|
19918
|
-
finally { if (e_1) throw e_1.error; }
|
|
20243
|
+
for await (const rawLine of parseCsv(stopsStream, [
|
|
20244
|
+
'stop_lat',
|
|
20245
|
+
'stop_lon',
|
|
20246
|
+
'location_type',
|
|
20247
|
+
])) {
|
|
20248
|
+
const line = rawLine;
|
|
20249
|
+
const stop = {
|
|
20250
|
+
id: i,
|
|
20251
|
+
sourceStopId: line.stop_id,
|
|
20252
|
+
name: line.stop_name,
|
|
20253
|
+
lat: line.stop_lat,
|
|
20254
|
+
lon: line.stop_lon,
|
|
20255
|
+
locationType: line.location_type
|
|
20256
|
+
? parseGtfsLocationType(line.location_type)
|
|
20257
|
+
: 'SIMPLE_STOP_OR_PLATFORM',
|
|
20258
|
+
...(line.platform_code && { platform: line.platform_code }),
|
|
20259
|
+
children: [],
|
|
20260
|
+
...(line.parent_station && { parentSourceId: line.parent_station }),
|
|
20261
|
+
};
|
|
20262
|
+
parsedStops.set(line.stop_id, stop);
|
|
20263
|
+
i = i + 1;
|
|
19919
20264
|
}
|
|
19920
20265
|
for (const [sourceStopId, stop] of parsedStops) {
|
|
19921
20266
|
if (stop.parentSourceId) {
|
|
@@ -19929,7 +20274,7 @@ const parseStops = (stopsStream) => __awaiter(void 0, void 0, void 0, function*
|
|
|
19929
20274
|
}
|
|
19930
20275
|
}
|
|
19931
20276
|
return parsedStops;
|
|
19932
|
-
}
|
|
20277
|
+
};
|
|
19933
20278
|
const parseGtfsLocationType = (gtfsLocationType) => {
|
|
19934
20279
|
switch (gtfsLocationType) {
|
|
19935
20280
|
case 0:
|
|
@@ -19952,58 +20297,158 @@ const parseGtfsLocationType = (gtfsLocationType) => {
|
|
|
19952
20297
|
* @param stopsStream The readable stream containing the stops data.
|
|
19953
20298
|
* @return A mapping of stop IDs to corresponding stop details.
|
|
19954
20299
|
*/
|
|
19955
|
-
const parseTransfers = (transfersStream, stopsMap) =>
|
|
19956
|
-
var _a, e_1, _b, _c;
|
|
20300
|
+
const parseTransfers = async (transfersStream, stopsMap) => {
|
|
19957
20301
|
const transfers = new Map();
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
|
|
19962
|
-
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
if (
|
|
19980
|
-
|
|
20302
|
+
const tripContinuations = [];
|
|
20303
|
+
for await (const rawLine of parseCsv(transfersStream, [
|
|
20304
|
+
'transfer_type',
|
|
20305
|
+
'min_transfer_time',
|
|
20306
|
+
])) {
|
|
20307
|
+
const transferEntry = rawLine;
|
|
20308
|
+
if (transferEntry.transfer_type === 3 ||
|
|
20309
|
+
transferEntry.transfer_type === 5) {
|
|
20310
|
+
continue;
|
|
20311
|
+
}
|
|
20312
|
+
if (!transferEntry.from_stop_id || !transferEntry.to_stop_id) {
|
|
20313
|
+
console.warn(`Missing transfer origin or destination stop.`);
|
|
20314
|
+
continue;
|
|
20315
|
+
}
|
|
20316
|
+
const fromStop = stopsMap.get(transferEntry.from_stop_id);
|
|
20317
|
+
const toStop = stopsMap.get(transferEntry.to_stop_id);
|
|
20318
|
+
if (!fromStop || !toStop) {
|
|
20319
|
+
console.warn(`Transfer references non-existent stop(s): from_stop_id=${transferEntry.from_stop_id}, to_stop_id=${transferEntry.to_stop_id}`);
|
|
20320
|
+
continue;
|
|
20321
|
+
}
|
|
20322
|
+
if (transferEntry.transfer_type === 4) {
|
|
20323
|
+
if (transferEntry.from_trip_id === undefined ||
|
|
20324
|
+
transferEntry.from_trip_id === '' ||
|
|
20325
|
+
transferEntry.to_trip_id === undefined ||
|
|
20326
|
+
transferEntry.to_trip_id === '') {
|
|
20327
|
+
console.warn(`Unsupported in-seat transfer, missing from_trip_id and/or to_trip_id.`);
|
|
19981
20328
|
continue;
|
|
19982
20329
|
}
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
|
|
19986
|
-
|
|
19987
|
-
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
|
|
20330
|
+
const tripBoardingEntry = {
|
|
20331
|
+
fromStop: fromStop.id,
|
|
20332
|
+
fromTrip: transferEntry.from_trip_id,
|
|
20333
|
+
toStop: toStop.id,
|
|
20334
|
+
toTrip: transferEntry.to_trip_id,
|
|
20335
|
+
};
|
|
20336
|
+
tripContinuations.push(tripBoardingEntry);
|
|
20337
|
+
continue;
|
|
20338
|
+
}
|
|
20339
|
+
if (transferEntry.from_trip_id && transferEntry.to_trip_id) {
|
|
20340
|
+
console.warn(`Unsupported transfer of type ${transferEntry.transfer_type} between trips ${transferEntry.from_trip_id} and ${transferEntry.to_trip_id}.`);
|
|
20341
|
+
continue;
|
|
20342
|
+
}
|
|
20343
|
+
if (transferEntry.from_route_id && transferEntry.to_route_id) {
|
|
20344
|
+
console.warn(`Unsupported transfer of type ${transferEntry.transfer_type} between routes ${transferEntry.from_route_id} and ${transferEntry.to_route_id}.`);
|
|
20345
|
+
continue;
|
|
20346
|
+
}
|
|
20347
|
+
if (transferEntry.transfer_type === 2 &&
|
|
20348
|
+
transferEntry.min_transfer_time === undefined) {
|
|
20349
|
+
console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
|
|
20350
|
+
}
|
|
20351
|
+
const transfer = {
|
|
20352
|
+
destination: toStop.id,
|
|
20353
|
+
type: parseGtfsTransferType(transferEntry.transfer_type),
|
|
20354
|
+
...(transferEntry.min_transfer_time !== undefined && {
|
|
19991
20355
|
minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
|
|
19992
|
-
})
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
20356
|
+
}),
|
|
20357
|
+
};
|
|
20358
|
+
const fromStopTransfers = transfers.get(fromStop.id) || [];
|
|
20359
|
+
fromStopTransfers.push(transfer);
|
|
20360
|
+
transfers.set(fromStop.id, fromStopTransfers);
|
|
20361
|
+
}
|
|
20362
|
+
return {
|
|
20363
|
+
transfers,
|
|
20364
|
+
tripContinuations,
|
|
20365
|
+
};
|
|
20366
|
+
};
|
|
20367
|
+
/**
|
|
20368
|
+
* Disambiguates stops involved in a transfer.
|
|
20369
|
+
*
|
|
20370
|
+
* The GTFS specification only refers to a stopId in the trip-to-trip transfers and not the
|
|
20371
|
+
* specific stop index in the route. For routes that have multiple stops with the same stopId,
|
|
20372
|
+
* we need to determine which are the from / to stop indices in respective routes.
|
|
20373
|
+
* We do so by picking the stop indices leading to the most coherent transfer.
|
|
20374
|
+
* (we pick the closest from stop index happening after the to stop index).
|
|
20375
|
+
*/
|
|
20376
|
+
const disambiguateTransferStopsIndices = (fromStop, fromRoute, fromTripIndex, toStop, toRoute, toTripIndex) => {
|
|
20377
|
+
const fromStopIndices = fromRoute.stopRouteIndices(fromStop);
|
|
20378
|
+
const toStopIndices = toRoute.stopRouteIndices(toStop);
|
|
20379
|
+
let bestFromStopIndex;
|
|
20380
|
+
let bestToStopIndex;
|
|
20381
|
+
let bestTimeDifference = Infinity;
|
|
20382
|
+
for (const originStopIndex of fromStopIndices) {
|
|
20383
|
+
const fromArrivalTime = fromRoute.arrivalAt(originStopIndex, fromTripIndex);
|
|
20384
|
+
for (const toStopIndex of toStopIndices) {
|
|
20385
|
+
const toDepartureTime = toRoute.departureFrom(toStopIndex, toTripIndex);
|
|
20386
|
+
if (toDepartureTime.isAfter(fromArrivalTime)) {
|
|
20387
|
+
const timeDifference = toDepartureTime.toMinutes() - fromArrivalTime.toMinutes();
|
|
20388
|
+
if (timeDifference < bestTimeDifference) {
|
|
20389
|
+
bestTimeDifference = timeDifference;
|
|
20390
|
+
bestFromStopIndex = originStopIndex;
|
|
20391
|
+
bestToStopIndex = toStopIndex;
|
|
20392
|
+
}
|
|
20393
|
+
}
|
|
19996
20394
|
}
|
|
19997
20395
|
}
|
|
19998
|
-
|
|
19999
|
-
|
|
20000
|
-
|
|
20001
|
-
|
|
20396
|
+
if (bestFromStopIndex !== undefined && bestToStopIndex !== undefined) {
|
|
20397
|
+
return {
|
|
20398
|
+
fromStopIndex: bestFromStopIndex,
|
|
20399
|
+
toStopIndex: bestToStopIndex,
|
|
20400
|
+
};
|
|
20401
|
+
}
|
|
20402
|
+
return undefined;
|
|
20403
|
+
};
|
|
20404
|
+
/**
|
|
20405
|
+
* Builds trip continuations map from GTFS trip continuation data.
|
|
20406
|
+
*
|
|
20407
|
+
* This function processes GTFS in-seat transfer data and creates a mapping
|
|
20408
|
+
* from trip boarding IDs to continuation boarding information. It disambiguates
|
|
20409
|
+
* stop indices when routes have multiple stops with the same ID by finding
|
|
20410
|
+
* the most coherent transfer timing.
|
|
20411
|
+
*
|
|
20412
|
+
* @param tripsMapping Mapping from GTFS trip IDs to internal trip representations
|
|
20413
|
+
* @param tripContinuations Array of GTFS trip continuation data from transfers.txt
|
|
20414
|
+
* @param timetable The timetable containing route and timing information
|
|
20415
|
+
* @param activeStopIds Set of stop IDs that are active/enabled in the system
|
|
20416
|
+
* @returns A map from trip boarding IDs to arrays of continuation boarding options
|
|
20417
|
+
*/
|
|
20418
|
+
const buildTripContinuations = (tripsMapping, tripContinuations, timetable, activeStopIds) => {
|
|
20419
|
+
const continuations = new Map();
|
|
20420
|
+
for (const gtfsContinuation of tripContinuations) {
|
|
20421
|
+
if (!activeStopIds.has(gtfsContinuation.fromStop) ||
|
|
20422
|
+
!activeStopIds.has(gtfsContinuation.toStop)) {
|
|
20423
|
+
continue;
|
|
20424
|
+
}
|
|
20425
|
+
const fromTripMapping = tripsMapping.get(gtfsContinuation.fromTrip);
|
|
20426
|
+
const toTripMapping = tripsMapping.get(gtfsContinuation.toTrip);
|
|
20427
|
+
if (!fromTripMapping || !toTripMapping) {
|
|
20428
|
+
continue;
|
|
20429
|
+
}
|
|
20430
|
+
const fromRoute = timetable.getRoute(fromTripMapping.routeId);
|
|
20431
|
+
const toRoute = timetable.getRoute(toTripMapping.routeId);
|
|
20432
|
+
if (!fromRoute || !toRoute) {
|
|
20433
|
+
continue;
|
|
20434
|
+
}
|
|
20435
|
+
const bestStopIndices = disambiguateTransferStopsIndices(gtfsContinuation.fromStop, fromRoute, fromTripMapping.tripRouteIndex, gtfsContinuation.toStop, toRoute, toTripMapping.tripRouteIndex);
|
|
20436
|
+
if (!bestStopIndices) {
|
|
20437
|
+
// No valid continuation found
|
|
20438
|
+
continue;
|
|
20002
20439
|
}
|
|
20003
|
-
|
|
20440
|
+
const tripBoardingId = encode(bestStopIndices.fromStopIndex, fromTripMapping.routeId, fromTripMapping.tripRouteIndex);
|
|
20441
|
+
const continuationBoarding = {
|
|
20442
|
+
hopOnStopIndex: bestStopIndices.toStopIndex,
|
|
20443
|
+
routeId: toTripMapping.routeId,
|
|
20444
|
+
tripIndex: toTripMapping.tripRouteIndex,
|
|
20445
|
+
};
|
|
20446
|
+
const existingContinuations = continuations.get(tripBoardingId) || [];
|
|
20447
|
+
existingContinuations.push(continuationBoarding);
|
|
20448
|
+
continuations.set(tripBoardingId, existingContinuations);
|
|
20004
20449
|
}
|
|
20005
|
-
return
|
|
20006
|
-
}
|
|
20450
|
+
return continuations;
|
|
20451
|
+
};
|
|
20007
20452
|
const parseGtfsTransferType = (gtfsTransferType) => {
|
|
20008
20453
|
switch (gtfsTransferType) {
|
|
20009
20454
|
case 0:
|
|
@@ -20065,11 +20510,13 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20065
20510
|
const stopTimesArray = new Uint16Array(stopsCount * tripsCount * 2);
|
|
20066
20511
|
const allPickUpTypes = [];
|
|
20067
20512
|
const allDropOffTypes = [];
|
|
20513
|
+
const gtfsTripIds = [];
|
|
20068
20514
|
for (let tripIndex = 0; tripIndex < tripsCount; tripIndex++) {
|
|
20069
20515
|
const trip = builder.trips[tripIndex];
|
|
20070
20516
|
if (!trip) {
|
|
20071
20517
|
throw new Error(`Missing trip data at index ${tripIndex}`);
|
|
20072
20518
|
}
|
|
20519
|
+
gtfsTripIds.push(trip.gtfsTripId);
|
|
20073
20520
|
const baseIndex = tripIndex * stopsCount * 2;
|
|
20074
20521
|
for (let stopIndex = 0; stopIndex < stopsCount; stopIndex++) {
|
|
20075
20522
|
const timeIndex = baseIndex + stopIndex * 2;
|
|
@@ -20091,12 +20538,15 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20091
20538
|
}
|
|
20092
20539
|
// Use 2-bit encoding for pickup/drop-off types
|
|
20093
20540
|
const pickUpDropOffTypesArray = encodePickUpDropOffTypes(allPickUpTypes, allDropOffTypes);
|
|
20094
|
-
return
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20541
|
+
return [
|
|
20542
|
+
{
|
|
20543
|
+
serviceRouteId: builder.serviceRouteId,
|
|
20544
|
+
stops: stopsArray,
|
|
20545
|
+
stopTimes: stopTimesArray,
|
|
20546
|
+
pickUpDropOffTypes: pickUpDropOffTypesArray,
|
|
20547
|
+
},
|
|
20548
|
+
gtfsTripIds,
|
|
20549
|
+
];
|
|
20100
20550
|
};
|
|
20101
20551
|
/**
|
|
20102
20552
|
* Parses the trips.txt file from a GTFS feed
|
|
@@ -20106,40 +20556,28 @@ const finalizeRouteFromBuilder = (builder) => {
|
|
|
20106
20556
|
* @param serviceRoutes A mapping of route IDs to route details.
|
|
20107
20557
|
* @returns A mapping of trip IDs to corresponding route IDs.
|
|
20108
20558
|
*/
|
|
20109
|
-
const parseTrips = (tripsStream, serviceIds, validGtfsRoutes) =>
|
|
20110
|
-
var _a, e_1, _b, _c;
|
|
20559
|
+
const parseTrips = async (tripsStream, serviceIds, validGtfsRoutes) => {
|
|
20111
20560
|
const trips = new Map();
|
|
20112
|
-
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
const line = rawLine;
|
|
20118
|
-
if (!serviceIds.has(line.service_id)) {
|
|
20119
|
-
// The trip doesn't correspond to an active service
|
|
20120
|
-
continue;
|
|
20121
|
-
}
|
|
20122
|
-
if (!validGtfsRoutes.has(line.route_id)) {
|
|
20123
|
-
// The trip doesn't correspond to a supported route
|
|
20124
|
-
continue;
|
|
20125
|
-
}
|
|
20126
|
-
trips.set(line.trip_id, line.route_id);
|
|
20561
|
+
for await (const rawLine of parseCsv(tripsStream, ['stop_sequence'])) {
|
|
20562
|
+
const line = rawLine;
|
|
20563
|
+
if (!serviceIds.has(line.service_id)) {
|
|
20564
|
+
// The trip doesn't correspond to an active service
|
|
20565
|
+
continue;
|
|
20127
20566
|
}
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
try {
|
|
20132
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
20567
|
+
if (!validGtfsRoutes.has(line.route_id)) {
|
|
20568
|
+
// The trip doesn't correspond to a supported route
|
|
20569
|
+
continue;
|
|
20133
20570
|
}
|
|
20134
|
-
|
|
20571
|
+
trips.set(line.trip_id, line.route_id);
|
|
20135
20572
|
}
|
|
20136
20573
|
return trips;
|
|
20137
|
-
}
|
|
20574
|
+
};
|
|
20138
20575
|
const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbStops, activeStops) => {
|
|
20139
|
-
// TODO somehow works when it's a map
|
|
20140
20576
|
const stopsAdjacency = new Array(nbStops);
|
|
20141
20577
|
for (let i = 0; i < nbStops; i++) {
|
|
20142
|
-
stopsAdjacency[i] = {
|
|
20578
|
+
stopsAdjacency[i] = {
|
|
20579
|
+
routes: [],
|
|
20580
|
+
};
|
|
20143
20581
|
}
|
|
20144
20582
|
for (let index = 0; index < routes.length; index++) {
|
|
20145
20583
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -20164,7 +20602,11 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
|
|
|
20164
20602
|
const transfer = transfers[i];
|
|
20165
20603
|
if (activeStops.has(stop) || activeStops.has(transfer.destination)) {
|
|
20166
20604
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20167
|
-
stopsAdjacency[stop]
|
|
20605
|
+
const stopAdj = stopsAdjacency[stop];
|
|
20606
|
+
if (!stopAdj.transfers) {
|
|
20607
|
+
stopAdj.transfers = [];
|
|
20608
|
+
}
|
|
20609
|
+
stopAdj.transfers.push(transfer);
|
|
20168
20610
|
activeStops.add(transfer.destination);
|
|
20169
20611
|
activeStops.add(stop);
|
|
20170
20612
|
}
|
|
@@ -20181,9 +20623,7 @@ const buildStopsAdjacencyStructure = (serviceRoutes, routes, transfersMap, nbSto
|
|
|
20181
20623
|
* @param activeStopIds A set of valid stop IDs.
|
|
20182
20624
|
* @returns A mapping of route IDs to route details. The routes returned correspond to the set of trips from GTFS that share the same stop list.
|
|
20183
20625
|
*/
|
|
20184
|
-
const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds) =>
|
|
20185
|
-
var _a, e_2, _b, _c;
|
|
20186
|
-
var _d, _e;
|
|
20626
|
+
const parseStopTimes = async (stopTimesStream, stopsMap, activeTripIds, activeStopIds) => {
|
|
20187
20627
|
/**
|
|
20188
20628
|
* Adds a trip to the appropriate route builder
|
|
20189
20629
|
*/
|
|
@@ -20228,6 +20668,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20228
20668
|
}
|
|
20229
20669
|
routeBuilder.trips.push({
|
|
20230
20670
|
firstDeparture,
|
|
20671
|
+
gtfsTripId: currentTripId,
|
|
20231
20672
|
arrivalTimes: arrivalTimes,
|
|
20232
20673
|
departureTimes: departureTimes,
|
|
20233
20674
|
pickUpTypes: pickUpTypes,
|
|
@@ -20250,63 +20691,62 @@ const parseStopTimes = (stopTimesStream, stopsMap, activeTripIds, activeStopIds)
|
|
|
20250
20691
|
let pickUpTypes = [];
|
|
20251
20692
|
let dropOffTypes = [];
|
|
20252
20693
|
let currentTripId = undefined;
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
20258
|
-
const line = rawLine;
|
|
20259
|
-
if (line.trip_id === currentTripId && line.stop_sequence <= previousSeq) {
|
|
20260
|
-
console.warn(`Stop sequences not increasing for trip ${line.trip_id}: ${line.stop_sequence} > ${previousSeq}.`);
|
|
20261
|
-
continue;
|
|
20262
|
-
}
|
|
20263
|
-
if (!line.arrival_time && !line.departure_time) {
|
|
20264
|
-
console.warn(`Missing arrival or departure time for ${line.trip_id} at stop ${line.stop_id}.`);
|
|
20265
|
-
continue;
|
|
20266
|
-
}
|
|
20267
|
-
if (line.pickup_type === '1' && line.drop_off_type === '1') {
|
|
20268
|
-
continue;
|
|
20269
|
-
}
|
|
20270
|
-
if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
|
|
20271
|
-
addTrip(currentTripId);
|
|
20272
|
-
}
|
|
20273
|
-
currentTripId = line.trip_id;
|
|
20274
|
-
const stopData = stopsMap.get(line.stop_id);
|
|
20275
|
-
if (!stopData) {
|
|
20276
|
-
console.warn(`Unknown stop ID: ${line.stop_id}`);
|
|
20277
|
-
continue;
|
|
20278
|
-
}
|
|
20279
|
-
stops.push(stopData.id);
|
|
20280
|
-
const departure = (_d = line.departure_time) !== null && _d !== void 0 ? _d : line.arrival_time;
|
|
20281
|
-
const arrival = (_e = line.arrival_time) !== null && _e !== void 0 ? _e : line.departure_time;
|
|
20282
|
-
if (!arrival || !departure) {
|
|
20283
|
-
console.warn(`Missing time data for ${line.trip_id} at stop ${line.stop_id}`);
|
|
20284
|
-
continue;
|
|
20285
|
-
}
|
|
20286
|
-
arrivalTimes.push(toTime(arrival).toMinutes());
|
|
20287
|
-
departureTimes.push(toTime(departure).toMinutes());
|
|
20288
|
-
pickUpTypes.push(parsePickupDropOffType(line.pickup_type));
|
|
20289
|
-
dropOffTypes.push(parsePickupDropOffType(line.drop_off_type));
|
|
20290
|
-
previousSeq = line.stop_sequence;
|
|
20694
|
+
for await (const rawLine of parseCsv(stopTimesStream, ['stop_sequence'])) {
|
|
20695
|
+
const line = rawLine;
|
|
20696
|
+
if (line.trip_id === currentTripId && line.stop_sequence <= previousSeq) {
|
|
20697
|
+
console.warn(`Stop sequences not increasing for trip ${line.trip_id}: ${line.stop_sequence} > ${previousSeq}.`);
|
|
20698
|
+
continue;
|
|
20291
20699
|
}
|
|
20292
|
-
|
|
20293
|
-
|
|
20294
|
-
|
|
20295
|
-
|
|
20296
|
-
|
|
20700
|
+
if (!line.arrival_time && !line.departure_time) {
|
|
20701
|
+
console.warn(`Missing arrival or departure time for ${line.trip_id} at stop ${line.stop_id}.`);
|
|
20702
|
+
continue;
|
|
20703
|
+
}
|
|
20704
|
+
if (line.pickup_type === '1' && line.drop_off_type === '1') {
|
|
20705
|
+
// Warning: could potentially lead to issues if there is an in-seat transfer
|
|
20706
|
+
// at this stop - it can be not boardable nor alightable but still useful for an in-seat transfer.
|
|
20707
|
+
// This doesn't seem to happen in practice for now so keeping this condition to save memory.
|
|
20708
|
+
continue;
|
|
20709
|
+
}
|
|
20710
|
+
if (currentTripId && line.trip_id !== currentTripId && stops.length > 0) {
|
|
20711
|
+
addTrip(currentTripId);
|
|
20297
20712
|
}
|
|
20298
|
-
|
|
20713
|
+
currentTripId = line.trip_id;
|
|
20714
|
+
const stopData = stopsMap.get(line.stop_id);
|
|
20715
|
+
if (!stopData) {
|
|
20716
|
+
console.warn(`Unknown stop ID: ${line.stop_id}`);
|
|
20717
|
+
continue;
|
|
20718
|
+
}
|
|
20719
|
+
stops.push(stopData.id);
|
|
20720
|
+
const departure = line.departure_time ?? line.arrival_time;
|
|
20721
|
+
const arrival = line.arrival_time ?? line.departure_time;
|
|
20722
|
+
if (!arrival || !departure) {
|
|
20723
|
+
console.warn(`Missing time data for ${line.trip_id} at stop ${line.stop_id}`);
|
|
20724
|
+
continue;
|
|
20725
|
+
}
|
|
20726
|
+
arrivalTimes.push(toTime(arrival).toMinutes());
|
|
20727
|
+
departureTimes.push(toTime(departure).toMinutes());
|
|
20728
|
+
pickUpTypes.push(parsePickupDropOffType(line.pickup_type));
|
|
20729
|
+
dropOffTypes.push(parsePickupDropOffType(line.drop_off_type));
|
|
20730
|
+
previousSeq = line.stop_sequence;
|
|
20299
20731
|
}
|
|
20300
20732
|
if (currentTripId) {
|
|
20301
20733
|
addTrip(currentTripId);
|
|
20302
20734
|
}
|
|
20303
20735
|
const routesAdjacency = [];
|
|
20736
|
+
const tripsMapping = new Map();
|
|
20304
20737
|
for (const [, routeBuilder] of routeBuilders) {
|
|
20305
|
-
const routeData = finalizeRouteFromBuilder(routeBuilder);
|
|
20306
|
-
|
|
20738
|
+
const [routeData, gtfsTripIds] = finalizeRouteFromBuilder(routeBuilder);
|
|
20739
|
+
const routeId = routesAdjacency.length;
|
|
20740
|
+
routesAdjacency.push(new Route$1(routeId, routeData.stopTimes, routeData.pickUpDropOffTypes, routeData.stops, routeData.serviceRouteId));
|
|
20741
|
+
gtfsTripIds.forEach((tripId, index) => {
|
|
20742
|
+
tripsMapping.set(tripId, {
|
|
20743
|
+
routeId,
|
|
20744
|
+
tripRouteIndex: index,
|
|
20745
|
+
});
|
|
20746
|
+
});
|
|
20307
20747
|
}
|
|
20308
|
-
return { routes: routesAdjacency, serviceRoutesMap };
|
|
20309
|
-
}
|
|
20748
|
+
return { routes: routesAdjacency, serviceRoutesMap, tripsMapping };
|
|
20749
|
+
};
|
|
20310
20750
|
const parsePickupDropOffType = (gtfsType) => {
|
|
20311
20751
|
switch (gtfsType) {
|
|
20312
20752
|
default:
|
|
@@ -20330,6 +20770,8 @@ const STOP_TIMES_FILE = 'stop_times.txt';
|
|
|
20330
20770
|
const STOPS_FILE = 'stops.txt';
|
|
20331
20771
|
const TRANSFERS_FILE = 'transfers.txt';
|
|
20332
20772
|
class GtfsParser {
|
|
20773
|
+
path;
|
|
20774
|
+
profile;
|
|
20333
20775
|
constructor(path, profile = standardProfile) {
|
|
20334
20776
|
// TODO: support input from multiple sources
|
|
20335
20777
|
this.path = path;
|
|
@@ -20341,74 +20783,81 @@ class GtfsParser {
|
|
|
20341
20783
|
* @param date The active date.
|
|
20342
20784
|
* @returns The parsed timetable.
|
|
20343
20785
|
*/
|
|
20344
|
-
parseTimetable(date) {
|
|
20345
|
-
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20354
|
-
|
|
20355
|
-
|
|
20356
|
-
|
|
20357
|
-
|
|
20358
|
-
|
|
20359
|
-
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
|
|
20366
|
-
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
|
|
20370
|
-
|
|
20371
|
-
|
|
20372
|
-
|
|
20373
|
-
|
|
20374
|
-
|
|
20375
|
-
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
|
|
20385
|
-
|
|
20386
|
-
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20390
|
-
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
log.info(
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
20411
|
-
|
|
20786
|
+
async parseTimetable(date) {
|
|
20787
|
+
log.setLevel('INFO');
|
|
20788
|
+
const zip = new StreamZip.async({ file: this.path });
|
|
20789
|
+
const entries = await zip.entries();
|
|
20790
|
+
const datetime = DateTime.fromJSDate(date);
|
|
20791
|
+
const activeServiceIds = new Set();
|
|
20792
|
+
const activeStopIds = new Set();
|
|
20793
|
+
log.info(`Parsing ${STOPS_FILE}`);
|
|
20794
|
+
const stopsStart = performance.now();
|
|
20795
|
+
const stopsStream = await zip.stream(STOPS_FILE);
|
|
20796
|
+
const parsedStops = await parseStops(stopsStream);
|
|
20797
|
+
const stopsEnd = performance.now();
|
|
20798
|
+
log.info(`${parsedStops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
|
|
20799
|
+
if (entries[CALENDAR_FILE]) {
|
|
20800
|
+
log.info(`Parsing ${CALENDAR_FILE}`);
|
|
20801
|
+
const calendarStart = performance.now();
|
|
20802
|
+
const calendarStream = await zip.stream(CALENDAR_FILE);
|
|
20803
|
+
await parseCalendar(calendarStream, activeServiceIds, datetime);
|
|
20804
|
+
const calendarEnd = performance.now();
|
|
20805
|
+
log.info(`${activeServiceIds.size} valid services. (${(calendarEnd - calendarStart).toFixed(2)}ms)`);
|
|
20806
|
+
}
|
|
20807
|
+
if (entries[CALENDAR_DATES_FILE]) {
|
|
20808
|
+
log.info(`Parsing ${CALENDAR_DATES_FILE}`);
|
|
20809
|
+
const calendarDatesStart = performance.now();
|
|
20810
|
+
const calendarDatesStream = await zip.stream(CALENDAR_DATES_FILE);
|
|
20811
|
+
await parseCalendarDates(calendarDatesStream, activeServiceIds, datetime);
|
|
20812
|
+
const calendarDatesEnd = performance.now();
|
|
20813
|
+
log.info(`${activeServiceIds.size} valid services. (${(calendarDatesEnd - calendarDatesStart).toFixed(2)}ms)`);
|
|
20814
|
+
}
|
|
20815
|
+
log.info(`Parsing ${ROUTES_FILE}`);
|
|
20816
|
+
const routesStart = performance.now();
|
|
20817
|
+
const routesStream = await zip.stream(ROUTES_FILE);
|
|
20818
|
+
const validGtfsRoutes = await parseRoutes(routesStream, this.profile);
|
|
20819
|
+
const routesEnd = performance.now();
|
|
20820
|
+
log.info(`${validGtfsRoutes.size} valid GTFS routes. (${(routesEnd - routesStart).toFixed(2)}ms)`);
|
|
20821
|
+
log.info(`Parsing ${TRIPS_FILE}`);
|
|
20822
|
+
const tripsStart = performance.now();
|
|
20823
|
+
const tripsStream = await zip.stream(TRIPS_FILE);
|
|
20824
|
+
const trips = await parseTrips(tripsStream, activeServiceIds, validGtfsRoutes);
|
|
20825
|
+
const tripsEnd = performance.now();
|
|
20826
|
+
log.info(`${trips.size} valid trips. (${(tripsEnd - tripsStart).toFixed(2)}ms)`);
|
|
20827
|
+
let transfers = new Map();
|
|
20828
|
+
let tripContinuationsMap = [];
|
|
20829
|
+
if (entries[TRANSFERS_FILE]) {
|
|
20830
|
+
log.info(`Parsing ${TRANSFERS_FILE}`);
|
|
20831
|
+
const transfersStart = performance.now();
|
|
20832
|
+
const transfersStream = await zip.stream(TRANSFERS_FILE);
|
|
20833
|
+
const { transfers: parsedTransfers, tripContinuations: parsedTripContinuations, } = await parseTransfers(transfersStream, parsedStops);
|
|
20834
|
+
transfers = parsedTransfers;
|
|
20835
|
+
tripContinuationsMap = parsedTripContinuations;
|
|
20836
|
+
const transfersEnd = performance.now();
|
|
20837
|
+
log.info(`${transfers.size} valid transfers and ${tripContinuationsMap.length} trip continuations. (${(transfersEnd - transfersStart).toFixed(2)}ms)`);
|
|
20838
|
+
}
|
|
20839
|
+
log.info(`Parsing ${STOP_TIMES_FILE}`);
|
|
20840
|
+
const stopTimesStart = performance.now();
|
|
20841
|
+
const stopTimesStream = await zip.stream(STOP_TIMES_FILE);
|
|
20842
|
+
const { routes, serviceRoutesMap, tripsMapping } = await parseStopTimes(stopTimesStream, parsedStops, trips, activeStopIds);
|
|
20843
|
+
const serviceRoutes = indexRoutes(validGtfsRoutes, serviceRoutesMap);
|
|
20844
|
+
const stopTimesEnd = performance.now();
|
|
20845
|
+
log.info(`${routes.length} valid unique routes. (${(stopTimesEnd - stopTimesStart).toFixed(2)}ms)`);
|
|
20846
|
+
log.info('Building stops adjacency structure');
|
|
20847
|
+
const stopsAdjacencyStart = performance.now();
|
|
20848
|
+
const stopsAdjacency = buildStopsAdjacencyStructure(serviceRoutes, routes, transfers, parsedStops.size, activeStopIds);
|
|
20849
|
+
const stopsAdjacencyEnd = performance.now();
|
|
20850
|
+
log.info(`${stopsAdjacency.length} valid stops in the structure. (${(stopsAdjacencyEnd - stopsAdjacencyStart).toFixed(2)}ms)`);
|
|
20851
|
+
await zip.close();
|
|
20852
|
+
// temporary timetable for building continuations
|
|
20853
|
+
const timetable = new Timetable(stopsAdjacency, routes, serviceRoutes);
|
|
20854
|
+
log.info('Building in-seat trip continuations');
|
|
20855
|
+
const tripContinuationsStart = performance.now();
|
|
20856
|
+
const tripContinuations = buildTripContinuations(tripsMapping, tripContinuationsMap, timetable, activeStopIds);
|
|
20857
|
+
const tripContinuationsEnd = performance.now();
|
|
20858
|
+
log.info(`${tripContinuations.size} in-seat trip continuations origins created. (${(tripContinuationsEnd - tripContinuationsStart).toFixed(2)}ms)`);
|
|
20859
|
+
log.info('Parsing complete.');
|
|
20860
|
+
return new Timetable(stopsAdjacency, routes, serviceRoutes, tripContinuations);
|
|
20412
20861
|
}
|
|
20413
20862
|
/**
|
|
20414
20863
|
* Parses a GTFS feed to extract all stops.
|
|
@@ -20416,18 +20865,16 @@ class GtfsParser {
|
|
|
20416
20865
|
* @param activeStops The set of active stop IDs to include in the index.
|
|
20417
20866
|
* @returns An index of stops.
|
|
20418
20867
|
*/
|
|
20419
|
-
parseStops() {
|
|
20420
|
-
|
|
20421
|
-
|
|
20422
|
-
|
|
20423
|
-
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
|
|
20429
|
-
return new StopsIndex(Array.from(stops.values()));
|
|
20430
|
-
});
|
|
20868
|
+
async parseStops() {
|
|
20869
|
+
const zip = new StreamZip.async({ file: this.path });
|
|
20870
|
+
log.info(`Parsing ${STOPS_FILE}`);
|
|
20871
|
+
const stopsStart = performance.now();
|
|
20872
|
+
const stopsStream = await zip.stream(STOPS_FILE);
|
|
20873
|
+
const stops = await parseStops(stopsStream);
|
|
20874
|
+
const stopsEnd = performance.now();
|
|
20875
|
+
log.info(`${stops.size} parsed stops. (${(stopsEnd - stopsStart).toFixed(2)}ms)`);
|
|
20876
|
+
await zip.close();
|
|
20877
|
+
return new StopsIndex(Array.from(stops.values()));
|
|
20431
20878
|
}
|
|
20432
20879
|
}
|
|
20433
20880
|
|
|
@@ -20481,133 +20928,324 @@ const chGtfsProfile = {
|
|
|
20481
20928
|
};
|
|
20482
20929
|
|
|
20483
20930
|
class Plotter {
|
|
20931
|
+
result;
|
|
20932
|
+
ROUND_COLORS = [
|
|
20933
|
+
'#60a5fa', // Round 1
|
|
20934
|
+
'#ff9800', // Round 2
|
|
20935
|
+
'#14b8a6', // Round 3
|
|
20936
|
+
'#fb7185', // Round 4
|
|
20937
|
+
'#ffdf00', // Round 5
|
|
20938
|
+
'#b600ff', // Round 6
|
|
20939
|
+
'#ee82ee', // Round 7+
|
|
20940
|
+
];
|
|
20484
20941
|
constructor(result) {
|
|
20485
20942
|
this.result = result;
|
|
20486
20943
|
}
|
|
20487
20944
|
/**
|
|
20488
|
-
*
|
|
20489
|
-
*
|
|
20490
|
-
* @returns A string representing the DOT graph of the path tree.
|
|
20945
|
+
* Gets the color for a round based on the specified palette.
|
|
20491
20946
|
*/
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
' node [shape=ellipse style=filled fillcolor=lightgrey];',
|
|
20498
|
-
];
|
|
20499
|
-
earliestArrivalsPerRound.forEach((arrivalsInRound, round) => {
|
|
20500
|
-
arrivalsInRound.forEach((tripLeg) => {
|
|
20501
|
-
const { origin, leg } = tripLeg;
|
|
20502
|
-
if (!leg)
|
|
20503
|
-
return; // Skip if leg is undefined
|
|
20504
|
-
const fromStop = this.result['stopsIndex'].findStopById(leg.from.id);
|
|
20505
|
-
const toStop = this.result['stopsIndex'].findStopById(leg.to.id);
|
|
20506
|
-
const originStop = this.result['stopsIndex'].findStopById(origin);
|
|
20507
|
-
if (fromStop && toStop && originStop) {
|
|
20508
|
-
const fromName = fromStop.platform
|
|
20509
|
-
? `${fromStop.name} (Pl. ${fromStop.platform})`
|
|
20510
|
-
: fromStop.name;
|
|
20511
|
-
const toName = toStop.platform
|
|
20512
|
-
? `${toStop.name} (Pl. ${toStop.platform})`
|
|
20513
|
-
: toStop.name;
|
|
20514
|
-
const originName = originStop.platform
|
|
20515
|
-
? `${originStop.name} (Pl. ${originStop.platform})`
|
|
20516
|
-
: originStop.name;
|
|
20517
|
-
const isVehicle = 'route' in leg;
|
|
20518
|
-
const routeLabelContent = isVehicle
|
|
20519
|
-
? `${leg.route.name}\n${leg.departureTime.toString()} - ${leg.arrivalTime.toString()}`
|
|
20520
|
-
: leg.minTransferTime
|
|
20521
|
-
? leg.minTransferTime.toString()
|
|
20522
|
-
: '';
|
|
20523
|
-
const intermediateNode = `IntermediateNode${fromStop.id}_${toStop.id}`;
|
|
20524
|
-
const lineColor = isVehicle ? '' : ', color="red", fontcolor="red"';
|
|
20525
|
-
const labelColor = isVehicle ? '' : ' fontcolor="red"';
|
|
20526
|
-
dotParts.push(` "${fromName} (Origin: ${originName}) [R${round}]\n(${fromStop.id})" -> "${intermediateNode}" [shape=point${lineColor}];`);
|
|
20527
|
-
dotParts.push(` "${intermediateNode}" [label="${routeLabelContent}" shape=rect style=filled fillcolor=white${labelColor} border=0];`);
|
|
20528
|
-
dotParts.push(` "${intermediateNode}" -> "${toName} (Origin: ${originName}) [R${round}]\n(${toStop.id})" [${lineColor.replace(', ', '')}];`);
|
|
20529
|
-
}
|
|
20530
|
-
});
|
|
20531
|
-
});
|
|
20532
|
-
dotParts.push('}');
|
|
20533
|
-
return dotParts.join('\n');
|
|
20947
|
+
getRoundColor(round) {
|
|
20948
|
+
if (round === 0)
|
|
20949
|
+
return '#888888';
|
|
20950
|
+
const colorIndex = Math.min(round - 1, this.ROUND_COLORS.length - 1);
|
|
20951
|
+
return this.ROUND_COLORS[colorIndex] ?? '#ee82ee';
|
|
20534
20952
|
}
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20953
|
+
/**
|
|
20954
|
+
* Escapes special characters in DOT strings to prevent syntax errors.
|
|
20955
|
+
*/
|
|
20956
|
+
escapeDotString(str) {
|
|
20957
|
+
return str
|
|
20958
|
+
.replace(/\\/g, '\\\\')
|
|
20959
|
+
.replace(/"/g, '\\"')
|
|
20960
|
+
.replace(/\n/g, '\\n')
|
|
20961
|
+
.replace(/\r/g, '\\r')
|
|
20962
|
+
.replace(/\t/g, '\\t');
|
|
20543
20963
|
}
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
maxTransfers: 5,
|
|
20552
|
-
minTransferTime: Duration.fromSeconds(120),
|
|
20553
|
-
transportModes: ALL_TRANSPORT_MODES,
|
|
20554
|
-
};
|
|
20964
|
+
/**
|
|
20965
|
+
* Determines station type (origin/destination) information.
|
|
20966
|
+
*/
|
|
20967
|
+
getStationInfo(stopId) {
|
|
20968
|
+
const isOrigin = this.result.routingState.graph[0]?.has(stopId) ?? false;
|
|
20969
|
+
const isDestination = this.result.routingState.destinations.includes(stopId);
|
|
20970
|
+
return { isOrigin, isDestination };
|
|
20555
20971
|
}
|
|
20556
20972
|
/**
|
|
20557
|
-
*
|
|
20973
|
+
* Formats a stop name for display, including platform information.
|
|
20558
20974
|
*/
|
|
20559
|
-
|
|
20560
|
-
|
|
20561
|
-
|
|
20975
|
+
formatStopName(stopId) {
|
|
20976
|
+
const stop = this.result.stopsIndex.findStopById(stopId);
|
|
20977
|
+
if (!stop)
|
|
20978
|
+
return `Unknown Stop (${stopId})`;
|
|
20979
|
+
const escapedName = this.escapeDotString(stop.name);
|
|
20980
|
+
const escapedPlatform = stop.platform
|
|
20981
|
+
? this.escapeDotString(stop.platform)
|
|
20982
|
+
: '';
|
|
20983
|
+
return escapedPlatform
|
|
20984
|
+
? `${escapedName}\\nPl. ${escapedPlatform}`
|
|
20985
|
+
: escapedName;
|
|
20562
20986
|
}
|
|
20563
20987
|
/**
|
|
20564
|
-
*
|
|
20988
|
+
* Gets the appropriate fill color for a station based on its type.
|
|
20565
20989
|
*/
|
|
20566
|
-
|
|
20567
|
-
|
|
20568
|
-
|
|
20990
|
+
getStationFillColor(isOrigin, isDestination) {
|
|
20991
|
+
if (isOrigin)
|
|
20992
|
+
return '#60a5fa';
|
|
20993
|
+
if (isDestination)
|
|
20994
|
+
return '#ee82ee';
|
|
20995
|
+
return 'white';
|
|
20569
20996
|
}
|
|
20570
20997
|
/**
|
|
20571
|
-
*
|
|
20572
|
-
* Note that the router will favor routes that depart shortly after the provided departure time,
|
|
20573
|
-
* even if a later route might arrive at the same time.
|
|
20574
|
-
* Range queries will allow to specify a range of departure times in the future.
|
|
20998
|
+
* Creates a DOT node for a station.
|
|
20575
20999
|
*/
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
21000
|
+
createStationNode(stopId) {
|
|
21001
|
+
const stop = this.result.stopsIndex.findStopById(stopId);
|
|
21002
|
+
if (!stop)
|
|
21003
|
+
return '';
|
|
21004
|
+
const displayName = this.formatStopName(stopId);
|
|
21005
|
+
const stopIdStr = this.escapeDotString(String(stopId));
|
|
21006
|
+
const nodeId = `s_${stopId}`;
|
|
21007
|
+
const stationInfo = this.getStationInfo(stopId);
|
|
21008
|
+
const fillColor = this.getStationFillColor(stationInfo.isOrigin, stationInfo.isDestination);
|
|
21009
|
+
return ` "${nodeId}" [label="${displayName}\\n${stopIdStr}" shape=box style=filled fillcolor="${fillColor}"];`;
|
|
20579
21010
|
}
|
|
20580
21011
|
/**
|
|
20581
|
-
*
|
|
21012
|
+
* Creates a vehicle edge with route information oval in the middle.
|
|
20582
21013
|
*/
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
|
|
21014
|
+
createVehicleEdge(edge, round) {
|
|
21015
|
+
const fromNodeId = `s_${edge.from}`;
|
|
21016
|
+
const toNodeId = `s_${edge.to}`;
|
|
21017
|
+
const roundColor = this.getRoundColor(round);
|
|
21018
|
+
const routeOvalId = `e_${edge.from}_${edge.to}_${edge.routeId}_${round}`;
|
|
21019
|
+
const route = this.result.timetable.getRoute(edge.routeId);
|
|
21020
|
+
const serviceRouteInfo = route
|
|
21021
|
+
? this.result.timetable.getServiceRouteInfo(route)
|
|
21022
|
+
: null;
|
|
21023
|
+
const routeName = serviceRouteInfo?.name ?? `Route ${String(edge.routeId)}`;
|
|
21024
|
+
const routeType = serviceRouteInfo?.type || 'UNKNOWN';
|
|
21025
|
+
const departureTime = route
|
|
21026
|
+
? route.departureFrom(edge.from, edge.tripIndex).toString()
|
|
21027
|
+
: 'N/A';
|
|
21028
|
+
const arrivalTime = edge.arrival.toString();
|
|
21029
|
+
const escapedRouteName = this.escapeDotString(routeName);
|
|
21030
|
+
const escapedRouteType = this.escapeDotString(routeType);
|
|
21031
|
+
const routeInfo = `${edge.routeId}:${edge.tripIndex}`;
|
|
21032
|
+
const ovalLabel = `${escapedRouteType} ${escapedRouteName}\\n${routeInfo}\\n${departureTime} → ${arrivalTime}`;
|
|
21033
|
+
return [
|
|
21034
|
+
` "${routeOvalId}" [label="${ovalLabel}" shape=oval style=filled fillcolor="white" color="${roundColor}"];`,
|
|
21035
|
+
` "${fromNodeId}" -> "${routeOvalId}" [color="${roundColor}"];`,
|
|
21036
|
+
` "${routeOvalId}" -> "${toNodeId}" [color="${roundColor}"];`,
|
|
21037
|
+
];
|
|
20586
21038
|
}
|
|
20587
21039
|
/**
|
|
20588
|
-
*
|
|
21040
|
+
* Creates a transfer edge with transfer information oval in the middle.
|
|
20589
21041
|
*/
|
|
20590
|
-
|
|
20591
|
-
|
|
20592
|
-
|
|
21042
|
+
createTransferEdge(edge, round) {
|
|
21043
|
+
const fromNodeId = `s_${edge.from}`;
|
|
21044
|
+
const toNodeId = `s_${edge.to}`;
|
|
21045
|
+
const roundColor = this.getRoundColor(round);
|
|
21046
|
+
const transferOvalId = `e_${edge.from}_${edge.to}_${round}`;
|
|
21047
|
+
const transferTime = edge.minTransferTime?.toString() || 'N/A';
|
|
21048
|
+
const escapedTransferTime = this.escapeDotString(transferTime);
|
|
21049
|
+
const ovalLabel = `Transfer\\n${escapedTransferTime}`;
|
|
21050
|
+
return [
|
|
21051
|
+
` "${transferOvalId}" [label="${ovalLabel}" shape=oval style="dashed,filled" fillcolor="white" color="${roundColor}"];`,
|
|
21052
|
+
` "${fromNodeId}" -> "${transferOvalId}" [color="${roundColor}" style="dashed"];`,
|
|
21053
|
+
` "${transferOvalId}" -> "${toNodeId}" [color="${roundColor}" style="dashed"];`,
|
|
21054
|
+
];
|
|
20593
21055
|
}
|
|
20594
21056
|
/**
|
|
20595
|
-
*
|
|
21057
|
+
* Creates a continuation edge to visually link trip continuations.
|
|
20596
21058
|
*/
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
|
|
21059
|
+
createContinuationEdge(fromEdge, toEdge, round) {
|
|
21060
|
+
const fromStationId = `s_${fromEdge.to}`;
|
|
21061
|
+
const toStationId = `s_${toEdge.from}`;
|
|
21062
|
+
const roundColor = this.getRoundColor(round);
|
|
21063
|
+
const continuationOvalId = `continuation_${fromEdge.to}_${toEdge.from}_${round}`;
|
|
21064
|
+
const fromRoute = this.result.timetable.getRoute(fromEdge.routeId);
|
|
21065
|
+
const toRoute = this.result.timetable.getRoute(toEdge.routeId);
|
|
21066
|
+
const fromServiceRouteInfo = fromRoute
|
|
21067
|
+
? this.result.timetable.getServiceRouteInfo(fromRoute)
|
|
21068
|
+
: null;
|
|
21069
|
+
const toServiceRouteInfo = toRoute
|
|
21070
|
+
? this.result.timetable.getServiceRouteInfo(toRoute)
|
|
21071
|
+
: null;
|
|
21072
|
+
const fromRouteName = fromServiceRouteInfo?.name ?? `Route ${String(fromEdge.routeId)}`;
|
|
21073
|
+
const toRouteName = toServiceRouteInfo?.name ?? `Route ${String(toEdge.routeId)}`;
|
|
21074
|
+
const fromRouteType = fromServiceRouteInfo?.type || 'UNKNOWN';
|
|
21075
|
+
const toRouteType = toServiceRouteInfo?.type || 'UNKNOWN';
|
|
21076
|
+
const fromArrivalTime = fromEdge.arrival.toString();
|
|
21077
|
+
const toDepartureTime = toRoute
|
|
21078
|
+
? toRoute.departureFrom(toEdge.from, toEdge.tripIndex).toString()
|
|
21079
|
+
: 'N/A';
|
|
21080
|
+
const escapedFromRouteName = this.escapeDotString(fromRouteName);
|
|
21081
|
+
const escapedToRouteName = this.escapeDotString(toRouteName);
|
|
21082
|
+
const escapedFromRouteType = this.escapeDotString(fromRouteType);
|
|
21083
|
+
const escapedToRouteType = this.escapeDotString(toRouteType);
|
|
21084
|
+
const fromRouteInfo = `${fromEdge.routeId}:${fromEdge.tripIndex}`;
|
|
21085
|
+
const toRouteInfo = `${toEdge.routeId}:${toEdge.tripIndex}`;
|
|
21086
|
+
const ovalLabel = `${escapedFromRouteType} ${escapedFromRouteName} (${fromRouteInfo}) ${fromArrivalTime}\\n↓\\n${escapedToRouteType} ${escapedToRouteName} (${toRouteInfo}) ${toDepartureTime}`;
|
|
21087
|
+
return [
|
|
21088
|
+
` "${continuationOvalId}" [label="${ovalLabel}" shape=oval style="filled,bold" fillcolor="#ffffcc" color="${roundColor}" penwidth="2"];`,
|
|
21089
|
+
` "${fromStationId}" -> "${continuationOvalId}" [color="${roundColor}" style="bold" penwidth="3"];`,
|
|
21090
|
+
` "${continuationOvalId}" -> "${toStationId}" [color="${roundColor}" style="bold" penwidth="3"];`,
|
|
21091
|
+
];
|
|
21092
|
+
}
|
|
21093
|
+
/**
|
|
21094
|
+
* Collects all stations and edges for the graph.
|
|
21095
|
+
*/
|
|
21096
|
+
collectGraphData() {
|
|
21097
|
+
const stations = new Set();
|
|
21098
|
+
const edges = [];
|
|
21099
|
+
const continuationEdges = [];
|
|
21100
|
+
const graph = this.result.routingState.graph;
|
|
21101
|
+
// Collect all stops that appear in the graph
|
|
21102
|
+
graph.forEach((roundMap) => {
|
|
21103
|
+
roundMap.forEach((edge, stopId) => {
|
|
21104
|
+
stations.add(stopId);
|
|
21105
|
+
if ('from' in edge && 'to' in edge) {
|
|
21106
|
+
stations.add(edge.from);
|
|
21107
|
+
stations.add(edge.to);
|
|
21108
|
+
}
|
|
21109
|
+
});
|
|
21110
|
+
});
|
|
21111
|
+
// Create edges for each round
|
|
21112
|
+
graph.forEach((roundMap, round) => {
|
|
21113
|
+
if (round === 0) {
|
|
21114
|
+
// Skip round 0 as it contains only origin nodes
|
|
21115
|
+
return;
|
|
21116
|
+
}
|
|
21117
|
+
roundMap.forEach((edge) => {
|
|
21118
|
+
if ('from' in edge && 'to' in edge) {
|
|
21119
|
+
if ('routeId' in edge) {
|
|
21120
|
+
const vehicleEdgeParts = this.createVehicleEdge(edge, round);
|
|
21121
|
+
edges.push(...vehicleEdgeParts);
|
|
21122
|
+
if (edge.continuationOf) {
|
|
21123
|
+
let currentEdge = edge;
|
|
21124
|
+
let previousEdge = edge.continuationOf;
|
|
21125
|
+
while (previousEdge) {
|
|
21126
|
+
const continuationEdgeParts = this.createContinuationEdge(previousEdge, currentEdge, round);
|
|
21127
|
+
continuationEdges.push(...continuationEdgeParts);
|
|
21128
|
+
currentEdge = previousEdge;
|
|
21129
|
+
previousEdge = previousEdge.continuationOf;
|
|
21130
|
+
}
|
|
21131
|
+
}
|
|
21132
|
+
}
|
|
21133
|
+
else {
|
|
21134
|
+
const transferEdgeParts = this.createTransferEdge(edge, round);
|
|
21135
|
+
edges.push(...transferEdgeParts);
|
|
21136
|
+
}
|
|
21137
|
+
}
|
|
21138
|
+
});
|
|
21139
|
+
});
|
|
21140
|
+
edges.push(...continuationEdges);
|
|
21141
|
+
return { stations, edges };
|
|
20600
21142
|
}
|
|
20601
|
-
|
|
20602
|
-
|
|
21143
|
+
/**
|
|
21144
|
+
* Plots the routing graph as a DOT graph for visualization.
|
|
21145
|
+
*/
|
|
21146
|
+
plotDotGraph() {
|
|
21147
|
+
const { stations, edges } = this.collectGraphData();
|
|
21148
|
+
const dotParts = [
|
|
21149
|
+
'digraph RoutingGraph {',
|
|
21150
|
+
' graph [overlap=false, splines=true, rankdir=TB, bgcolor=white, nodesep=0.8, ranksep=1.2, concentrate=true];',
|
|
21151
|
+
' node [fontname="Arial" margin=0.1];',
|
|
21152
|
+
' edge [fontname="Arial" fontsize=10];',
|
|
21153
|
+
'',
|
|
21154
|
+
' // Stations',
|
|
21155
|
+
];
|
|
21156
|
+
stations.forEach((stopId) => {
|
|
21157
|
+
const stationNode = this.createStationNode(stopId);
|
|
21158
|
+
if (stationNode) {
|
|
21159
|
+
dotParts.push(stationNode);
|
|
21160
|
+
}
|
|
21161
|
+
});
|
|
21162
|
+
dotParts.push('', ' // Edges');
|
|
21163
|
+
dotParts.push(...edges);
|
|
21164
|
+
dotParts.push('}');
|
|
21165
|
+
return dotParts.join('\n');
|
|
20603
21166
|
}
|
|
20604
|
-
}
|
|
21167
|
+
}
|
|
21168
|
+
|
|
21169
|
+
class Query {
|
|
21170
|
+
from;
|
|
21171
|
+
to;
|
|
21172
|
+
departureTime;
|
|
21173
|
+
lastDepartureTime;
|
|
21174
|
+
options;
|
|
21175
|
+
constructor(builder) {
|
|
21176
|
+
this.from = builder.fromValue;
|
|
21177
|
+
this.to = builder.toValue;
|
|
21178
|
+
this.departureTime = builder.departureTimeValue;
|
|
21179
|
+
this.options = builder.optionsValue;
|
|
21180
|
+
}
|
|
21181
|
+
static Builder = class {
|
|
21182
|
+
fromValue;
|
|
21183
|
+
toValue = new Set();
|
|
21184
|
+
departureTimeValue;
|
|
21185
|
+
// lastDepartureTimeValue?: Date;
|
|
21186
|
+
// via: StopId[] = [];
|
|
21187
|
+
optionsValue = {
|
|
21188
|
+
maxTransfers: 5,
|
|
21189
|
+
minTransferTime: Duration.fromSeconds(120),
|
|
21190
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
21191
|
+
};
|
|
21192
|
+
/**
|
|
21193
|
+
* Sets the starting stop.
|
|
21194
|
+
*/
|
|
21195
|
+
from(from) {
|
|
21196
|
+
this.fromValue = from;
|
|
21197
|
+
return this;
|
|
21198
|
+
}
|
|
21199
|
+
/**
|
|
21200
|
+
* Sets the destination stops(s), routing will stop when all the provided stops are reached.
|
|
21201
|
+
*/
|
|
21202
|
+
to(to) {
|
|
21203
|
+
this.toValue = to instanceof Set ? to : new Set([to]);
|
|
21204
|
+
return this;
|
|
21205
|
+
}
|
|
21206
|
+
/**
|
|
21207
|
+
* Sets the departure time for the query.
|
|
21208
|
+
* Note that the router will favor routes that depart shortly after the provided departure time,
|
|
21209
|
+
* even if a later route might arrive at the same time.
|
|
21210
|
+
* Range queries will allow to specify a range of departure times in the future.
|
|
21211
|
+
*/
|
|
21212
|
+
departureTime(departureTime) {
|
|
21213
|
+
this.departureTimeValue = departureTime;
|
|
21214
|
+
return this;
|
|
21215
|
+
}
|
|
21216
|
+
/**
|
|
21217
|
+
* Sets the maximum number of transfers allowed.
|
|
21218
|
+
*/
|
|
21219
|
+
maxTransfers(maxTransfers) {
|
|
21220
|
+
this.optionsValue.maxTransfers = maxTransfers;
|
|
21221
|
+
return this;
|
|
21222
|
+
}
|
|
21223
|
+
/**
|
|
21224
|
+
* Sets the minimum transfer time to use when no transfer time is provided in the data.
|
|
21225
|
+
*/
|
|
21226
|
+
minTransferTime(minTransferTime) {
|
|
21227
|
+
this.optionsValue.minTransferTime = minTransferTime;
|
|
21228
|
+
return this;
|
|
21229
|
+
}
|
|
21230
|
+
/**
|
|
21231
|
+
* Sets the transport modes to consider.
|
|
21232
|
+
*/
|
|
21233
|
+
transportModes(transportModes) {
|
|
21234
|
+
this.optionsValue.transportModes = transportModes;
|
|
21235
|
+
return this;
|
|
21236
|
+
}
|
|
21237
|
+
build() {
|
|
21238
|
+
return new Query(this);
|
|
21239
|
+
}
|
|
21240
|
+
};
|
|
21241
|
+
}
|
|
20605
21242
|
|
|
20606
21243
|
/**
|
|
20607
21244
|
* Represents a resolved route consisting of multiple legs,
|
|
20608
21245
|
* which can be either vehicle legs or transfer legs.
|
|
20609
21246
|
*/
|
|
20610
21247
|
class Route {
|
|
21248
|
+
legs;
|
|
20611
21249
|
constructor(legs) {
|
|
20612
21250
|
this.legs = legs;
|
|
20613
21251
|
}
|
|
@@ -20677,11 +21315,10 @@ class Route {
|
|
|
20677
21315
|
toString() {
|
|
20678
21316
|
return this.legs
|
|
20679
21317
|
.map((leg, index) => {
|
|
20680
|
-
var _a;
|
|
20681
21318
|
const fromStop = `From: ${leg.from.name}${leg.from.platform ? ` (Pl. ${leg.from.platform})` : ''}`;
|
|
20682
21319
|
const toStop = `To: ${leg.to.name}${leg.to.platform ? ` (Pl. ${leg.to.platform})` : ''}`;
|
|
20683
21320
|
const transferDetails = 'minTransferTime' in leg
|
|
20684
|
-
? `Minimum Transfer Time: ${
|
|
21321
|
+
? `Minimum Transfer Time: ${leg.minTransferTime?.toString()}`
|
|
20685
21322
|
: '';
|
|
20686
21323
|
const travelDetails = 'route' in leg && 'departureTime' in leg && 'arrivalTime' in leg
|
|
20687
21324
|
? `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`
|
|
@@ -20717,9 +21354,14 @@ class Route {
|
|
|
20717
21354
|
};
|
|
20718
21355
|
}
|
|
20719
21356
|
else {
|
|
20720
|
-
return
|
|
20721
|
-
|
|
20722
|
-
|
|
21357
|
+
return {
|
|
21358
|
+
from: leg.from.sourceStopId,
|
|
21359
|
+
to: leg.to.sourceStopId,
|
|
21360
|
+
type: leg.type,
|
|
21361
|
+
...(leg.minTransferTime !== undefined && {
|
|
21362
|
+
minTransferTime: leg.minTransferTime.toString(),
|
|
21363
|
+
}),
|
|
21364
|
+
};
|
|
20723
21365
|
}
|
|
20724
21366
|
});
|
|
20725
21367
|
return jsonLegs;
|
|
@@ -20727,11 +21369,15 @@ class Route {
|
|
|
20727
21369
|
}
|
|
20728
21370
|
|
|
20729
21371
|
class Result {
|
|
20730
|
-
|
|
21372
|
+
query;
|
|
21373
|
+
routingState;
|
|
21374
|
+
stopsIndex;
|
|
21375
|
+
timetable;
|
|
21376
|
+
constructor(query, routingState, stopsIndex, timetable) {
|
|
20731
21377
|
this.query = query;
|
|
20732
|
-
this.
|
|
20733
|
-
this.earliestArrivalsPerRound = earliestArrivalsPerRound;
|
|
21378
|
+
this.routingState = routingState;
|
|
20734
21379
|
this.stopsIndex = stopsIndex;
|
|
21380
|
+
this.timetable = timetable;
|
|
20735
21381
|
}
|
|
20736
21382
|
/**
|
|
20737
21383
|
* Reconstructs the best route to a stop.
|
|
@@ -20741,17 +21387,17 @@ class Result {
|
|
|
20741
21387
|
* @returns a route to the destination stop if it exists.
|
|
20742
21388
|
*/
|
|
20743
21389
|
bestRoute(to) {
|
|
20744
|
-
var _a, _b, _c;
|
|
20745
21390
|
const destinationList = to instanceof Set
|
|
20746
21391
|
? Array.from(to)
|
|
20747
21392
|
: to
|
|
20748
21393
|
? [to]
|
|
20749
21394
|
: Array.from(this.query.to);
|
|
20750
21395
|
const destinations = destinationList.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
21396
|
+
// find the first reached destination
|
|
20751
21397
|
let fastestDestination = undefined;
|
|
20752
21398
|
let fastestTime = undefined;
|
|
20753
21399
|
for (const destination of destinations) {
|
|
20754
|
-
const arrivalTime = this.earliestArrivals.get(destination.id);
|
|
21400
|
+
const arrivalTime = this.routingState.earliestArrivals.get(destination.id);
|
|
20755
21401
|
if (arrivalTime !== undefined) {
|
|
20756
21402
|
if (fastestTime === undefined ||
|
|
20757
21403
|
arrivalTime.arrival.isBefore(fastestTime.arrival)) {
|
|
@@ -20766,19 +21412,84 @@ class Result {
|
|
|
20766
21412
|
const route = [];
|
|
20767
21413
|
let currentStop = fastestDestination;
|
|
20768
21414
|
let round = fastestTime.legNumber;
|
|
20769
|
-
while (
|
|
20770
|
-
const
|
|
20771
|
-
if (!
|
|
20772
|
-
throw new Error(`No
|
|
21415
|
+
while (round > 0) {
|
|
21416
|
+
const edge = this.routingState.graph[round]?.get(currentStop);
|
|
21417
|
+
if (!edge) {
|
|
21418
|
+
throw new Error(`No edge arriving at stop ${currentStop} at round ${round}`);
|
|
20773
21419
|
}
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
21420
|
+
let leg;
|
|
21421
|
+
if ('routeId' in edge) {
|
|
21422
|
+
let vehicleEdge = edge;
|
|
21423
|
+
// Handle leg reconstruction for in-seat trip continuations
|
|
21424
|
+
const chainedEdges = [vehicleEdge];
|
|
21425
|
+
while ('routeId' in vehicleEdge && vehicleEdge.continuationOf) {
|
|
21426
|
+
chainedEdges.push(vehicleEdge.continuationOf);
|
|
21427
|
+
vehicleEdge = vehicleEdge.continuationOf;
|
|
21428
|
+
}
|
|
21429
|
+
leg = this.buildVehicleLeg(chainedEdges);
|
|
21430
|
+
}
|
|
21431
|
+
else if ('type' in edge) {
|
|
21432
|
+
leg = this.buildTransferLeg(edge);
|
|
21433
|
+
}
|
|
21434
|
+
else {
|
|
21435
|
+
break;
|
|
21436
|
+
}
|
|
21437
|
+
route.unshift(leg);
|
|
21438
|
+
currentStop = leg.from.id;
|
|
21439
|
+
if ('routeId' in edge) {
|
|
20777
21440
|
round -= 1;
|
|
20778
21441
|
}
|
|
20779
21442
|
}
|
|
20780
21443
|
return new Route(route);
|
|
20781
21444
|
}
|
|
21445
|
+
/**
|
|
21446
|
+
* Builds a vehicle leg from a chain of vehicle edges.
|
|
21447
|
+
*
|
|
21448
|
+
* @param edges Array of vehicle edges representing continuous trips on transit vehicles
|
|
21449
|
+
* @returns A vehicle leg with departure/arrival information and route details
|
|
21450
|
+
* @throws Error if the edges array is empty
|
|
21451
|
+
*/
|
|
21452
|
+
buildVehicleLeg(edges) {
|
|
21453
|
+
if (edges.length === 0) {
|
|
21454
|
+
throw new Error('Cannot build vehicle leg from empty edges');
|
|
21455
|
+
}
|
|
21456
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21457
|
+
const firstEdge = edges[edges.length - 1];
|
|
21458
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21459
|
+
const lastEdge = edges[0];
|
|
21460
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21461
|
+
const firstRoute = this.timetable.getRoute(firstEdge.routeId);
|
|
21462
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21463
|
+
const lastRoute = this.timetable.getRoute(lastEdge.routeId);
|
|
21464
|
+
return {
|
|
21465
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21466
|
+
from: this.stopsIndex.findStopById(firstRoute.stopId(firstEdge.from)),
|
|
21467
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21468
|
+
to: this.stopsIndex.findStopById(lastRoute.stopId(lastEdge.to)),
|
|
21469
|
+
// The route info comes from the first boarded route in case on continuous trips
|
|
21470
|
+
route: this.timetable.getServiceRouteInfo(firstRoute),
|
|
21471
|
+
departureTime: firstRoute.departureFrom(firstEdge.from, firstEdge.tripIndex),
|
|
21472
|
+
arrivalTime: lastEdge.arrival,
|
|
21473
|
+
pickUpType: firstRoute.pickUpTypeFrom(firstEdge.from, firstEdge.tripIndex),
|
|
21474
|
+
dropOffType: lastRoute.dropOffTypeAt(lastEdge.to, lastEdge.tripIndex),
|
|
21475
|
+
};
|
|
21476
|
+
}
|
|
21477
|
+
/**
|
|
21478
|
+
* Builds a transfer leg from a transfer edge.
|
|
21479
|
+
*
|
|
21480
|
+
* @param edge Transfer edge representing a walking connection between stops
|
|
21481
|
+
* @returns A transfer leg with from/to stops and transfer details
|
|
21482
|
+
*/
|
|
21483
|
+
buildTransferLeg(edge) {
|
|
21484
|
+
return {
|
|
21485
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21486
|
+
from: this.stopsIndex.findStopById(edge.from),
|
|
21487
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
21488
|
+
to: this.stopsIndex.findStopById(edge.to),
|
|
21489
|
+
minTransferTime: edge.minTransferTime,
|
|
21490
|
+
type: edge.type,
|
|
21491
|
+
};
|
|
21492
|
+
}
|
|
20782
21493
|
/**
|
|
20783
21494
|
* Returns the arrival time at any stop reachable in less time / transfers than the destination(s) of the query)
|
|
20784
21495
|
*
|
|
@@ -20789,12 +21500,25 @@ class Result {
|
|
|
20789
21500
|
arrivalAt(stop, maxTransfers) {
|
|
20790
21501
|
const equivalentStops = this.stopsIndex.equivalentStops(stop);
|
|
20791
21502
|
let earliestArrival = undefined;
|
|
20792
|
-
const relevantArrivals = maxTransfers !== undefined
|
|
20793
|
-
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20794
|
-
this.earliestArrivalsPerRound[maxTransfers + 1]
|
|
20795
|
-
: this.earliestArrivals;
|
|
20796
21503
|
for (const equivalentStop of equivalentStops) {
|
|
20797
|
-
|
|
21504
|
+
let arrivalTime;
|
|
21505
|
+
if (maxTransfers === undefined) {
|
|
21506
|
+
arrivalTime = this.routingState.earliestArrivals.get(equivalentStop.id);
|
|
21507
|
+
}
|
|
21508
|
+
else {
|
|
21509
|
+
// We have no guarantee that the stop was visited in the last round,
|
|
21510
|
+
// so we need to check all rounds if it's not found in the last one.
|
|
21511
|
+
for (let i = maxTransfers + 1; i >= 0; i--) {
|
|
21512
|
+
const arrivalEdge = this.routingState.graph[i]?.get(equivalentStop.id);
|
|
21513
|
+
if (arrivalEdge !== undefined) {
|
|
21514
|
+
arrivalTime = {
|
|
21515
|
+
arrival: arrivalEdge.arrival,
|
|
21516
|
+
legNumber: i,
|
|
21517
|
+
};
|
|
21518
|
+
break;
|
|
21519
|
+
}
|
|
21520
|
+
}
|
|
21521
|
+
}
|
|
20798
21522
|
if (arrivalTime !== undefined) {
|
|
20799
21523
|
if (earliestArrival === undefined ||
|
|
20800
21524
|
arrivalTime.arrival.isBefore(earliestArrival.arrival)) {
|
|
@@ -20808,36 +21532,237 @@ class Result {
|
|
|
20808
21532
|
|
|
20809
21533
|
const UNREACHED = Time.infinity();
|
|
20810
21534
|
/**
|
|
20811
|
-
* A public transportation
|
|
20812
|
-
*
|
|
20813
|
-
*
|
|
21535
|
+
* A public transportation router implementing the RAPTOR algorithm.
|
|
21536
|
+
* For more information on the RAPTOR algorithm,
|
|
21537
|
+
* refer to its detailed explanation in the research paper:
|
|
20814
21538
|
* https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
|
|
20815
21539
|
*/
|
|
20816
21540
|
class Router {
|
|
21541
|
+
timetable;
|
|
21542
|
+
stopsIndex;
|
|
20817
21543
|
constructor(timetable, stopsIndex) {
|
|
20818
21544
|
this.timetable = timetable;
|
|
20819
21545
|
this.stopsIndex = stopsIndex;
|
|
20820
21546
|
}
|
|
20821
21547
|
/**
|
|
20822
|
-
*
|
|
20823
|
-
*
|
|
20824
|
-
*
|
|
21548
|
+
* The main Raptor algorithm implementation.
|
|
21549
|
+
*
|
|
21550
|
+
* @param query The query containing the main parameters for the routing.
|
|
21551
|
+
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
21552
|
+
*/
|
|
21553
|
+
route(query) {
|
|
21554
|
+
const routingState = this.initRoutingState(query);
|
|
21555
|
+
const markedStops = new Set();
|
|
21556
|
+
for (const originStop of routingState.graph[0].keys()) {
|
|
21557
|
+
markedStops.add(originStop);
|
|
21558
|
+
}
|
|
21559
|
+
// Initial transfer consideration for origins
|
|
21560
|
+
const newlyMarkedStops = this.considerTransfers(query, 0, markedStops, routingState);
|
|
21561
|
+
for (const newStop of newlyMarkedStops) {
|
|
21562
|
+
markedStops.add(newStop);
|
|
21563
|
+
}
|
|
21564
|
+
for (let round = 1; round <= query.options.maxTransfers + 1; round++) {
|
|
21565
|
+
const edgesAtCurrentRound = new Map();
|
|
21566
|
+
routingState.graph.push(edgesAtCurrentRound);
|
|
21567
|
+
const reachableRoutes = this.timetable.findReachableRoutes(markedStops, query.options.transportModes);
|
|
21568
|
+
markedStops.clear();
|
|
21569
|
+
// for each route that can be reached with at least round - 1 trips
|
|
21570
|
+
for (const [route, hopOnStopIndex] of reachableRoutes) {
|
|
21571
|
+
const newlyMarkedStops = this.scanRoute(route, hopOnStopIndex, round, routingState);
|
|
21572
|
+
for (const newStop of newlyMarkedStops) {
|
|
21573
|
+
markedStops.add(newStop);
|
|
21574
|
+
}
|
|
21575
|
+
}
|
|
21576
|
+
// process in-seat trip continuations
|
|
21577
|
+
let continuations = this.findTripContinuations(markedStops, edgesAtCurrentRound);
|
|
21578
|
+
while (continuations.length > 0) {
|
|
21579
|
+
const stopsFromContinuations = new Set();
|
|
21580
|
+
for (const continuation of continuations) {
|
|
21581
|
+
const route = this.timetable.getRoute(continuation.routeId);
|
|
21582
|
+
const routeScanResults = this.scanRoute(route, continuation.hopOnStopIndex, round, routingState, continuation);
|
|
21583
|
+
for (const newStop of routeScanResults) {
|
|
21584
|
+
stopsFromContinuations.add(newStop);
|
|
21585
|
+
}
|
|
21586
|
+
}
|
|
21587
|
+
for (const newStop of stopsFromContinuations) {
|
|
21588
|
+
markedStops.add(newStop);
|
|
21589
|
+
}
|
|
21590
|
+
continuations = this.findTripContinuations(stopsFromContinuations, edgesAtCurrentRound);
|
|
21591
|
+
}
|
|
21592
|
+
const newlyMarkedStops = this.considerTransfers(query, round, markedStops, routingState);
|
|
21593
|
+
for (const newStop of newlyMarkedStops) {
|
|
21594
|
+
markedStops.add(newStop);
|
|
21595
|
+
}
|
|
21596
|
+
if (markedStops.size === 0)
|
|
21597
|
+
break;
|
|
21598
|
+
}
|
|
21599
|
+
return new Result(query, routingState, this.stopsIndex, this.timetable);
|
|
21600
|
+
}
|
|
21601
|
+
/**
|
|
21602
|
+
* Finds trip continuations for the given marked stops and edges at the current round.
|
|
21603
|
+
* @param markedStops The set of marked stops.
|
|
21604
|
+
* @param edgesAtCurrentRound The map of edges at the current round.
|
|
21605
|
+
* @returns An array of trip continuations.
|
|
21606
|
+
*/
|
|
21607
|
+
findTripContinuations(markedStops, edgesAtCurrentRound) {
|
|
21608
|
+
const continuations = [];
|
|
21609
|
+
for (const stopId of markedStops) {
|
|
21610
|
+
const arrival = edgesAtCurrentRound.get(stopId);
|
|
21611
|
+
if (!arrival || !('routeId' in arrival))
|
|
21612
|
+
continue;
|
|
21613
|
+
const continuousTrips = this.timetable.getContinuousTrips(arrival.to, arrival.routeId, arrival.tripIndex);
|
|
21614
|
+
for (let i = 0; i < continuousTrips.length; i++) {
|
|
21615
|
+
const trip = continuousTrips[i];
|
|
21616
|
+
continuations.push({
|
|
21617
|
+
routeId: trip.routeId,
|
|
21618
|
+
hopOnStopIndex: trip.hopOnStopIndex,
|
|
21619
|
+
tripIndex: trip.tripIndex,
|
|
21620
|
+
previousEdge: arrival,
|
|
21621
|
+
});
|
|
21622
|
+
}
|
|
21623
|
+
}
|
|
21624
|
+
return continuations;
|
|
21625
|
+
}
|
|
21626
|
+
/**
|
|
21627
|
+
* Initializes the routing state for the RAPTOR algorithm.
|
|
21628
|
+
*
|
|
21629
|
+
* This method sets up the initial data structures needed for route planning,
|
|
21630
|
+
* including origin and destination stops (considering equivalent stops),
|
|
21631
|
+
* earliest arrival times, and marked stops for processing.
|
|
21632
|
+
*
|
|
21633
|
+
* @param query The routing query containing origin, destination, and departure time
|
|
21634
|
+
* @returns The initialized routing state with all necessary data structures
|
|
20825
21635
|
*/
|
|
20826
|
-
|
|
20827
|
-
|
|
21636
|
+
initRoutingState(query) {
|
|
21637
|
+
const { from, to, departureTime } = query;
|
|
21638
|
+
// Consider children or siblings of the "from" stop as potential origins
|
|
21639
|
+
const origins = this.stopsIndex
|
|
21640
|
+
.equivalentStops(from)
|
|
21641
|
+
.map((origin) => origin.id);
|
|
21642
|
+
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
21643
|
+
const destinations = Array.from(to)
|
|
21644
|
+
.flatMap((destination) => this.stopsIndex.equivalentStops(destination))
|
|
21645
|
+
.map((destination) => destination.id);
|
|
21646
|
+
const earliestArrivals = new Map();
|
|
21647
|
+
const earliestArrivalsWithoutAnyLeg = new Map();
|
|
21648
|
+
const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
|
|
21649
|
+
const initialState = {
|
|
21650
|
+
arrival: departureTime,
|
|
21651
|
+
legNumber: 0,
|
|
21652
|
+
};
|
|
21653
|
+
for (const originStop of origins) {
|
|
21654
|
+
earliestArrivals.set(originStop, initialState);
|
|
21655
|
+
earliestArrivalsWithoutAnyLeg.set(originStop, initialState);
|
|
21656
|
+
}
|
|
21657
|
+
return {
|
|
21658
|
+
destinations,
|
|
21659
|
+
earliestArrivals,
|
|
21660
|
+
graph: earliestArrivalsPerRound,
|
|
21661
|
+
};
|
|
21662
|
+
}
|
|
21663
|
+
/**
|
|
21664
|
+
* Scans a route to find the earliest possible trips (if not provided) and updates arrival times.
|
|
21665
|
+
*
|
|
21666
|
+
* This method implements the core route scanning logic of the RAPTOR algorithm.
|
|
21667
|
+
* It iterates through all stops on a given route starting from the hop-on stop,
|
|
21668
|
+
* maintaining the current best trip and updating arrival times when improvements
|
|
21669
|
+
* are found. The method also handles boarding new trips when earlier departures
|
|
21670
|
+
* are available if no given trip is provided as a parameter.
|
|
21671
|
+
*
|
|
21672
|
+
* @param route The route to scan for possible trips
|
|
21673
|
+
* @param hopOnStopIndex The stop index where passengers can board the route
|
|
21674
|
+
* @param round The current round number in the RAPTOR algorithm
|
|
21675
|
+
* @param routingState The current routing state containing arrival times and marked stops
|
|
21676
|
+
*/
|
|
21677
|
+
scanRoute(route, hopOnStopIndex, round, routingState, tripContinuation) {
|
|
21678
|
+
const newlyMarkedStops = new Set();
|
|
21679
|
+
let activeTrip = tripContinuation
|
|
21680
|
+
? {
|
|
21681
|
+
routeId: route.id,
|
|
21682
|
+
hopOnStopIndex,
|
|
21683
|
+
tripIndex: tripContinuation.tripIndex,
|
|
21684
|
+
}
|
|
21685
|
+
: undefined;
|
|
21686
|
+
const edgesAtCurrentRound = routingState.graph[round];
|
|
21687
|
+
const edgesAtPreviousRound = routingState.graph[round - 1];
|
|
21688
|
+
// Compute target pruning criteria only once per route
|
|
21689
|
+
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(routingState.earliestArrivals, routingState.destinations);
|
|
21690
|
+
for (let currentStopIndex = hopOnStopIndex; currentStopIndex < route.getNbStops(); currentStopIndex++) {
|
|
21691
|
+
const currentStop = route.stops[currentStopIndex];
|
|
21692
|
+
// If we're currently on a trip,
|
|
21693
|
+
// check if arrival at the stop improves the earliest arrival time
|
|
21694
|
+
if (activeTrip !== undefined) {
|
|
21695
|
+
const arrivalTime = route.arrivalAt(currentStopIndex, activeTrip.tripIndex);
|
|
21696
|
+
const dropOffType = route.dropOffTypeAt(currentStopIndex, activeTrip.tripIndex);
|
|
21697
|
+
const earliestArrivalAtCurrentStop = routingState.earliestArrivals.get(currentStop)?.arrival ?? UNREACHED;
|
|
21698
|
+
if (dropOffType !== 'NOT_AVAILABLE' &&
|
|
21699
|
+
arrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
|
|
21700
|
+
arrivalTime.isBefore(earliestArrivalAtAnyDestination)) {
|
|
21701
|
+
const edge = {
|
|
21702
|
+
arrival: arrivalTime,
|
|
21703
|
+
routeId: route.id,
|
|
21704
|
+
tripIndex: activeTrip.tripIndex,
|
|
21705
|
+
from: activeTrip.hopOnStopIndex,
|
|
21706
|
+
to: currentStopIndex,
|
|
21707
|
+
};
|
|
21708
|
+
if (tripContinuation) {
|
|
21709
|
+
// In case of continuous trip, we set a pointer to the previous edge
|
|
21710
|
+
edge.continuationOf = tripContinuation.previousEdge;
|
|
21711
|
+
}
|
|
21712
|
+
edgesAtCurrentRound.set(currentStop, edge);
|
|
21713
|
+
routingState.earliestArrivals.set(currentStop, {
|
|
21714
|
+
arrival: arrivalTime,
|
|
21715
|
+
legNumber: round,
|
|
21716
|
+
});
|
|
21717
|
+
newlyMarkedStops.add(currentStop);
|
|
21718
|
+
}
|
|
21719
|
+
}
|
|
21720
|
+
if (tripContinuation) {
|
|
21721
|
+
// If it's a trip continuation, no need to check for earlier trips
|
|
21722
|
+
continue;
|
|
21723
|
+
}
|
|
21724
|
+
// check if we can board an earlier trip at the current stop
|
|
21725
|
+
// if there was no current trip, find the first one reachable
|
|
21726
|
+
const earliestArrivalOnPreviousRound = edgesAtPreviousRound.get(currentStop)?.arrival;
|
|
21727
|
+
// TODO if the last edge is not a transfer, and if there is no trip continuation of type 1 (guaranteed)
|
|
21728
|
+
// Add the minTransferTime to make sure there's at least 2 minutes to transfer.
|
|
21729
|
+
// If platforms are collapsed, make sure to apply the station level transfer time
|
|
21730
|
+
// (or later at route reconstruction time)
|
|
21731
|
+
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
21732
|
+
(activeTrip === undefined ||
|
|
21733
|
+
earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStopIndex, activeTrip.tripIndex)) ||
|
|
21734
|
+
earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStopIndex, activeTrip.tripIndex)))) {
|
|
21735
|
+
const earliestTrip = route.findEarliestTrip(currentStopIndex, earliestArrivalOnPreviousRound, activeTrip?.tripIndex);
|
|
21736
|
+
if (earliestTrip !== undefined) {
|
|
21737
|
+
activeTrip = {
|
|
21738
|
+
routeId: route.id,
|
|
21739
|
+
tripIndex: earliestTrip,
|
|
21740
|
+
hopOnStopIndex: currentStopIndex,
|
|
21741
|
+
};
|
|
21742
|
+
}
|
|
21743
|
+
}
|
|
21744
|
+
}
|
|
21745
|
+
return newlyMarkedStops;
|
|
21746
|
+
}
|
|
21747
|
+
/**
|
|
21748
|
+
* Processes all currently marked stops to find available transfers
|
|
21749
|
+
* and determines if using these transfers would result in earlier arrival times
|
|
21750
|
+
* at destination stops. It handles different transfer types including in-seat
|
|
21751
|
+
* transfers and walking transfers with appropriate minimum transfer times.
|
|
21752
|
+
*
|
|
21753
|
+
* @param query The routing query containing transfer options and constraints
|
|
21754
|
+
* @param round The current round number in the RAPTOR algorithm
|
|
21755
|
+
* @param routingState The current routing state containing arrival times and marked stops
|
|
21756
|
+
*/
|
|
21757
|
+
considerTransfers(query, round, markedStops, routingState) {
|
|
20828
21758
|
const { options } = query;
|
|
21759
|
+
const arrivalsAtCurrentRound = routingState.graph[round];
|
|
20829
21760
|
const newlyMarkedStops = new Set();
|
|
20830
|
-
const
|
|
20831
|
-
for (let i = 0; i < markedStopsArray.length; i++) {
|
|
20832
|
-
const stop = markedStopsArray[i];
|
|
21761
|
+
for (const stop of markedStops) {
|
|
20833
21762
|
const currentArrival = arrivalsAtCurrentRound.get(stop);
|
|
20834
|
-
if (!currentArrival)
|
|
20835
|
-
continue;
|
|
20836
21763
|
// Skip transfers if the last leg was also a transfer
|
|
20837
|
-
|
|
20838
|
-
if (previousLeg && !('route' in previousLeg)) {
|
|
21764
|
+
if (!currentArrival || 'type' in currentArrival)
|
|
20839
21765
|
continue;
|
|
20840
|
-
}
|
|
20841
21766
|
const transfers = this.timetable.getTransfers(stop);
|
|
20842
21767
|
for (let j = 0; j < transfers.length; j++) {
|
|
20843
21768
|
const transfer = transfers[j];
|
|
@@ -20846,40 +21771,32 @@ class Router {
|
|
|
20846
21771
|
transferTime = transfer.minTransferTime;
|
|
20847
21772
|
}
|
|
20848
21773
|
else if (transfer.type === 'IN_SEAT') {
|
|
21774
|
+
// TODO not needed anymore now that trip continuations are handled separately
|
|
20849
21775
|
transferTime = Duration.zero();
|
|
20850
21776
|
}
|
|
20851
21777
|
else {
|
|
20852
21778
|
transferTime = options.minTransferTime;
|
|
20853
21779
|
}
|
|
20854
21780
|
const arrivalAfterTransfer = currentArrival.arrival.plus(transferTime);
|
|
20855
|
-
const originalArrival =
|
|
21781
|
+
const originalArrival = arrivalsAtCurrentRound.get(transfer.destination)?.arrival ??
|
|
21782
|
+
UNREACHED;
|
|
20856
21783
|
if (arrivalAfterTransfer.isBefore(originalArrival)) {
|
|
20857
|
-
const origin = currentArrival.origin;
|
|
20858
21784
|
arrivalsAtCurrentRound.set(transfer.destination, {
|
|
20859
21785
|
arrival: arrivalAfterTransfer,
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
|
|
20863
|
-
|
|
20864
|
-
to: this.stopsIndex.findStopById(transfer.destination),
|
|
20865
|
-
minTransferTime: transfer.minTransferTime,
|
|
20866
|
-
type: transfer.type,
|
|
20867
|
-
},
|
|
21786
|
+
from: stop,
|
|
21787
|
+
to: transfer.destination,
|
|
21788
|
+
minTransferTime: transfer.minTransferTime,
|
|
21789
|
+
type: transfer.type,
|
|
20868
21790
|
});
|
|
20869
|
-
earliestArrivals.set(transfer.destination, {
|
|
21791
|
+
routingState.earliestArrivals.set(transfer.destination, {
|
|
20870
21792
|
arrival: arrivalAfterTransfer,
|
|
20871
21793
|
legNumber: round,
|
|
20872
|
-
origin: origin,
|
|
20873
21794
|
});
|
|
20874
21795
|
newlyMarkedStops.add(transfer.destination);
|
|
20875
21796
|
}
|
|
20876
21797
|
}
|
|
20877
21798
|
}
|
|
20878
|
-
|
|
20879
|
-
for (let i = 0; i < newlyMarkedStopsArray.length; i++) {
|
|
20880
|
-
const newStop = newlyMarkedStopsArray[i];
|
|
20881
|
-
markedStops.add(newStop);
|
|
20882
|
-
}
|
|
21799
|
+
return newlyMarkedStops;
|
|
20883
21800
|
}
|
|
20884
21801
|
/**
|
|
20885
21802
|
* Finds the earliest arrival time at any stop from a given set of destinations.
|
|
@@ -20889,121 +21806,14 @@ class Router {
|
|
|
20889
21806
|
* @returns The earliest arrival time among the provided destinations.
|
|
20890
21807
|
*/
|
|
20891
21808
|
earliestArrivalAtAnyStop(earliestArrivals, destinations) {
|
|
20892
|
-
var _a, _b;
|
|
20893
21809
|
let earliestArrivalAtAnyDestination = UNREACHED;
|
|
20894
21810
|
for (let i = 0; i < destinations.length; i++) {
|
|
20895
21811
|
const destination = destinations[i];
|
|
20896
|
-
const arrival =
|
|
21812
|
+
const arrival = earliestArrivals.get(destination)?.arrival ?? UNREACHED;
|
|
20897
21813
|
earliestArrivalAtAnyDestination = Time.min(earliestArrivalAtAnyDestination, arrival);
|
|
20898
21814
|
}
|
|
20899
21815
|
return earliestArrivalAtAnyDestination;
|
|
20900
21816
|
}
|
|
20901
|
-
/**
|
|
20902
|
-
* The main Raptor algorithm implementation.
|
|
20903
|
-
*
|
|
20904
|
-
* @param query The query containing the main parameters for the routing.
|
|
20905
|
-
* @returns A result object containing data structures allowing to reconstruct routes and .
|
|
20906
|
-
*/
|
|
20907
|
-
route(query) {
|
|
20908
|
-
var _a, _b, _c, _d, _e;
|
|
20909
|
-
const { from, to, departureTime, options } = query;
|
|
20910
|
-
// Consider children or siblings of the "from" stop as potential origins
|
|
20911
|
-
const origins = this.stopsIndex.equivalentStops(from);
|
|
20912
|
-
// Consider children or siblings of the "to" stop(s) as potential destinations
|
|
20913
|
-
const destinations = Array.from(to).flatMap((destination) => this.stopsIndex.equivalentStops(destination));
|
|
20914
|
-
const earliestArrivals = new Map();
|
|
20915
|
-
const earliestArrivalsWithoutAnyLeg = new Map();
|
|
20916
|
-
const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
|
|
20917
|
-
// Stops that have been improved at round k-1
|
|
20918
|
-
const markedStops = new Set();
|
|
20919
|
-
for (let i = 0; i < origins.length; i++) {
|
|
20920
|
-
const originStop = origins[i];
|
|
20921
|
-
markedStops.add(originStop.id);
|
|
20922
|
-
earliestArrivals.set(originStop.id, {
|
|
20923
|
-
arrival: departureTime,
|
|
20924
|
-
legNumber: 0,
|
|
20925
|
-
origin: originStop.id,
|
|
20926
|
-
});
|
|
20927
|
-
earliestArrivalsWithoutAnyLeg.set(originStop.id, {
|
|
20928
|
-
arrival: departureTime,
|
|
20929
|
-
legNumber: 0,
|
|
20930
|
-
origin: originStop.id,
|
|
20931
|
-
});
|
|
20932
|
-
}
|
|
20933
|
-
// on the first round we need to first consider transfers to discover all possible route origins
|
|
20934
|
-
this.considerTransfers(query, markedStops, earliestArrivalsWithoutAnyLeg, earliestArrivals, 0);
|
|
20935
|
-
for (let round = 1; round <= options.maxTransfers + 1; round++) {
|
|
20936
|
-
const arrivalsAtCurrentRound = new Map();
|
|
20937
|
-
earliestArrivalsPerRound.push(arrivalsAtCurrentRound);
|
|
20938
|
-
const arrivalsAtPreviousRound = earliestArrivalsPerRound[round - 1];
|
|
20939
|
-
// Routes that contain at least one stop reached with at least round - 1 legs
|
|
20940
|
-
// together with corresponding hop on stop index (earliest marked stop)
|
|
20941
|
-
const reachableRoutes = this.timetable.findReachableRoutes(markedStops, options.transportModes);
|
|
20942
|
-
markedStops.clear();
|
|
20943
|
-
const earliestArrivalAtAnyDestination = this.earliestArrivalAtAnyStop(earliestArrivals, destinations);
|
|
20944
|
-
// for each route that can be reached with at least round - 1 trips
|
|
20945
|
-
const reachableRoutesArray = Array.from(reachableRoutes.entries());
|
|
20946
|
-
for (let i = 0; i < reachableRoutesArray.length; i++) {
|
|
20947
|
-
const [route, hopOnStop] = reachableRoutesArray[i];
|
|
20948
|
-
let currentTrip = undefined;
|
|
20949
|
-
const startIndex = route.stopIndex(hopOnStop);
|
|
20950
|
-
for (let j = startIndex; j < route.getNbStops(); j++) {
|
|
20951
|
-
const currentStop = route.stops[j];
|
|
20952
|
-
// If we're currently on a trip,
|
|
20953
|
-
// check if arrival at the stop improves the earliest arrival time
|
|
20954
|
-
if (currentTrip !== undefined) {
|
|
20955
|
-
const currentArrivalTime = route.arrivalAt(currentStop, currentTrip.tripIndex);
|
|
20956
|
-
const currentDropOffType = route.dropOffTypeAt(currentStop, currentTrip.tripIndex);
|
|
20957
|
-
const earliestArrivalAtCurrentStop = (_b = (_a = earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
|
|
20958
|
-
if (currentDropOffType !== 'NOT_AVAILABLE' &&
|
|
20959
|
-
currentArrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
|
|
20960
|
-
currentArrivalTime.isBefore(earliestArrivalAtAnyDestination)) {
|
|
20961
|
-
const bestHopOnDepartureTime = route.departureFrom(currentTrip.bestHopOnStop, currentTrip.tripIndex);
|
|
20962
|
-
arrivalsAtCurrentRound.set(currentStop, {
|
|
20963
|
-
arrival: currentArrivalTime,
|
|
20964
|
-
legNumber: round,
|
|
20965
|
-
origin: currentTrip.origin,
|
|
20966
|
-
leg: {
|
|
20967
|
-
from: this.stopsIndex.findStopById(currentTrip.bestHopOnStop),
|
|
20968
|
-
to: this.stopsIndex.findStopById(currentStop),
|
|
20969
|
-
departureTime: bestHopOnDepartureTime,
|
|
20970
|
-
arrivalTime: currentArrivalTime,
|
|
20971
|
-
route: this.timetable.getServiceRouteInfo(route),
|
|
20972
|
-
},
|
|
20973
|
-
});
|
|
20974
|
-
earliestArrivals.set(currentStop, {
|
|
20975
|
-
arrival: currentArrivalTime,
|
|
20976
|
-
legNumber: round,
|
|
20977
|
-
origin: currentTrip.origin,
|
|
20978
|
-
});
|
|
20979
|
-
markedStops.add(currentStop);
|
|
20980
|
-
}
|
|
20981
|
-
}
|
|
20982
|
-
// check if we can board an earlier trip at the current stop
|
|
20983
|
-
// if there was no current trip, find the first one reachable
|
|
20984
|
-
const earliestArrivalOnPreviousRound = (_c = arrivalsAtPreviousRound.get(currentStop)) === null || _c === void 0 ? void 0 : _c.arrival;
|
|
20985
|
-
if (earliestArrivalOnPreviousRound !== undefined &&
|
|
20986
|
-
(currentTrip === undefined ||
|
|
20987
|
-
earliestArrivalOnPreviousRound.isBefore(route.departureFrom(currentStop, currentTrip.tripIndex)) ||
|
|
20988
|
-
earliestArrivalOnPreviousRound.equals(route.departureFrom(currentStop, currentTrip.tripIndex)))) {
|
|
20989
|
-
const earliestTrip = route.findEarliestTrip(currentStop, earliestArrivalOnPreviousRound, currentTrip === null || currentTrip === void 0 ? void 0 : currentTrip.tripIndex);
|
|
20990
|
-
if (earliestTrip !== undefined) {
|
|
20991
|
-
currentTrip = {
|
|
20992
|
-
tripIndex: earliestTrip,
|
|
20993
|
-
// we need to keep track of the best hop-on stop to reconstruct the route at the end
|
|
20994
|
-
bestHopOnStop: currentStop,
|
|
20995
|
-
origin: (_e = (_d = arrivalsAtPreviousRound.get(currentStop)) === null || _d === void 0 ? void 0 : _d.origin) !== null && _e !== void 0 ? _e : currentStop,
|
|
20996
|
-
};
|
|
20997
|
-
}
|
|
20998
|
-
}
|
|
20999
|
-
}
|
|
21000
|
-
}
|
|
21001
|
-
this.considerTransfers(query, markedStops, arrivalsAtCurrentRound, earliestArrivals, round);
|
|
21002
|
-
if (markedStops.size === 0)
|
|
21003
|
-
break;
|
|
21004
|
-
}
|
|
21005
|
-
return new Result(query, earliestArrivals, earliestArrivalsPerRound, this.stopsIndex);
|
|
21006
|
-
}
|
|
21007
21817
|
}
|
|
21008
21818
|
|
|
21009
21819
|
/**
|
|
@@ -21140,7 +21950,6 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21140
21950
|
replServer.defineCommand('route', {
|
|
21141
21951
|
help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
|
|
21142
21952
|
action(routeQuery) {
|
|
21143
|
-
var _a;
|
|
21144
21953
|
this.clearBufferedCommand();
|
|
21145
21954
|
const parts = routeQuery.split(' ').filter(Boolean);
|
|
21146
21955
|
const withTransfersIndex = parts.indexOf('with');
|
|
@@ -21196,7 +22005,7 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21196
22005
|
console.log(`Destination not reachable`);
|
|
21197
22006
|
}
|
|
21198
22007
|
else {
|
|
21199
|
-
console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${
|
|
22008
|
+
console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${fromStop.name}.`);
|
|
21200
22009
|
}
|
|
21201
22010
|
const bestRoute = result.bestRoute(toStop.sourceStopId);
|
|
21202
22011
|
if (bestRoute) {
|
|
@@ -21277,6 +22086,192 @@ const startRepl = (stopsPath, timetablePath) => {
|
|
|
21277
22086
|
this.displayPrompt();
|
|
21278
22087
|
},
|
|
21279
22088
|
});
|
|
22089
|
+
const formatPickupDropoffType = (type) => {
|
|
22090
|
+
switch (type) {
|
|
22091
|
+
case 'REGULAR':
|
|
22092
|
+
return 'R';
|
|
22093
|
+
case 'NOT_AVAILABLE':
|
|
22094
|
+
return 'N';
|
|
22095
|
+
case 'MUST_PHONE_AGENCY':
|
|
22096
|
+
return 'A';
|
|
22097
|
+
case 'MUST_COORDINATE_WITH_DRIVER':
|
|
22098
|
+
return 'D';
|
|
22099
|
+
default:
|
|
22100
|
+
return '?';
|
|
22101
|
+
}
|
|
22102
|
+
};
|
|
22103
|
+
replServer.defineCommand('inspect', {
|
|
22104
|
+
help: 'Inspect a route or stop using .inspect route <routeId> or .inspect stop <stopId>',
|
|
22105
|
+
action(inspectQuery) {
|
|
22106
|
+
this.clearBufferedCommand();
|
|
22107
|
+
const parts = inspectQuery.trim().split(' ');
|
|
22108
|
+
if (parts.length !== 2) {
|
|
22109
|
+
console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
|
|
22110
|
+
this.displayPrompt();
|
|
22111
|
+
return;
|
|
22112
|
+
}
|
|
22113
|
+
const [type, idStr] = parts;
|
|
22114
|
+
if (type !== 'route' && type !== 'stop') {
|
|
22115
|
+
console.log('Usage: .inspect route <routeId> or .inspect stop <stopId>');
|
|
22116
|
+
this.displayPrompt();
|
|
22117
|
+
return;
|
|
22118
|
+
}
|
|
22119
|
+
const inspectRoute = (routeIdStr) => {
|
|
22120
|
+
const routeId = parseInt(routeIdStr.trim());
|
|
22121
|
+
if (isNaN(routeId)) {
|
|
22122
|
+
console.log('Usage: .inspect route <routeId>');
|
|
22123
|
+
return;
|
|
22124
|
+
}
|
|
22125
|
+
const route = timetable.getRoute(routeId);
|
|
22126
|
+
if (!route) {
|
|
22127
|
+
console.log(`Route ${routeId} not found`);
|
|
22128
|
+
return;
|
|
22129
|
+
}
|
|
22130
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22131
|
+
const routeName = serviceRouteInfo.name;
|
|
22132
|
+
const routeType = serviceRouteInfo.type;
|
|
22133
|
+
console.log(`\n=== Route ${routeId} ===`);
|
|
22134
|
+
console.log(`Service Route: ${routeName}`);
|
|
22135
|
+
console.log(`Type: ${routeType}`);
|
|
22136
|
+
console.log(`Number of stops: ${route.getNbStops()}`);
|
|
22137
|
+
console.log(`Number of trips: ${route.getNbTrips()}`);
|
|
22138
|
+
console.log('\n--- Stops ---');
|
|
22139
|
+
for (let i = 0; i < route.stops.length; i++) {
|
|
22140
|
+
const stopId = route.stopId(i);
|
|
22141
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22142
|
+
const stop = stopsIndex.findStopById(stopId);
|
|
22143
|
+
const platform = stop.platform ? ` (Pl. ${stop.platform})` : '';
|
|
22144
|
+
console.log(`${i + 1}. ${stop.name}${platform} (${stopId}, ${stop.sourceStopId})`);
|
|
22145
|
+
}
|
|
22146
|
+
console.log('\n--- Trips ---');
|
|
22147
|
+
for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
|
|
22148
|
+
console.log(`\nTrip ${tripIndex}:`);
|
|
22149
|
+
for (let stopIndex = 0; stopIndex < route.stops.length; stopIndex++) {
|
|
22150
|
+
const stopId = route.stopId(stopIndex);
|
|
22151
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22152
|
+
const stop = stopsIndex.findStopById(stopId);
|
|
22153
|
+
const departure = route.departureFrom(stopIndex, tripIndex);
|
|
22154
|
+
const arrival = route.arrivalAt(stopIndex, tripIndex);
|
|
22155
|
+
const pickupType = route.pickUpTypeFrom(stopIndex, tripIndex);
|
|
22156
|
+
const dropOffType = route.dropOffTypeAt(stopIndex, tripIndex);
|
|
22157
|
+
const pickupStr = formatPickupDropoffType(pickupType);
|
|
22158
|
+
const dropOffStr = formatPickupDropoffType(dropOffType);
|
|
22159
|
+
console.log(` ${stopIndex + 1}. ${stop.name}: arr ${arrival.toString()} (${pickupStr}) → dep ${departure.toString()} (${dropOffStr})`);
|
|
22160
|
+
}
|
|
22161
|
+
}
|
|
22162
|
+
console.log();
|
|
22163
|
+
};
|
|
22164
|
+
const inspectStop = (stopIdStr) => {
|
|
22165
|
+
let stop;
|
|
22166
|
+
const stopBySourceId = stopsIndex.findStopBySourceStopId(stopIdStr);
|
|
22167
|
+
if (stopBySourceId !== undefined) {
|
|
22168
|
+
stop = stopBySourceId;
|
|
22169
|
+
}
|
|
22170
|
+
else if (!isNaN(Number(stopIdStr))) {
|
|
22171
|
+
const stopById = stopsIndex.findStopById(Number(stopIdStr));
|
|
22172
|
+
if (stopById !== undefined) {
|
|
22173
|
+
stop = stopById;
|
|
22174
|
+
}
|
|
22175
|
+
}
|
|
22176
|
+
else {
|
|
22177
|
+
const stops = stopsIndex.findStopsByName(stopIdStr);
|
|
22178
|
+
if (stops.length > 0) {
|
|
22179
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22180
|
+
stop = stops[0];
|
|
22181
|
+
}
|
|
22182
|
+
}
|
|
22183
|
+
if (!stop) {
|
|
22184
|
+
console.log(`Stop not found: ${stopIdStr}`);
|
|
22185
|
+
return;
|
|
22186
|
+
}
|
|
22187
|
+
console.log(`\n=== Stop ${stop.id} ===`);
|
|
22188
|
+
console.log(`Name: ${stop.name}`);
|
|
22189
|
+
if (stop.platform) {
|
|
22190
|
+
console.log(`Platform: ${stop.platform}`);
|
|
22191
|
+
}
|
|
22192
|
+
console.log(`Source ID: ${stop.sourceStopId}`);
|
|
22193
|
+
const routes = timetable.routesPassingThrough(stop.id);
|
|
22194
|
+
console.log(`Number of routes: ${routes.length}`);
|
|
22195
|
+
const equivalentStops = stopsIndex
|
|
22196
|
+
.equivalentStops(stop.sourceStopId)
|
|
22197
|
+
.filter((equivStop) => equivStop.id !== stop.id);
|
|
22198
|
+
console.log(`Number of equivalent stops: ${equivalentStops.length}`);
|
|
22199
|
+
if (equivalentStops.length > 0) {
|
|
22200
|
+
console.log('\n--- Equivalent Stops ---');
|
|
22201
|
+
equivalentStops.forEach((equivStop, index) => {
|
|
22202
|
+
const platform = equivStop.platform
|
|
22203
|
+
? ` (Pl. ${equivStop.platform})`
|
|
22204
|
+
: '';
|
|
22205
|
+
console.log(`${index + 1}. ${equivStop.name}${platform} (${equivStop.id}, ${equivStop.sourceStopId})`);
|
|
22206
|
+
});
|
|
22207
|
+
}
|
|
22208
|
+
if (routes.length > 0) {
|
|
22209
|
+
console.log('\n--- Routes ---');
|
|
22210
|
+
routes.forEach((route, index) => {
|
|
22211
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22212
|
+
console.log(`${index + 1}. Route ${route.id}: ${serviceRouteInfo.name} (${serviceRouteInfo.type})`);
|
|
22213
|
+
});
|
|
22214
|
+
}
|
|
22215
|
+
const transfers = timetable.getTransfers(stop.id);
|
|
22216
|
+
console.log(`Number of transfers: ${transfers.length}`);
|
|
22217
|
+
if (transfers.length > 0) {
|
|
22218
|
+
console.log('\n--- Transfers ---');
|
|
22219
|
+
transfers.forEach((transfer, index) => {
|
|
22220
|
+
const destStop = stopsIndex.findStopById(transfer.destination);
|
|
22221
|
+
const platform = destStop?.platform
|
|
22222
|
+
? ` (Pl. ${destStop.platform})`
|
|
22223
|
+
: '';
|
|
22224
|
+
const minTime = transfer.minTransferTime
|
|
22225
|
+
? ` (min: ${Math.floor(transfer.minTransferTime.toSeconds() / 60)}min)`
|
|
22226
|
+
: '';
|
|
22227
|
+
console.log(`${index + 1}. ${transfer.type} to ${destStop?.name ?? 'Unknown'}${platform} (${transfer.destination}, ${destStop?.sourceStopId ?? 'N/A'})${minTime}`);
|
|
22228
|
+
});
|
|
22229
|
+
}
|
|
22230
|
+
let totalContinuations = 0;
|
|
22231
|
+
console.log('\n--- Trip Continuations ---');
|
|
22232
|
+
routes.forEach((route) => {
|
|
22233
|
+
const serviceRouteInfo = timetable.getServiceRouteInfo(route);
|
|
22234
|
+
const stopIndices = route.stopRouteIndices(stop.id);
|
|
22235
|
+
for (let tripIndex = 0; tripIndex < route.getNbTrips(); tripIndex++) {
|
|
22236
|
+
for (const stopIndex of stopIndices) {
|
|
22237
|
+
const continuations = timetable.getContinuousTrips(stopIndex, route.id, tripIndex);
|
|
22238
|
+
for (const continuation of continuations) {
|
|
22239
|
+
totalContinuations++;
|
|
22240
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22241
|
+
const destRoute = timetable.getRoute(continuation.routeId);
|
|
22242
|
+
const destStopId = destRoute.stopId(continuation.hopOnStopIndex);
|
|
22243
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
22244
|
+
const destStop = stopsIndex.findStopById(destStopId);
|
|
22245
|
+
const destPlatform = destStop.platform
|
|
22246
|
+
? ` (Pl. ${destStop.platform})`
|
|
22247
|
+
: '';
|
|
22248
|
+
const destServiceRouteInfo = timetable.getServiceRouteInfo(destRoute);
|
|
22249
|
+
const originTime = route.departureFrom(stopIndex, tripIndex);
|
|
22250
|
+
const continuationTime = destRoute.departureFrom(continuation.hopOnStopIndex, continuation.tripIndex);
|
|
22251
|
+
console.log(`${totalContinuations}. From Route ${route.id} (${serviceRouteInfo.name}) Trip ${tripIndex} at ${originTime.toString()} → ` +
|
|
22252
|
+
`Route ${continuation.routeId} (${destServiceRouteInfo.name}) Trip ${continuation.tripIndex} at ${continuationTime.toString()} ` +
|
|
22253
|
+
`at ${destStop.name}${destPlatform} (${destStopId}, ${destStop.sourceStopId})`);
|
|
22254
|
+
}
|
|
22255
|
+
}
|
|
22256
|
+
}
|
|
22257
|
+
});
|
|
22258
|
+
if (totalContinuations === 0) {
|
|
22259
|
+
console.log('No trip continuations found.');
|
|
22260
|
+
}
|
|
22261
|
+
else {
|
|
22262
|
+
console.log(`\nTotal trip continuations: ${totalContinuations}`);
|
|
22263
|
+
}
|
|
22264
|
+
console.log();
|
|
22265
|
+
};
|
|
22266
|
+
if (type === 'route') {
|
|
22267
|
+
inspectRoute(idStr ?? '');
|
|
22268
|
+
}
|
|
22269
|
+
else {
|
|
22270
|
+
inspectStop(idStr ?? '');
|
|
22271
|
+
}
|
|
22272
|
+
this.displayPrompt();
|
|
22273
|
+
},
|
|
22274
|
+
});
|
|
21280
22275
|
};
|
|
21281
22276
|
|
|
21282
22277
|
const program = new Command();
|
|
@@ -21296,7 +22291,7 @@ program
|
|
|
21296
22291
|
.option('-s, --stopsOutputPath <path>', 'Path to output stops file', '/tmp/stops')
|
|
21297
22292
|
.option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
|
|
21298
22293
|
.option('-v, --verbose', 'Verbose mode', false)
|
|
21299
|
-
.action((gtfsPath, options) =>
|
|
22294
|
+
.action(async (gtfsPath, options) => {
|
|
21300
22295
|
if (options.verbose) {
|
|
21301
22296
|
log.setDefaultLevel(log.levels.INFO);
|
|
21302
22297
|
}
|
|
@@ -21304,11 +22299,11 @@ program
|
|
|
21304
22299
|
log.setDefaultLevel(log.levels.ERROR);
|
|
21305
22300
|
}
|
|
21306
22301
|
const parser = new GtfsParser(gtfsPath, profiles[options.profileName]);
|
|
21307
|
-
const stopsIndex =
|
|
22302
|
+
const stopsIndex = await parser.parseStops();
|
|
21308
22303
|
fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
|
|
21309
|
-
const timetable =
|
|
22304
|
+
const timetable = await parser.parseTimetable(new Date(options.date));
|
|
21310
22305
|
fs.writeFileSync(options.timetableOutputPath, timetable.serialize());
|
|
21311
|
-
})
|
|
22306
|
+
});
|
|
21312
22307
|
program
|
|
21313
22308
|
.command('parse-stops')
|
|
21314
22309
|
.description('Parse a GTFS feed and output a stops file.')
|
|
@@ -21316,7 +22311,7 @@ program
|
|
|
21316
22311
|
.option('-s, --outputPath <path>', 'Path to output stops file', '/tmp/stops')
|
|
21317
22312
|
.option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
|
|
21318
22313
|
.option('-v, --verbose', 'Verbose mode', false)
|
|
21319
|
-
.action((gtfsPath, options) =>
|
|
22314
|
+
.action(async (gtfsPath, options) => {
|
|
21320
22315
|
if (options.verbose) {
|
|
21321
22316
|
log.setDefaultLevel(log.levels.INFO);
|
|
21322
22317
|
}
|
|
@@ -21324,9 +22319,9 @@ program
|
|
|
21324
22319
|
log.setDefaultLevel(log.levels.ERROR);
|
|
21325
22320
|
}
|
|
21326
22321
|
const parser = new GtfsParser(gtfsPath, profiles[options.profileName]);
|
|
21327
|
-
const stopsIndex =
|
|
22322
|
+
const stopsIndex = await parser.parseStops();
|
|
21328
22323
|
fs.writeFileSync(options.stopsOutputPath, stopsIndex.serialize());
|
|
21329
|
-
})
|
|
22324
|
+
});
|
|
21330
22325
|
program
|
|
21331
22326
|
.command('repl')
|
|
21332
22327
|
.description('Find stops matching a textual query')
|