minotor 1.0.7 → 2.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/CHANGELOG.md +9 -3
- package/README.md +3 -2
- package/dist/cli.mjs +604 -531
- package/dist/cli.mjs.map +1 -1
- package/dist/gtfs/stops.d.ts +19 -5
- package/dist/gtfs/transfers.d.ts +5 -4
- package/dist/gtfs/trips.d.ts +7 -5
- package/dist/gtfs/utils.d.ts +7 -8
- package/dist/parser.cjs.js +569 -501
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +569 -501
- 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 +3 -3
- 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__/route.test.d.ts +1 -0
- package/dist/routing/query.d.ts +7 -7
- package/dist/routing/result.d.ts +3 -3
- package/dist/routing/route.d.ts +1 -0
- package/dist/stops/proto/stops.d.ts +5 -4
- package/dist/stops/stops.d.ts +10 -1
- package/dist/stops/stopsIndex.d.ts +21 -4
- package/dist/timetable/proto/timetable.d.ts +21 -18
- package/dist/timetable/timetable.d.ts +38 -14
- package/package.json +4 -3
- package/src/cli/repl.ts +13 -10
- package/src/gtfs/__tests__/parser.test.ts +50 -579
- package/src/gtfs/__tests__/stops.test.ts +181 -112
- package/src/gtfs/__tests__/transfers.test.ts +170 -12
- package/src/gtfs/__tests__/trips.test.ts +212 -141
- package/src/gtfs/__tests__/utils.test.ts +4 -4
- package/src/gtfs/parser.ts +22 -13
- package/src/gtfs/stops.ts +63 -28
- package/src/gtfs/transfers.ts +14 -6
- package/src/gtfs/trips.ts +110 -47
- package/src/gtfs/utils.ts +11 -11
- package/src/router.ts +2 -4
- package/src/routing/__tests__/route.test.ts +112 -0
- package/src/routing/__tests__/router.test.ts +234 -244
- package/src/routing/query.ts +7 -7
- package/src/routing/result.ts +9 -6
- package/src/routing/route.ts +11 -0
- package/src/routing/router.ts +26 -24
- package/src/stops/__tests__/io.test.ts +9 -8
- package/src/stops/__tests__/stopFinder.test.ts +45 -36
- package/src/stops/io.ts +8 -5
- package/src/stops/proto/stops.proto +8 -7
- package/src/stops/proto/stops.ts +68 -38
- package/src/stops/stops.ts +13 -1
- package/src/stops/stopsIndex.ts +50 -7
- package/src/timetable/__tests__/io.test.ts +40 -49
- package/src/timetable/__tests__/timetable.test.ts +50 -58
- package/src/timetable/io.ts +69 -56
- package/src/timetable/proto/timetable.proto +22 -17
- package/src/timetable/proto/timetable.ts +94 -184
- package/src/timetable/timetable.ts +62 -29
package/src/routing/query.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceStopId } from '../stops/stops.js';
|
|
2
2
|
import { Duration } from '../timetable/duration.js';
|
|
3
3
|
import { Time } from '../timetable/time.js';
|
|
4
4
|
import { ALL_TRANSPORT_MODES, RouteType } from '../timetable/timetable.js';
|
|
5
5
|
|
|
6
6
|
export class Query {
|
|
7
|
-
from:
|
|
8
|
-
to:
|
|
7
|
+
from: SourceStopId;
|
|
8
|
+
to: SourceStopId[];
|
|
9
9
|
departureTime: Time;
|
|
10
10
|
lastDepartureTime?: Time;
|
|
11
11
|
options: {
|
|
@@ -22,8 +22,8 @@ export class Query {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
static Builder = class {
|
|
25
|
-
fromValue!:
|
|
26
|
-
toValue:
|
|
25
|
+
fromValue!: SourceStopId;
|
|
26
|
+
toValue: SourceStopId[] = [];
|
|
27
27
|
departureTimeValue!: Time;
|
|
28
28
|
// lastDepartureTimeValue?: Date;
|
|
29
29
|
// via: StopId[] = [];
|
|
@@ -37,12 +37,12 @@ export class Query {
|
|
|
37
37
|
transportModes: ALL_TRANSPORT_MODES,
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
from(from:
|
|
40
|
+
from(from: SourceStopId): this {
|
|
41
41
|
this.fromValue = from;
|
|
42
42
|
return this;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
to(to:
|
|
45
|
+
to(to: SourceStopId | SourceStopId[]): this {
|
|
46
46
|
this.toValue = Array.isArray(to) ? to : [to];
|
|
47
47
|
return this;
|
|
48
48
|
}
|
package/src/routing/result.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StopId } from '../stops/stops.js';
|
|
1
|
+
import { SourceStopId, StopId } from '../stops/stops.js';
|
|
2
2
|
import { StopsIndex } from '../stops/stopsIndex.js';
|
|
3
3
|
import { Query } from './query.js';
|
|
4
4
|
import { Leg, Route } from './route.js';
|
|
@@ -29,7 +29,7 @@ export class Result {
|
|
|
29
29
|
* @param to The destination stop. Defaults to the destination of the original query.
|
|
30
30
|
* @returns a route to the destination stop if it exists.
|
|
31
31
|
*/
|
|
32
|
-
bestRoute(to?:
|
|
32
|
+
bestRoute(to?: SourceStopId | SourceStopId[]): Route | undefined {
|
|
33
33
|
const destinationList = Array.isArray(to) ? to : to ? [to] : this.query.to;
|
|
34
34
|
const destinations = destinationList.flatMap((destination) =>
|
|
35
35
|
this.stopsIndex.equivalentStops(destination),
|
|
@@ -38,13 +38,13 @@ export class Result {
|
|
|
38
38
|
let fastestDestination: StopId | undefined = undefined;
|
|
39
39
|
let fastestTime: ReachingTime | undefined = undefined;
|
|
40
40
|
for (const destination of destinations) {
|
|
41
|
-
const arrivalTime = this.earliestArrivals.get(destination);
|
|
41
|
+
const arrivalTime = this.earliestArrivals.get(destination.id);
|
|
42
42
|
if (arrivalTime !== undefined) {
|
|
43
43
|
if (
|
|
44
44
|
fastestTime === undefined ||
|
|
45
45
|
arrivalTime.time.toSeconds() < fastestTime.time.toSeconds()
|
|
46
46
|
) {
|
|
47
|
-
fastestDestination = destination;
|
|
47
|
+
fastestDestination = destination.id;
|
|
48
48
|
fastestTime = arrivalTime;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -81,7 +81,10 @@ export class Result {
|
|
|
81
81
|
* @param maxTransfers The optional maximum number of transfers allowed.
|
|
82
82
|
* @returns The arrival time if the target stop is reachable, otherwise undefined.
|
|
83
83
|
*/
|
|
84
|
-
arrivalAt(
|
|
84
|
+
arrivalAt(
|
|
85
|
+
stop: SourceStopId,
|
|
86
|
+
maxTransfers?: number,
|
|
87
|
+
): ReachingTime | undefined {
|
|
85
88
|
const equivalentStops = this.stopsIndex.equivalentStops(stop);
|
|
86
89
|
let earliestArrival: ReachingTime | undefined = undefined;
|
|
87
90
|
|
|
@@ -92,7 +95,7 @@ export class Result {
|
|
|
92
95
|
: this.earliestArrivals;
|
|
93
96
|
|
|
94
97
|
for (const equivalentStop of equivalentStops) {
|
|
95
|
-
const arrivalTime = relevantArrivals.get(equivalentStop);
|
|
98
|
+
const arrivalTime = relevantArrivals.get(equivalentStop.id);
|
|
96
99
|
if (arrivalTime !== undefined) {
|
|
97
100
|
if (
|
|
98
101
|
earliestArrival === undefined ||
|
package/src/routing/route.ts
CHANGED
|
@@ -3,6 +3,12 @@ import { Duration } from '../timetable/duration.js';
|
|
|
3
3
|
import { Time } from '../timetable/time.js';
|
|
4
4
|
import { ServiceRoute, TransferType } from '../timetable/timetable.js';
|
|
5
5
|
|
|
6
|
+
export type PickUpDropOffType =
|
|
7
|
+
| 'REGULAR'
|
|
8
|
+
| 'NOT_AVAILABLE'
|
|
9
|
+
| 'MUST_PHONE_AGENCY'
|
|
10
|
+
| 'MUST_COORDINATE_WITH_DRIVER';
|
|
11
|
+
|
|
6
12
|
export type BaseLeg = {
|
|
7
13
|
from: Stop;
|
|
8
14
|
to: Stop;
|
|
@@ -17,6 +23,11 @@ export type VehicleLeg = BaseLeg & {
|
|
|
17
23
|
route: ServiceRoute;
|
|
18
24
|
departureTime: Time;
|
|
19
25
|
arrivalTime: Time;
|
|
26
|
+
// TODO support pick up and drop off types
|
|
27
|
+
/*
|
|
28
|
+
pickUpType: PickUpDropOffType;
|
|
29
|
+
dropOffType: PickUpDropOffType;
|
|
30
|
+
*/
|
|
20
31
|
};
|
|
21
32
|
|
|
22
33
|
export type Leg = Transfer | VehicleLeg;
|
package/src/routing/router.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { StopId } from '../stops/stops.js';
|
|
|
3
3
|
import { StopsIndex } from '../stops/stopsIndex.js';
|
|
4
4
|
import { Duration } from '../timetable/duration.js';
|
|
5
5
|
import { Time } from '../timetable/time.js';
|
|
6
|
-
import { Timetable } from '../timetable/timetable.js';
|
|
6
|
+
import { NOT_AVAILABLE, Timetable } from '../timetable/timetable.js';
|
|
7
7
|
import { Query } from './query.js';
|
|
8
8
|
import { Result } from './result.js';
|
|
9
9
|
import { Leg } from './route.js';
|
|
@@ -113,16 +113,16 @@ export class Router {
|
|
|
113
113
|
const markedStops = new Set<StopId>();
|
|
114
114
|
|
|
115
115
|
for (const originStop of origins) {
|
|
116
|
-
markedStops.add(originStop);
|
|
117
|
-
earliestArrivals.set(originStop, {
|
|
116
|
+
markedStops.add(originStop.id);
|
|
117
|
+
earliestArrivals.set(originStop.id, {
|
|
118
118
|
time: departureTime,
|
|
119
119
|
legNumber: 0,
|
|
120
|
-
origin: originStop,
|
|
120
|
+
origin: originStop.id,
|
|
121
121
|
});
|
|
122
|
-
earliestArrivalsWithoutAnyLeg.set(originStop, {
|
|
122
|
+
earliestArrivalsWithoutAnyLeg.set(originStop.id, {
|
|
123
123
|
time: departureTime,
|
|
124
124
|
legNumber: 0,
|
|
125
|
-
origin: originStop,
|
|
125
|
+
origin: originStop.id,
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
// on the first round we need to first consider transfers to discover all possible route origins
|
|
@@ -150,13 +150,17 @@ export class Router {
|
|
|
150
150
|
const route = this.timetable.getRoute(routeId)!;
|
|
151
151
|
let currentTrip: CurrentTrip | undefined = undefined;
|
|
152
152
|
const hopOnIndex = route.stopIndices.get(hopOnStop)!;
|
|
153
|
-
// for each
|
|
153
|
+
// for each stop in the route starting with the hop-on one
|
|
154
154
|
for (let i = hopOnIndex; i < route.stops.length; i++) {
|
|
155
155
|
const currentStop = route.stops[i]!;
|
|
156
156
|
const stopNumbers = route.stops.length;
|
|
157
157
|
if (currentTrip !== undefined) {
|
|
158
|
-
const
|
|
159
|
-
|
|
158
|
+
const currentArrivalIndex =
|
|
159
|
+
(currentTrip.trip * stopNumbers + i) * 2;
|
|
160
|
+
const currentArrivalTime = Time.fromSeconds(
|
|
161
|
+
route.stopTimes[currentArrivalIndex]!,
|
|
162
|
+
);
|
|
163
|
+
const currentDropOffType = route.pickUpDropOffTypes[i * 2 + 1];
|
|
160
164
|
const earliestArrivalAtCurrentStop =
|
|
161
165
|
earliestArrivals.get(currentStop)?.time ?? UNREACHED;
|
|
162
166
|
let arrivalToImprove = earliestArrivalAtCurrentStop;
|
|
@@ -166,7 +170,7 @@ export class Router {
|
|
|
166
170
|
// should compare to the earliest arrival at any of them
|
|
167
171
|
for (const destinationStop of destinations) {
|
|
168
172
|
const earliestArrivalAtDestination =
|
|
169
|
-
earliestArrivals.get(destinationStop)?.time ?? UNREACHED;
|
|
173
|
+
earliestArrivals.get(destinationStop.id)?.time ?? UNREACHED;
|
|
170
174
|
earliestArrivalsAtDestinations.push(
|
|
171
175
|
earliestArrivalAtDestination,
|
|
172
176
|
);
|
|
@@ -180,19 +184,19 @@ export class Router {
|
|
|
180
184
|
);
|
|
181
185
|
}
|
|
182
186
|
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
arrivalToImprove.toSeconds()
|
|
187
|
+
currentDropOffType !== NOT_AVAILABLE &&
|
|
188
|
+
currentArrivalTime.toSeconds() < arrivalToImprove.toSeconds()
|
|
186
189
|
) {
|
|
187
190
|
const bestHopOnStopIndex = route.stopIndices.get(
|
|
188
191
|
currentTrip.bestHopOnStop,
|
|
189
192
|
)!;
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
]
|
|
193
|
+
const bestHopOnStopDepartureIndex =
|
|
194
|
+
currentTrip.trip * stopNumbers * 2 + bestHopOnStopIndex * 2 + 1;
|
|
195
|
+
const bestHopOnDepartureTime = Time.fromSeconds(
|
|
196
|
+
route.stopTimes[bestHopOnStopDepartureIndex]!,
|
|
197
|
+
);
|
|
194
198
|
arrivalsAtCurrentRound.set(currentStop, {
|
|
195
|
-
time:
|
|
199
|
+
time: currentArrivalTime,
|
|
196
200
|
legNumber: round,
|
|
197
201
|
origin: currentTrip.origin,
|
|
198
202
|
leg: {
|
|
@@ -200,13 +204,13 @@ export class Router {
|
|
|
200
204
|
currentTrip.bestHopOnStop,
|
|
201
205
|
)!,
|
|
202
206
|
to: this.stopsIndex.findStopById(currentStop)!,
|
|
203
|
-
departureTime:
|
|
204
|
-
arrivalTime:
|
|
207
|
+
departureTime: bestHopOnDepartureTime,
|
|
208
|
+
arrivalTime: currentArrivalTime,
|
|
205
209
|
route: this.timetable.getServiceRoute(route.serviceRouteId)!,
|
|
206
210
|
},
|
|
207
211
|
});
|
|
208
212
|
earliestArrivals.set(currentStop, {
|
|
209
|
-
time:
|
|
213
|
+
time: currentArrivalTime,
|
|
210
214
|
legNumber: round,
|
|
211
215
|
origin: currentTrip.origin,
|
|
212
216
|
});
|
|
@@ -221,9 +225,7 @@ export class Router {
|
|
|
221
225
|
earliestArrivalOnPreviousRound !== undefined &&
|
|
222
226
|
(currentTrip === undefined ||
|
|
223
227
|
earliestArrivalOnPreviousRound.toSeconds() <=
|
|
224
|
-
route.stopTimes[
|
|
225
|
-
currentTrip.trip * stopNumbers + i
|
|
226
|
-
]!.departure.toSeconds())
|
|
228
|
+
route.stopTimes[(currentTrip.trip * stopNumbers + i) * 2]!)
|
|
227
229
|
) {
|
|
228
230
|
const earliestTrip = this.timetable.findEarliestTrip(
|
|
229
231
|
route,
|
|
@@ -7,33 +7,34 @@ import { StopsMap } from '../stops.js';
|
|
|
7
7
|
describe('stops io', () => {
|
|
8
8
|
const stopsMap: StopsMap = new Map([
|
|
9
9
|
[
|
|
10
|
-
|
|
10
|
+
1,
|
|
11
11
|
{
|
|
12
|
-
id:
|
|
12
|
+
id: 1,
|
|
13
|
+
sourceStopId: 'stop1',
|
|
13
14
|
name: 'Stop 1',
|
|
14
15
|
lat: 40.712776,
|
|
15
16
|
lon: -74.005974,
|
|
16
|
-
children: [
|
|
17
|
-
parent:
|
|
17
|
+
children: [2],
|
|
18
|
+
parent: 3,
|
|
18
19
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
19
20
|
platform: 'Platform 1',
|
|
20
21
|
},
|
|
21
22
|
],
|
|
22
23
|
[
|
|
23
|
-
|
|
24
|
+
2,
|
|
24
25
|
{
|
|
25
|
-
id:
|
|
26
|
+
id: 2,
|
|
27
|
+
sourceStopId: 'stop2',
|
|
26
28
|
name: 'Stop 2',
|
|
27
29
|
lat: 34.052235,
|
|
28
30
|
lon: -118.243683,
|
|
29
31
|
children: [],
|
|
30
|
-
parent:
|
|
32
|
+
parent: 1,
|
|
31
33
|
locationType: 'STATION',
|
|
32
34
|
platform: 'Platform 2',
|
|
33
35
|
},
|
|
34
36
|
],
|
|
35
37
|
]);
|
|
36
|
-
|
|
37
38
|
it('should serialize and deserialize stops correctly', () => {
|
|
38
39
|
const serializedData = serializeStopsMap(stopsMap);
|
|
39
40
|
const deserializedStopsMap = deserializeStopsMap(serializedData);
|
|
@@ -5,9 +5,10 @@ import { StopsMap } from '../stops.js';
|
|
|
5
5
|
import { StopsIndex } from '../stopsIndex.js';
|
|
6
6
|
const mockStops: StopsMap = new Map([
|
|
7
7
|
[
|
|
8
|
-
|
|
8
|
+
1,
|
|
9
9
|
{
|
|
10
|
-
id:
|
|
10
|
+
id: 1,
|
|
11
|
+
sourceStopId: '8587255',
|
|
11
12
|
name: 'Fribourg, Tilleul/Cathédrale',
|
|
12
13
|
lat: 46.8061375857565,
|
|
13
14
|
lon: 7.16145029437328,
|
|
@@ -16,9 +17,10 @@ const mockStops: StopsMap = new Map([
|
|
|
16
17
|
},
|
|
17
18
|
],
|
|
18
19
|
[
|
|
19
|
-
|
|
20
|
+
2,
|
|
20
21
|
{
|
|
21
|
-
id:
|
|
22
|
+
id: 2,
|
|
23
|
+
sourceStopId: '8592383',
|
|
22
24
|
name: 'Fribourg, Neuveville/Court-Ch.',
|
|
23
25
|
lat: 46.8042990960992,
|
|
24
26
|
lon: 7.16060587800609,
|
|
@@ -27,9 +29,10 @@ const mockStops: StopsMap = new Map([
|
|
|
27
29
|
},
|
|
28
30
|
],
|
|
29
31
|
[
|
|
30
|
-
|
|
32
|
+
3,
|
|
31
33
|
{
|
|
32
|
-
id:
|
|
34
|
+
id: 3,
|
|
35
|
+
sourceStopId: '8592386',
|
|
33
36
|
name: 'Fribourg, Petit-St-Jean',
|
|
34
37
|
lat: 46.8035550740648,
|
|
35
38
|
lon: 7.16806189486532,
|
|
@@ -38,50 +41,54 @@ const mockStops: StopsMap = new Map([
|
|
|
38
41
|
},
|
|
39
42
|
],
|
|
40
43
|
[
|
|
41
|
-
|
|
44
|
+
4,
|
|
42
45
|
{
|
|
43
|
-
id:
|
|
46
|
+
id: 4,
|
|
47
|
+
sourceStopId: 'Parent8504100',
|
|
44
48
|
name: 'Fribourg/Freiburg',
|
|
45
49
|
lat: 46.8031492395272,
|
|
46
50
|
lon: 7.15104780338173,
|
|
47
|
-
children: [
|
|
51
|
+
children: [5, 6, 7],
|
|
48
52
|
locationType: 'STATION',
|
|
49
53
|
},
|
|
50
54
|
],
|
|
51
55
|
[
|
|
52
|
-
|
|
56
|
+
5,
|
|
53
57
|
{
|
|
54
|
-
id:
|
|
58
|
+
id: 5,
|
|
59
|
+
sourceStopId: '8504100:0:1',
|
|
55
60
|
name: 'Fribourg/Freiburg',
|
|
56
61
|
lat: 46.8031492395272,
|
|
57
62
|
lon: 7.15104780338173,
|
|
58
63
|
children: [],
|
|
59
64
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
60
|
-
parent:
|
|
65
|
+
parent: 4,
|
|
61
66
|
},
|
|
62
67
|
],
|
|
63
68
|
[
|
|
64
|
-
|
|
69
|
+
6,
|
|
65
70
|
{
|
|
66
|
-
id:
|
|
71
|
+
id: 6,
|
|
72
|
+
sourceStopId: '8504100:0:1AB',
|
|
67
73
|
name: 'Fribourg/Freiburg',
|
|
68
74
|
lat: 46.8031492395272,
|
|
69
75
|
lon: 7.15104780338173,
|
|
70
76
|
children: [],
|
|
71
77
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
72
|
-
parent:
|
|
78
|
+
parent: 4,
|
|
73
79
|
},
|
|
74
80
|
],
|
|
75
81
|
[
|
|
76
|
-
|
|
82
|
+
7,
|
|
77
83
|
{
|
|
78
|
-
id:
|
|
84
|
+
id: 7,
|
|
85
|
+
sourceStopId: '8504100:0:2',
|
|
79
86
|
name: 'Fribourg/Freiburg',
|
|
80
87
|
lat: 46.8031492395272,
|
|
81
88
|
lon: 7.15104780338173,
|
|
82
89
|
children: [],
|
|
83
90
|
locationType: 'SIMPLE_STOP_OR_PLATFORM',
|
|
84
|
-
parent:
|
|
91
|
+
parent: 4,
|
|
85
92
|
},
|
|
86
93
|
],
|
|
87
94
|
]);
|
|
@@ -98,25 +105,25 @@ describe('StopFinder', () => {
|
|
|
98
105
|
const results = stopFinder.findStopsByName(
|
|
99
106
|
'Fribourg, Tilleul/Cathédrale',
|
|
100
107
|
);
|
|
101
|
-
assert.strictEqual(results[0]?.id,
|
|
108
|
+
assert.strictEqual(results[0]?.id, 1);
|
|
102
109
|
});
|
|
103
110
|
|
|
104
111
|
it('should not include children stops', () => {
|
|
105
112
|
const results = stopFinder.findStopsByName('Fribourg/Freiburg', 2);
|
|
106
|
-
assert.strictEqual(results[0]?.id,
|
|
107
|
-
assert.strictEqual(results[1]?.id,
|
|
113
|
+
assert.strictEqual(results[0]?.id, 4);
|
|
114
|
+
assert.strictEqual(results[1]?.id, 1);
|
|
108
115
|
});
|
|
109
116
|
|
|
110
117
|
it('should find stops by partial name', () => {
|
|
111
118
|
const results = stopFinder.findStopsByName('Cathédrale');
|
|
112
119
|
assert.strictEqual(results.length, 1);
|
|
113
|
-
assert.strictEqual(results[0]?.id,
|
|
120
|
+
assert.strictEqual(results[0]?.id, 1);
|
|
114
121
|
});
|
|
115
122
|
|
|
116
123
|
it('should find stops by name with accents', () => {
|
|
117
124
|
const results = stopFinder.findStopsByName('Cathedrale');
|
|
118
125
|
assert.strictEqual(results.length, 1);
|
|
119
|
-
assert.strictEqual(results[0]?.id,
|
|
126
|
+
assert.strictEqual(results[0]?.id, 1);
|
|
120
127
|
});
|
|
121
128
|
|
|
122
129
|
it('should return an empty array if no stops match the query', () => {
|
|
@@ -129,22 +136,22 @@ describe('StopFinder', () => {
|
|
|
129
136
|
it('should find stops by geographic location', () => {
|
|
130
137
|
const results = stopFinder.findStopsByLocation(46.8061, 7.1614, 1);
|
|
131
138
|
assert.strictEqual(results.length, 1);
|
|
132
|
-
assert.strictEqual(results[0]?.id,
|
|
139
|
+
assert.strictEqual(results[0]?.id, 1);
|
|
133
140
|
});
|
|
134
141
|
|
|
135
142
|
it('should find multiple stops within the radius', () => {
|
|
136
143
|
const results = stopFinder.findStopsByLocation(46.8, 7.16, 10, 0.75);
|
|
137
144
|
assert.strictEqual(results.length, 3);
|
|
138
|
-
assert.strictEqual(results[0]?.id,
|
|
139
|
-
assert.strictEqual(results[1]?.id,
|
|
140
|
-
assert.strictEqual(results[2]?.id,
|
|
145
|
+
assert.strictEqual(results[0]?.id, 2);
|
|
146
|
+
assert.strictEqual(results[1]?.id, 1);
|
|
147
|
+
assert.strictEqual(results[2]?.id, 3);
|
|
141
148
|
});
|
|
142
149
|
|
|
143
150
|
it('should find the N closest stops', () => {
|
|
144
151
|
const results = stopFinder.findStopsByLocation(46.8, 7.16, 2, 10);
|
|
145
152
|
assert.strictEqual(results.length, 2);
|
|
146
|
-
assert.strictEqual(results[0]?.id,
|
|
147
|
-
assert.strictEqual(results[1]?.id,
|
|
153
|
+
assert.strictEqual(results[0]?.id, 2);
|
|
154
|
+
assert.strictEqual(results[1]?.id, 1);
|
|
148
155
|
});
|
|
149
156
|
|
|
150
157
|
it('should return an empty array if no stops are within the radius', () => {
|
|
@@ -165,20 +172,22 @@ describe('StopFinder', () => {
|
|
|
165
172
|
describe('equivalentStops', () => {
|
|
166
173
|
it('should find equivalent stops for a given stop ID', () => {
|
|
167
174
|
const equivalentStops = stopFinder.equivalentStops('8504100:0:1');
|
|
168
|
-
assert.deepStrictEqual(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
]);
|
|
175
|
+
assert.deepStrictEqual(
|
|
176
|
+
equivalentStops.map((stop) => stop.id),
|
|
177
|
+
[5, 6, 7],
|
|
178
|
+
);
|
|
173
179
|
});
|
|
174
180
|
|
|
175
181
|
it('should return the same stop ID in an array if no equivalents', () => {
|
|
176
182
|
const equivalentStops = stopFinder.equivalentStops('8587255');
|
|
177
|
-
assert.deepStrictEqual(
|
|
183
|
+
assert.deepStrictEqual(
|
|
184
|
+
equivalentStops.map((stop) => stop.id),
|
|
185
|
+
[1],
|
|
186
|
+
);
|
|
178
187
|
});
|
|
179
188
|
|
|
180
189
|
it('should return an empty array for non-existent stop ID', () => {
|
|
181
|
-
const equivalentStops = stopFinder.equivalentStops('
|
|
190
|
+
const equivalentStops = stopFinder.equivalentStops('999');
|
|
182
191
|
assert.deepStrictEqual(equivalentStops, []);
|
|
183
192
|
});
|
|
184
193
|
});
|
package/src/stops/io.ts
CHANGED
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
Stop as ProtoStop,
|
|
4
4
|
StopsMap as ProtoStopsMap,
|
|
5
5
|
} from './proto/stops.js';
|
|
6
|
-
import { LocationType, Stop,
|
|
6
|
+
import { LocationType, Stop, StopsMap } from './stops.js';
|
|
7
7
|
|
|
8
|
-
const CURRENT_VERSION = '0.0.
|
|
8
|
+
const CURRENT_VERSION = '0.0.2';
|
|
9
9
|
const serializeStop = (stop: Stop): ProtoStop => {
|
|
10
10
|
return {
|
|
11
11
|
name: stop.name,
|
|
12
|
+
sourceStopId: stop.sourceStopId,
|
|
12
13
|
lat: stop.lat,
|
|
13
14
|
lon: stop.lon,
|
|
14
15
|
children: stop.children,
|
|
@@ -24,16 +25,17 @@ export const serializeStopsMap = (stopsMap: StopsMap): ProtoStopsMap => {
|
|
|
24
25
|
stops: {},
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
stopsMap.forEach((value: Stop, key:
|
|
28
|
+
stopsMap.forEach((value: Stop, key: number) => {
|
|
28
29
|
protoStopsMap.stops[key] = serializeStop(value);
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
return protoStopsMap;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
const deserializeStop = (stopId:
|
|
35
|
+
const deserializeStop = (stopId: number, protoStop: ProtoStop): Stop => {
|
|
35
36
|
return {
|
|
36
37
|
id: stopId,
|
|
38
|
+
sourceStopId: protoStop.sourceStopId,
|
|
37
39
|
name: protoStop.name,
|
|
38
40
|
lat: protoStop.lat,
|
|
39
41
|
lon: protoStop.lon,
|
|
@@ -51,7 +53,8 @@ export const deserializeStopsMap = (protoStopsMap: ProtoStopsMap): StopsMap => {
|
|
|
51
53
|
const stopsMap: StopsMap = new Map();
|
|
52
54
|
|
|
53
55
|
Object.entries(protoStopsMap.stops).forEach(([key, value]) => {
|
|
54
|
-
|
|
56
|
+
const intKey = parseInt(key, 10);
|
|
57
|
+
stopsMap.set(intKey, deserializeStop(intKey, value));
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
return stopsMap;
|
|
@@ -12,15 +12,16 @@ enum LocationType {
|
|
|
12
12
|
|
|
13
13
|
message Stop {
|
|
14
14
|
string name = 1;
|
|
15
|
-
|
|
16
|
-
optional double
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
string sourceStopId = 2;
|
|
16
|
+
optional double lat = 3;
|
|
17
|
+
optional double lon = 4;
|
|
18
|
+
repeated uint32 children = 5;
|
|
19
|
+
optional uint32 parent = 6;
|
|
20
|
+
LocationType locationType = 7;
|
|
21
|
+
optional string platform = 8;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
message StopsMap {
|
|
24
25
|
string version = 1;
|
|
25
|
-
map<
|
|
26
|
+
map<uint32, Stop> stops = 2;
|
|
26
27
|
}
|