minotor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cspell.json +43 -0
- package/.czrc +3 -0
- package/.editorconfig +10 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +4 -0
- package/.github/workflows/minotor.yml +85 -0
- package/.prettierrc +7 -0
- package/.releaserc.json +27 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/bundle.cjs.js +16507 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/bundle.esm.js +16496 -0
- package/dist/bundle.esm.js.map +1 -0
- package/dist/bundle.umd.js +2 -0
- package/dist/bundle.umd.js.map +1 -0
- package/dist/cli/__tests__/minotor.test.d.ts +1 -0
- package/dist/cli/minotor.d.ts +5 -0
- package/dist/cli/repl.d.ts +1 -0
- package/dist/cli/utils.d.ts +3 -0
- package/dist/cli.mjs +20504 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/gtfs/__tests__/parser.test.d.ts +1 -0
- package/dist/gtfs/__tests__/routes.test.d.ts +1 -0
- package/dist/gtfs/__tests__/services.test.d.ts +1 -0
- package/dist/gtfs/__tests__/stops.test.d.ts +1 -0
- package/dist/gtfs/__tests__/time.test.d.ts +1 -0
- package/dist/gtfs/__tests__/transfers.test.d.ts +1 -0
- package/dist/gtfs/__tests__/trips.test.d.ts +1 -0
- package/dist/gtfs/__tests__/utils.test.d.ts +1 -0
- package/dist/gtfs/parser.d.ts +34 -0
- package/dist/gtfs/profiles/__tests__/ch.test.d.ts +1 -0
- package/dist/gtfs/profiles/ch.d.ts +2 -0
- package/dist/gtfs/profiles/standard.d.ts +2 -0
- package/dist/gtfs/routes.d.ts +11 -0
- package/dist/gtfs/services.d.ts +19 -0
- package/dist/gtfs/stops.d.ts +20 -0
- package/dist/gtfs/time.d.ts +17 -0
- package/dist/gtfs/transfers.d.ts +22 -0
- package/dist/gtfs/trips.d.ts +26 -0
- package/dist/gtfs/utils.d.ts +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/routing/__tests__/router.test.d.ts +1 -0
- package/dist/routing/plotter.d.ts +11 -0
- package/dist/routing/query.d.ts +35 -0
- package/dist/routing/result.d.ts +28 -0
- package/dist/routing/route.d.ts +25 -0
- package/dist/routing/router.d.ts +33 -0
- package/dist/stops/__tests__/io.test.d.ts +1 -0
- package/dist/stops/__tests__/stopFinder.test.d.ts +1 -0
- package/dist/stops/i18n.d.ts +10 -0
- package/dist/stops/io.d.ts +4 -0
- package/dist/stops/proto/stops.d.ts +53 -0
- package/dist/stops/stops.d.ts +16 -0
- package/dist/stops/stopsIndex.d.ts +52 -0
- package/dist/timetable/__tests__/io.test.d.ts +1 -0
- package/dist/timetable/__tests__/timetable.test.d.ts +1 -0
- package/dist/timetable/duration.d.ts +51 -0
- package/dist/timetable/io.d.ts +8 -0
- package/dist/timetable/proto/timetable.d.ts +122 -0
- package/dist/timetable/time.d.ts +98 -0
- package/dist/timetable/timetable.d.ts +82 -0
- package/dist/umdIndex.d.ts +9 -0
- package/eslint.config.mjs +52 -0
- package/package.json +109 -0
- package/rollup.config.js +44 -0
- package/src/cli/__tests__/minotor.test.ts +23 -0
- package/src/cli/minotor.ts +112 -0
- package/src/cli/repl.ts +200 -0
- package/src/cli/utils.ts +36 -0
- package/src/gtfs/__tests__/parser.test.ts +591 -0
- package/src/gtfs/__tests__/resources/sample-feed/agency.txt +2 -0
- package/src/gtfs/__tests__/resources/sample-feed/calendar.txt +3 -0
- package/src/gtfs/__tests__/resources/sample-feed/calendar_dates.txt +2 -0
- package/src/gtfs/__tests__/resources/sample-feed/fare_attributes.txt +3 -0
- package/src/gtfs/__tests__/resources/sample-feed/fare_rules.txt +5 -0
- package/src/gtfs/__tests__/resources/sample-feed/frequencies.txt +12 -0
- package/src/gtfs/__tests__/resources/sample-feed/routes.txt +6 -0
- package/src/gtfs/__tests__/resources/sample-feed/sample-feed.zip +0 -0
- package/src/gtfs/__tests__/resources/sample-feed/shapes.txt +1 -0
- package/src/gtfs/__tests__/resources/sample-feed/stop_times.txt +34 -0
- package/src/gtfs/__tests__/resources/sample-feed/stops.txt +10 -0
- package/src/gtfs/__tests__/resources/sample-feed/trips.txt +13 -0
- package/src/gtfs/__tests__/resources/sample-feed.zip +0 -0
- package/src/gtfs/__tests__/routes.test.ts +63 -0
- package/src/gtfs/__tests__/services.test.ts +209 -0
- package/src/gtfs/__tests__/stops.test.ts +177 -0
- package/src/gtfs/__tests__/time.test.ts +27 -0
- package/src/gtfs/__tests__/transfers.test.ts +117 -0
- package/src/gtfs/__tests__/trips.test.ts +463 -0
- package/src/gtfs/__tests__/utils.test.ts +13 -0
- package/src/gtfs/parser.ts +154 -0
- package/src/gtfs/profiles/__tests__/ch.test.ts +43 -0
- package/src/gtfs/profiles/ch.ts +70 -0
- package/src/gtfs/profiles/standard.ts +39 -0
- package/src/gtfs/routes.ts +48 -0
- package/src/gtfs/services.ts +98 -0
- package/src/gtfs/stops.ts +112 -0
- package/src/gtfs/time.ts +33 -0
- package/src/gtfs/transfers.ts +102 -0
- package/src/gtfs/trips.ts +228 -0
- package/src/gtfs/utils.ts +42 -0
- package/src/index.ts +28 -0
- package/src/routing/__tests__/router.test.ts +760 -0
- package/src/routing/plotter.ts +70 -0
- package/src/routing/query.ts +74 -0
- package/src/routing/result.ts +108 -0
- package/src/routing/route.ts +94 -0
- package/src/routing/router.ts +262 -0
- package/src/stops/__tests__/io.test.ts +43 -0
- package/src/stops/__tests__/stopFinder.test.ts +185 -0
- package/src/stops/i18n.ts +40 -0
- package/src/stops/io.ts +94 -0
- package/src/stops/proto/stops.proto +26 -0
- package/src/stops/proto/stops.ts +445 -0
- package/src/stops/stops.ts +24 -0
- package/src/stops/stopsIndex.ts +151 -0
- package/src/timetable/__tests__/io.test.ts +175 -0
- package/src/timetable/__tests__/timetable.test.ts +180 -0
- package/src/timetable/duration.ts +85 -0
- package/src/timetable/io.ts +265 -0
- package/src/timetable/proto/timetable.proto +76 -0
- package/src/timetable/proto/timetable.ts +1304 -0
- package/src/timetable/time.ts +192 -0
- package/src/timetable/timetable.ts +286 -0
- package/src/umdIndex.ts +14 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { Duration } from '../duration.js';
|
|
5
|
+
import {
|
|
6
|
+
deserializeRoutesAdjacency,
|
|
7
|
+
deserializeServiceRoutesMap,
|
|
8
|
+
deserializeStopsAdjacency,
|
|
9
|
+
serializeRoutesAdjacency,
|
|
10
|
+
serializeServiceRoutesMap,
|
|
11
|
+
serializeStopsAdjacency,
|
|
12
|
+
} from '../io.js';
|
|
13
|
+
import { Time } from '../time.js';
|
|
14
|
+
import {
|
|
15
|
+
RoutesAdjacency,
|
|
16
|
+
ServiceRoutesMap,
|
|
17
|
+
StopsAdjacency,
|
|
18
|
+
} from '../timetable.js';
|
|
19
|
+
|
|
20
|
+
describe('timetable io', () => {
|
|
21
|
+
const stopsAdjacency: StopsAdjacency = new Map([
|
|
22
|
+
[
|
|
23
|
+
'stop1',
|
|
24
|
+
{
|
|
25
|
+
transfers: [{ destination: 'stop2', type: 'RECOMMENDED' }],
|
|
26
|
+
routes: ['route1'],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
'stop2',
|
|
31
|
+
{
|
|
32
|
+
transfers: [
|
|
33
|
+
{
|
|
34
|
+
destination: 'stop1',
|
|
35
|
+
type: 'GUARANTEED',
|
|
36
|
+
minTransferTime: Duration.fromMinutes(3),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
routes: ['route2'],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
]);
|
|
43
|
+
const routesAdjacency: RoutesAdjacency = new Map([
|
|
44
|
+
[
|
|
45
|
+
'route1',
|
|
46
|
+
{
|
|
47
|
+
stopTimes: [
|
|
48
|
+
{
|
|
49
|
+
arrival: Time.fromSeconds(1000),
|
|
50
|
+
departure: Time.fromSeconds(1010),
|
|
51
|
+
pickUpType: 'REGULAR',
|
|
52
|
+
dropOffType: 'REGULAR',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
stops: ['stop1', 'stop2'],
|
|
56
|
+
stopIndices: new Map([
|
|
57
|
+
['stop1', 0],
|
|
58
|
+
['stop2', 1],
|
|
59
|
+
]),
|
|
60
|
+
serviceRouteId: 'gtfs1',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
'route2',
|
|
65
|
+
{
|
|
66
|
+
stopTimes: [
|
|
67
|
+
{
|
|
68
|
+
arrival: Time.fromSeconds(2000),
|
|
69
|
+
departure: Time.fromSeconds(2010),
|
|
70
|
+
pickUpType: 'REGULAR',
|
|
71
|
+
dropOffType: 'REGULAR',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
stops: ['stop2', 'stop1'],
|
|
75
|
+
stopIndices: new Map([
|
|
76
|
+
['stop2', 0],
|
|
77
|
+
['stop1', 1],
|
|
78
|
+
]),
|
|
79
|
+
serviceRouteId: 'gtfs2',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
]);
|
|
83
|
+
const routes: ServiceRoutesMap = new Map([
|
|
84
|
+
['gtfs1', { type: 'RAIL', name: 'Route 1' }],
|
|
85
|
+
['gtfs2', { type: 'RAIL', name: 'Route 2' }],
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const stopsAdjacencyProto = {
|
|
89
|
+
stops: {
|
|
90
|
+
stop1: {
|
|
91
|
+
transfers: [{ destination: 'stop2', type: 0 }],
|
|
92
|
+
routes: ['route1'],
|
|
93
|
+
},
|
|
94
|
+
stop2: {
|
|
95
|
+
transfers: [
|
|
96
|
+
{
|
|
97
|
+
destination: 'stop1',
|
|
98
|
+
type: 1,
|
|
99
|
+
minTransferTime: 180,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
routes: ['route2'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const routesAdjacencyProto = {
|
|
108
|
+
routes: {
|
|
109
|
+
route1: {
|
|
110
|
+
stopTimes: [
|
|
111
|
+
{
|
|
112
|
+
arrival: 1000,
|
|
113
|
+
departure: 1010,
|
|
114
|
+
pickUpType: undefined,
|
|
115
|
+
dropOffType: undefined,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
stops: ['stop1', 'stop2'],
|
|
119
|
+
serviceRouteId: 'gtfs1',
|
|
120
|
+
},
|
|
121
|
+
route2: {
|
|
122
|
+
stopTimes: [
|
|
123
|
+
{
|
|
124
|
+
arrival: 2000,
|
|
125
|
+
departure: 2010,
|
|
126
|
+
pickUpType: undefined,
|
|
127
|
+
dropOffType: undefined,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
stops: ['stop2', 'stop1'],
|
|
131
|
+
serviceRouteId: 'gtfs2',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const routesProto = {
|
|
137
|
+
routes: {
|
|
138
|
+
gtfs1: { type: 2, name: 'Route 1' },
|
|
139
|
+
gtfs2: { type: 2, name: 'Route 2' },
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
it('should serialize a stops adjacency matrix to a Uint8Array', () => {
|
|
144
|
+
const serializedData = serializeStopsAdjacency(stopsAdjacency);
|
|
145
|
+
assert.deepStrictEqual(serializedData, stopsAdjacencyProto);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should deserialize a Uint8Array to a stops adjacency matrix', () => {
|
|
149
|
+
const serializedData = serializeStopsAdjacency(stopsAdjacency);
|
|
150
|
+
const deserializedData = deserializeStopsAdjacency(serializedData);
|
|
151
|
+
assert.deepStrictEqual(deserializedData, stopsAdjacency);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should serialize a routes adjacency matrix to a Uint8Array', () => {
|
|
155
|
+
const serializedData = serializeRoutesAdjacency(routesAdjacency);
|
|
156
|
+
assert.deepStrictEqual(serializedData, routesAdjacencyProto);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should deserialize a Uint8Array to a routes adjacency matrix', () => {
|
|
160
|
+
const serializedData = serializeRoutesAdjacency(routesAdjacency);
|
|
161
|
+
const deserializedData = deserializeRoutesAdjacency(serializedData);
|
|
162
|
+
assert.deepStrictEqual(deserializedData, routesAdjacency);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should serialize a service route map to a Uint8Array', () => {
|
|
166
|
+
const serializedData = serializeServiceRoutesMap(routes);
|
|
167
|
+
assert.deepStrictEqual(serializedData, routesProto);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should deserialize a Uint8Array to a service route map', () => {
|
|
171
|
+
const serializedData = serializeServiceRoutesMap(routes);
|
|
172
|
+
const deserializedData = deserializeServiceRoutesMap(serializedData);
|
|
173
|
+
assert.deepStrictEqual(deserializedData, routes);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { Duration } from '../duration.js';
|
|
5
|
+
import { Time } from '../time.js';
|
|
6
|
+
import {
|
|
7
|
+
RoutesAdjacency,
|
|
8
|
+
ServiceRoutesMap,
|
|
9
|
+
StopsAdjacency,
|
|
10
|
+
Timetable,
|
|
11
|
+
} from '../timetable.js';
|
|
12
|
+
|
|
13
|
+
describe('timetable io', () => {
|
|
14
|
+
const stopsAdjacency: StopsAdjacency = new Map([
|
|
15
|
+
[
|
|
16
|
+
'stop1',
|
|
17
|
+
{
|
|
18
|
+
transfers: [{ destination: 'stop2', type: 'RECOMMENDED' }],
|
|
19
|
+
routes: ['route1'],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'stop2',
|
|
24
|
+
{
|
|
25
|
+
transfers: [
|
|
26
|
+
{
|
|
27
|
+
destination: 'stop1',
|
|
28
|
+
type: 'GUARANTEED',
|
|
29
|
+
minTransferTime: Duration.fromMinutes(3),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
routes: ['route2'],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
]);
|
|
36
|
+
const routesAdjacency: RoutesAdjacency = new Map([
|
|
37
|
+
[
|
|
38
|
+
'route1',
|
|
39
|
+
{
|
|
40
|
+
stopTimes: [
|
|
41
|
+
{
|
|
42
|
+
arrival: Time.fromHMS(0, 16, 40),
|
|
43
|
+
departure: Time.fromHMS(0, 16, 50),
|
|
44
|
+
pickUpType: 'REGULAR',
|
|
45
|
+
dropOffType: 'REGULAR',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
arrival: Time.fromHMS(0, 33, 20),
|
|
49
|
+
departure: Time.fromHMS(0, 33, 30),
|
|
50
|
+
pickUpType: 'NOT_AVAILABLE',
|
|
51
|
+
dropOffType: 'REGULAR',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
arrival: Time.fromHMS(0, 50, 0),
|
|
55
|
+
departure: Time.fromHMS(0, 50, 10),
|
|
56
|
+
pickUpType: 'REGULAR',
|
|
57
|
+
dropOffType: 'REGULAR',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
arrival: Time.fromHMS(1, 10, 0),
|
|
61
|
+
departure: Time.fromHMS(1, 10, 10),
|
|
62
|
+
pickUpType: 'REGULAR',
|
|
63
|
+
dropOffType: 'REGULAR',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
stops: ['stop1', 'stop2'],
|
|
67
|
+
stopIndices: new Map([
|
|
68
|
+
['stop1', 0],
|
|
69
|
+
['stop2', 1],
|
|
70
|
+
]),
|
|
71
|
+
serviceRouteId: 'gtfs1',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
'route2',
|
|
76
|
+
{
|
|
77
|
+
stopTimes: [
|
|
78
|
+
{
|
|
79
|
+
arrival: Time.fromHMS(1, 6, 40),
|
|
80
|
+
departure: Time.fromHMS(1, 6, 50),
|
|
81
|
+
pickUpType: 'REGULAR',
|
|
82
|
+
dropOffType: 'REGULAR',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
arrival: Time.fromHMS(1, 23, 20),
|
|
86
|
+
departure: Time.fromHMS(1, 23, 30),
|
|
87
|
+
pickUpType: 'REGULAR',
|
|
88
|
+
dropOffType: 'REGULAR',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
stops: ['stop2', 'stop1'],
|
|
92
|
+
stopIndices: new Map([
|
|
93
|
+
['stop2', 0],
|
|
94
|
+
['stop1', 1],
|
|
95
|
+
]),
|
|
96
|
+
serviceRouteId: 'gtfs2',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
]);
|
|
100
|
+
const routes: ServiceRoutesMap = new Map([
|
|
101
|
+
['gtfs1', { type: 'RAIL', name: 'Route 1' }],
|
|
102
|
+
['gtfs2', { type: 'RAIL', name: 'Route 2' }],
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const sampleTimetable: Timetable = new Timetable(
|
|
106
|
+
stopsAdjacency,
|
|
107
|
+
routesAdjacency,
|
|
108
|
+
routes,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it('should serialize a timetable to a Uint8Array', () => {
|
|
112
|
+
const serializedData = sampleTimetable.serialize();
|
|
113
|
+
assert(serializedData instanceof Uint8Array);
|
|
114
|
+
assert(serializedData.length > 0);
|
|
115
|
+
});
|
|
116
|
+
it('should deserialize a Uint8Array to a timetable', () => {
|
|
117
|
+
const serializedData = sampleTimetable.serialize();
|
|
118
|
+
const deserializedTimetable = Timetable.fromData(serializedData);
|
|
119
|
+
assert.deepStrictEqual(deserializedTimetable, sampleTimetable);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should find the earliest trip for stop1 on route1', () => {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
124
|
+
const route = sampleTimetable.getRoute('route1')!;
|
|
125
|
+
const tripIndex = sampleTimetable.findEarliestTrip(route, 'stop1');
|
|
126
|
+
assert.strictEqual(tripIndex, 0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should find the earliest trip for stop1 on route1 after a specific time', () => {
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
131
|
+
const route = sampleTimetable.getRoute('route1')!;
|
|
132
|
+
const afterTime = Time.fromHMS(0, 25, 0);
|
|
133
|
+
const tripIndex = sampleTimetable.findEarliestTrip(
|
|
134
|
+
route,
|
|
135
|
+
'stop1',
|
|
136
|
+
undefined,
|
|
137
|
+
afterTime,
|
|
138
|
+
);
|
|
139
|
+
assert.strictEqual(tripIndex, 1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should return undefined if no valid trip exists after a specific time', () => {
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
144
|
+
const route = sampleTimetable.getRoute('route1')!;
|
|
145
|
+
const afterTime = Time.fromHMS(0, 58, 20);
|
|
146
|
+
const tripIndex = sampleTimetable.findEarliestTrip(
|
|
147
|
+
route,
|
|
148
|
+
'stop1',
|
|
149
|
+
undefined,
|
|
150
|
+
afterTime,
|
|
151
|
+
);
|
|
152
|
+
assert.strictEqual(tripIndex, undefined);
|
|
153
|
+
});
|
|
154
|
+
it('should return undefined if the stop on a trip has pick up not available', () => {
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
156
|
+
const route = sampleTimetable.getRoute('route1')!;
|
|
157
|
+
const tripIndex = sampleTimetable.findEarliestTrip(route, 'stop2');
|
|
158
|
+
assert.strictEqual(tripIndex, 1);
|
|
159
|
+
});
|
|
160
|
+
it('should find reachable routes from a set of stop IDs', () => {
|
|
161
|
+
const fromStops = new Set(['stop1']);
|
|
162
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
163
|
+
assert.strictEqual(reachableRoutes.size, 1);
|
|
164
|
+
assert.strictEqual(reachableRoutes.get('route1'), 'stop1');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should find no reachable routes if starting from a non-existent stop', () => {
|
|
168
|
+
const fromStops = new Set(['non_existent_stop']);
|
|
169
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
|
|
170
|
+
assert.strictEqual(reachableRoutes.size, 0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should find reachable routes filtered by transport modes', () => {
|
|
174
|
+
const fromStops = new Set(['stop1']);
|
|
175
|
+
const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops, [
|
|
176
|
+
'BUS',
|
|
177
|
+
]);
|
|
178
|
+
assert.strictEqual(reachableRoutes.size, 0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class Duration {
|
|
2
|
+
private totalSeconds: number;
|
|
3
|
+
|
|
4
|
+
private constructor(totalSeconds: number) {
|
|
5
|
+
this.totalSeconds = totalSeconds;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a Duration instance from a given number of seconds.
|
|
9
|
+
*
|
|
10
|
+
* @param seconds - The number of seconds for the duration.
|
|
11
|
+
* @returns A Duration instance representing the specified duration.
|
|
12
|
+
*/
|
|
13
|
+
static fromSeconds(seconds: number): Duration {
|
|
14
|
+
return new Duration(seconds);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Duration instance from a given number of minutes.
|
|
18
|
+
*
|
|
19
|
+
* @param minutes - The number of minutes for the duration.
|
|
20
|
+
* @returns A Duration instance representing the specified duration.
|
|
21
|
+
*/
|
|
22
|
+
static fromMinutes(minutes: number): Duration {
|
|
23
|
+
return new Duration(minutes * 60);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets a Duration instance representing zero duration (0 hours, 0 minutes, 0 seconds).
|
|
27
|
+
*
|
|
28
|
+
* @returns A Duration instance representing zero duration.
|
|
29
|
+
*/
|
|
30
|
+
static zero(): Duration {
|
|
31
|
+
return new Duration(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts the duration instance to a string in "HH:MM:SS" format.
|
|
36
|
+
*
|
|
37
|
+
* @returns A string representing the duration.
|
|
38
|
+
*/
|
|
39
|
+
toString(): string {
|
|
40
|
+
const hours = Math.floor(this.totalSeconds / 3600);
|
|
41
|
+
const minutes = Math.floor((this.totalSeconds % 3600) / 60);
|
|
42
|
+
const seconds = this.totalSeconds % 60;
|
|
43
|
+
if (hours > 0) {
|
|
44
|
+
return `${hours.toString().padStart(2, '0')}:${minutes
|
|
45
|
+
.toString()
|
|
46
|
+
.padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
47
|
+
} else {
|
|
48
|
+
return `${minutes.toString().padStart(2, '0')}:${seconds
|
|
49
|
+
.toString()
|
|
50
|
+
.padStart(2, '0')}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Gets the duration as the number of seconds.
|
|
56
|
+
*
|
|
57
|
+
* @returns The duration in seconds.
|
|
58
|
+
*/
|
|
59
|
+
toSeconds(): number {
|
|
60
|
+
return this.totalSeconds;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Adds another Duration to this instance and returns the result as a new Duration.
|
|
65
|
+
*
|
|
66
|
+
* @param other - The other Duration to add.
|
|
67
|
+
* @returns A new Duration instance representing the sum.
|
|
68
|
+
*/
|
|
69
|
+
add(other: Duration): Duration {
|
|
70
|
+
const totalSeconds = this.totalSeconds + other.toSeconds();
|
|
71
|
+
return new Duration(totalSeconds);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Subtracts another Duration from this instance and returns the result as a new Duration.
|
|
76
|
+
* If the result would be negative, it returns a Duration of 0 seconds.
|
|
77
|
+
*
|
|
78
|
+
* @param other - The other Duration to subtract.
|
|
79
|
+
* @returns A new Duration instance representing the difference.
|
|
80
|
+
*/
|
|
81
|
+
subtract(other: Duration): Duration {
|
|
82
|
+
const totalSeconds = Math.max(0, this.totalSeconds - other.toSeconds());
|
|
83
|
+
return new Duration(totalSeconds);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Duration } from './duration.js';
|
|
2
|
+
import {
|
|
3
|
+
PickUpDropOffType as ProtoPickUpDropOffType,
|
|
4
|
+
RoutesAdjacency as ProtoRoutesAdjacency,
|
|
5
|
+
RouteType as ProtoRouteType,
|
|
6
|
+
ServiceRoutesMap as ProtoServiceRoutesMap,
|
|
7
|
+
StopsAdjacency as ProtoStopsAdjacency,
|
|
8
|
+
Transfer as ProtoTransfer,
|
|
9
|
+
TransferType as ProtoTransferType,
|
|
10
|
+
} from './proto/timetable.js';
|
|
11
|
+
import { Time } from './time.js';
|
|
12
|
+
import {
|
|
13
|
+
PickUpDropOffType,
|
|
14
|
+
Route,
|
|
15
|
+
RoutesAdjacency,
|
|
16
|
+
RouteType,
|
|
17
|
+
ServiceRoutesMap,
|
|
18
|
+
StopsAdjacency,
|
|
19
|
+
Transfer,
|
|
20
|
+
TransferType,
|
|
21
|
+
} from './timetable.js';
|
|
22
|
+
|
|
23
|
+
export const serializeStopsAdjacency = (
|
|
24
|
+
stopsAdjacency: StopsAdjacency,
|
|
25
|
+
): ProtoStopsAdjacency => {
|
|
26
|
+
const protoStopsAdjacency: ProtoStopsAdjacency = {
|
|
27
|
+
stops: {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
stopsAdjacency.forEach(
|
|
31
|
+
(value: { transfers: Transfer[]; routes: string[] }, key: string) => {
|
|
32
|
+
protoStopsAdjacency.stops[key] = {
|
|
33
|
+
transfers: value.transfers.map((transfer) => ({
|
|
34
|
+
destination: transfer.destination,
|
|
35
|
+
type: serializeTransferType(transfer.type),
|
|
36
|
+
...(transfer.minTransferTime !== undefined && {
|
|
37
|
+
minTransferTime: transfer.minTransferTime.toSeconds(),
|
|
38
|
+
}),
|
|
39
|
+
})),
|
|
40
|
+
routes: value.routes,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return protoStopsAdjacency;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const serializeRoutesAdjacency = (
|
|
49
|
+
routesAdjacency: RoutesAdjacency,
|
|
50
|
+
): ProtoRoutesAdjacency => {
|
|
51
|
+
const protoRoutesAdjacency: ProtoRoutesAdjacency = {
|
|
52
|
+
routes: {},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
routesAdjacency.forEach((value: Route, key: string) => {
|
|
56
|
+
protoRoutesAdjacency.routes[key] = {
|
|
57
|
+
stopTimes: value.stopTimes.map((stopTimes) => ({
|
|
58
|
+
arrival: stopTimes.arrival.toSeconds(),
|
|
59
|
+
departure: stopTimes.departure.toSeconds(),
|
|
60
|
+
pickUpType: serializePickUpDropOffType(stopTimes.pickUpType),
|
|
61
|
+
dropOffType: serializePickUpDropOffType(stopTimes.dropOffType),
|
|
62
|
+
})),
|
|
63
|
+
stops: value.stops,
|
|
64
|
+
serviceRouteId: value.serviceRouteId,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return protoRoutesAdjacency;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const serializeServiceRoutesMap = (
|
|
72
|
+
serviceRoutesMap: ServiceRoutesMap,
|
|
73
|
+
): ProtoServiceRoutesMap => {
|
|
74
|
+
const protoServiceRoutesMap: ProtoServiceRoutesMap = {
|
|
75
|
+
routes: {},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
serviceRoutesMap.forEach(
|
|
79
|
+
(value: { type: RouteType; name: string }, key: string) => {
|
|
80
|
+
protoServiceRoutesMap.routes[key] = {
|
|
81
|
+
type: serializeRouteType(value.type),
|
|
82
|
+
name: value.name,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return protoServiceRoutesMap;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const deserializeStopsAdjacency = (
|
|
91
|
+
protoStopsAdjacency: ProtoStopsAdjacency,
|
|
92
|
+
): StopsAdjacency => {
|
|
93
|
+
const stopsAdjacency: StopsAdjacency = new Map();
|
|
94
|
+
|
|
95
|
+
Object.entries(protoStopsAdjacency.stops).forEach(([key, value]) => {
|
|
96
|
+
stopsAdjacency.set(key, {
|
|
97
|
+
transfers: value.transfers.map(
|
|
98
|
+
(transfer: ProtoTransfer): Transfer => ({
|
|
99
|
+
destination: transfer.destination,
|
|
100
|
+
type: parseTransferType(transfer.type),
|
|
101
|
+
...(transfer.minTransferTime !== undefined && {
|
|
102
|
+
minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
routes: value.routes,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return stopsAdjacency;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const deserializeRoutesAdjacency = (
|
|
114
|
+
protoRoutesAdjacency: ProtoRoutesAdjacency,
|
|
115
|
+
): RoutesAdjacency => {
|
|
116
|
+
const routesAdjacency: RoutesAdjacency = new Map();
|
|
117
|
+
|
|
118
|
+
Object.entries(protoRoutesAdjacency.routes).forEach(([key, value]) => {
|
|
119
|
+
routesAdjacency.set(key, {
|
|
120
|
+
stopTimes: value.stopTimes.map((stopTimes) => ({
|
|
121
|
+
arrival: Time.fromSeconds(stopTimes.arrival),
|
|
122
|
+
departure: Time.fromSeconds(stopTimes.departure),
|
|
123
|
+
pickUpType:
|
|
124
|
+
stopTimes.pickUpType !== undefined
|
|
125
|
+
? parsePickUpDropOffType(stopTimes.pickUpType)
|
|
126
|
+
: 'REGULAR',
|
|
127
|
+
dropOffType:
|
|
128
|
+
stopTimes.dropOffType !== undefined
|
|
129
|
+
? parsePickUpDropOffType(stopTimes.dropOffType)
|
|
130
|
+
: 'REGULAR',
|
|
131
|
+
})),
|
|
132
|
+
stops: value.stops,
|
|
133
|
+
stopIndices: new Map(value.stops.map((stop, index) => [stop, index])),
|
|
134
|
+
serviceRouteId: value.serviceRouteId,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return routesAdjacency;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const deserializeServiceRoutesMap = (
|
|
142
|
+
protoServiceRoutesMap: ProtoServiceRoutesMap,
|
|
143
|
+
): ServiceRoutesMap => {
|
|
144
|
+
const serviceRoutesMap: ServiceRoutesMap = new Map();
|
|
145
|
+
|
|
146
|
+
Object.entries(protoServiceRoutesMap.routes).forEach(([key, value]) => {
|
|
147
|
+
serviceRoutesMap.set(key, {
|
|
148
|
+
type: parseRouteType(value.type),
|
|
149
|
+
name: value.name,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return serviceRoutesMap;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const parseTransferType = (type: ProtoTransferType): TransferType => {
|
|
157
|
+
switch (type) {
|
|
158
|
+
case ProtoTransferType.RECOMMENDED_TRANSFER_POINT:
|
|
159
|
+
return 'RECOMMENDED';
|
|
160
|
+
case ProtoTransferType.TIMED_TRANSFER:
|
|
161
|
+
return 'GUARANTEED';
|
|
162
|
+
case ProtoTransferType.REQUIRES_MINIMAL_TIME:
|
|
163
|
+
return 'REQUIRES_MINIMAL_TIME';
|
|
164
|
+
case ProtoTransferType.IN_SEAT_TRANSFER:
|
|
165
|
+
return 'IN_SEAT';
|
|
166
|
+
case ProtoTransferType.UNRECOGNIZED:
|
|
167
|
+
throw new Error('Unrecognized protobuf transfer type.');
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const serializeTransferType = (type: TransferType): ProtoTransferType => {
|
|
172
|
+
switch (type) {
|
|
173
|
+
case 'RECOMMENDED':
|
|
174
|
+
return ProtoTransferType.RECOMMENDED_TRANSFER_POINT;
|
|
175
|
+
case 'GUARANTEED':
|
|
176
|
+
return ProtoTransferType.TIMED_TRANSFER;
|
|
177
|
+
case 'REQUIRES_MINIMAL_TIME':
|
|
178
|
+
return ProtoTransferType.REQUIRES_MINIMAL_TIME;
|
|
179
|
+
case 'IN_SEAT':
|
|
180
|
+
return ProtoTransferType.IN_SEAT_TRANSFER;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const parseRouteType = (type: ProtoRouteType): RouteType => {
|
|
185
|
+
switch (type) {
|
|
186
|
+
case ProtoRouteType.TRAM:
|
|
187
|
+
return 'TRAM';
|
|
188
|
+
case ProtoRouteType.SUBWAY:
|
|
189
|
+
return 'SUBWAY';
|
|
190
|
+
case ProtoRouteType.RAIL:
|
|
191
|
+
return 'RAIL';
|
|
192
|
+
case ProtoRouteType.BUS:
|
|
193
|
+
return 'BUS';
|
|
194
|
+
case ProtoRouteType.FERRY:
|
|
195
|
+
return 'FERRY';
|
|
196
|
+
case ProtoRouteType.CABLE_TRAM:
|
|
197
|
+
return 'CABLE_TRAM';
|
|
198
|
+
case ProtoRouteType.AERIAL_LIFT:
|
|
199
|
+
return 'AERIAL_LIFT';
|
|
200
|
+
case ProtoRouteType.FUNICULAR:
|
|
201
|
+
return 'FUNICULAR';
|
|
202
|
+
case ProtoRouteType.TROLLEYBUS:
|
|
203
|
+
return 'TROLLEYBUS';
|
|
204
|
+
case ProtoRouteType.MONORAIL:
|
|
205
|
+
return 'MONORAIL';
|
|
206
|
+
case ProtoRouteType.UNRECOGNIZED:
|
|
207
|
+
default:
|
|
208
|
+
throw new Error('Unrecognized protobuf route type.');
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const serializeRouteType = (type: RouteType): ProtoRouteType => {
|
|
213
|
+
switch (type) {
|
|
214
|
+
case 'TRAM':
|
|
215
|
+
return ProtoRouteType.TRAM;
|
|
216
|
+
case 'SUBWAY':
|
|
217
|
+
return ProtoRouteType.SUBWAY;
|
|
218
|
+
case 'RAIL':
|
|
219
|
+
return ProtoRouteType.RAIL;
|
|
220
|
+
case 'BUS':
|
|
221
|
+
return ProtoRouteType.BUS;
|
|
222
|
+
case 'FERRY':
|
|
223
|
+
return ProtoRouteType.FERRY;
|
|
224
|
+
case 'CABLE_TRAM':
|
|
225
|
+
return ProtoRouteType.CABLE_TRAM;
|
|
226
|
+
case 'AERIAL_LIFT':
|
|
227
|
+
return ProtoRouteType.AERIAL_LIFT;
|
|
228
|
+
case 'FUNICULAR':
|
|
229
|
+
return ProtoRouteType.FUNICULAR;
|
|
230
|
+
case 'TROLLEYBUS':
|
|
231
|
+
return ProtoRouteType.TROLLEYBUS;
|
|
232
|
+
case 'MONORAIL':
|
|
233
|
+
return ProtoRouteType.MONORAIL;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const parsePickUpDropOffType = (
|
|
238
|
+
type: ProtoPickUpDropOffType,
|
|
239
|
+
): PickUpDropOffType => {
|
|
240
|
+
switch (type) {
|
|
241
|
+
case ProtoPickUpDropOffType.MUST_PHONE_AGENCY:
|
|
242
|
+
return 'MUST_PHONE_AGENCY';
|
|
243
|
+
case ProtoPickUpDropOffType.MUST_COORDINATE_WITH_DRIVER:
|
|
244
|
+
return 'MUST_COORDINATE_WITH_DRIVER';
|
|
245
|
+
case ProtoPickUpDropOffType.NOT_AVAILABLE:
|
|
246
|
+
return 'NOT_AVAILABLE';
|
|
247
|
+
default:
|
|
248
|
+
return 'REGULAR';
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const serializePickUpDropOffType = (
|
|
253
|
+
type: PickUpDropOffType,
|
|
254
|
+
): ProtoPickUpDropOffType | undefined => {
|
|
255
|
+
switch (type) {
|
|
256
|
+
case 'REGULAR':
|
|
257
|
+
return undefined;
|
|
258
|
+
case 'NOT_AVAILABLE':
|
|
259
|
+
return ProtoPickUpDropOffType.NOT_AVAILABLE;
|
|
260
|
+
case 'MUST_COORDINATE_WITH_DRIVER':
|
|
261
|
+
return ProtoPickUpDropOffType.MUST_COORDINATE_WITH_DRIVER;
|
|
262
|
+
case 'MUST_PHONE_AGENCY':
|
|
263
|
+
return ProtoPickUpDropOffType.MUST_PHONE_AGENCY;
|
|
264
|
+
}
|
|
265
|
+
};
|