mobility-toolbox-js 2.0.0-beta.2 → 2.0.0-beta.20
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/README.md +4 -1
- package/api/RoutingAPI.js +15 -0
- package/api/RoutingAPI.test.js +25 -0
- package/api/StopsAPI.js +12 -0
- package/api/StopsAPI.test.js +22 -0
- package/api/TralisAPI.js +359 -0
- package/api/TralisAPI.test.js +67 -0
- package/api/{tralis/TralisAPIUtils.js → TralisAPIUtils.js} +2 -32
- package/api/index.js +3 -3
- package/{ol/README.md → api/typedefs.js} +0 -0
- package/common/api/HttpAPI.js +32 -0
- package/common/api/HttpAPI.test.js +50 -0
- package/common/api/WebSocketAPI.js +175 -0
- package/{api/tralis/WebSocketConnector.test.js → common/api/WebSocketAPI.test.js} +100 -145
- package/common/controls/Control.js +26 -91
- package/common/controls/Control.test.js +32 -43
- package/common/index.js +2 -0
- package/common/layers/Layer.js +101 -369
- package/common/layers/Layer.test.js +68 -519
- package/common/mixins/CopyrightMixin.js +20 -44
- package/common/mixins/SearchMixin.js +100 -166
- package/common/mixins/TralisLayerMixin.js +398 -895
- package/common/mixins/UserInteractionsLayerMixin.js +124 -0
- package/common/mixins/UserInteractionsLayerMixin.test.js +199 -0
- package/common/styles/index.js +4 -4
- package/common/styles/trackerDefaultStyle.js +117 -248
- package/common/styles/trackerDelayStyle.js +2 -11
- package/common/styles/trackerSimpleStyle.js +4 -8
- package/common/typedefs.js +0 -23
- package/common/utils/createCanvas.js +17 -0
- package/common/utils/createTrackerFilters.js +10 -41
- package/common/utils/createTrackerFilters.test.js +40 -56
- package/common/utils/getLayersAsFlatArray.js +14 -0
- package/common/utils/getMapboxMapCopyrights.js +3 -16
- package/common/utils/getMapboxMapCopyrights.test.js +32 -39
- package/common/utils/getUrlWithParams.js +11 -0
- package/common/utils/getVehiclePosition.js +3 -33
- package/common/utils/index.js +9 -6
- package/common/utils/removeDuplicate.js +3 -17
- package/common/utils/removeDuplicate.test.js +17 -20
- package/common/utils/renderTrajectories.js +86 -0
- package/common/utils/sortByDelay.js +2 -7
- package/common/utils/timeUtils.js +8 -32
- package/common/utils/timeUtils.test.js +7 -13
- package/common/utils/trackerConfig.js +129 -0
- package/common/utils/trackerConfig.test.js +23 -0
- package/index.js +8 -2
- package/mapbox/controls/CopyrightControl.js +9 -38
- package/mapbox/controls/index.js +1 -0
- package/mapbox/index.js +4 -3
- package/mapbox/layers/Layer.js +28 -90
- package/mapbox/layers/Layer.test.js +85 -105
- package/mapbox/layers/TralisLayer.js +46 -199
- package/mapbox/layers/TralisLayer.test.js +4 -34
- package/mapbox/layers/index.js +2 -0
- package/mapbox/utils.js +7 -21
- package/mbt.js +50073 -0
- package/mbt.js.map +7 -0
- package/mbt.min.js +1008 -0
- package/mbt.min.js.map +7 -0
- package/ol/controls/CopyrightControl.js +8 -46
- package/ol/controls/CopyrightControl.test.js +77 -123
- package/ol/controls/RoutingControl.js +168 -532
- package/ol/controls/RoutingControl.test.js +94 -164
- package/ol/controls/StopFinderControl.js +3 -31
- package/ol/controls/StopFinderControl.test.js +18 -29
- package/ol/controls/index.js +3 -0
- package/ol/index.js +5 -13
- package/ol/layers/Layer.js +40 -150
- package/ol/layers/Layer.test.js +84 -106
- package/ol/layers/MapboxLayer.js +69 -243
- package/ol/layers/MapboxLayer.test.js +58 -84
- package/ol/layers/MapboxStyleLayer.js +38 -268
- package/ol/layers/MapboxStyleLayer.test.js +97 -133
- package/ol/layers/MaplibreLayer.js +53 -193
- package/ol/layers/RoutingLayer.js +21 -51
- package/ol/layers/RoutingLayer.test.js +15 -25
- package/ol/layers/TralisLayer.js +102 -278
- package/ol/layers/TralisLayer.test.js +27 -57
- package/ol/layers/VectorLayer.js +3 -24
- package/ol/layers/VectorLayer.test.js +31 -53
- package/ol/layers/WMSLayer.js +15 -57
- package/ol/layers/WMSLayer.test.js +27 -54
- package/ol/layers/index.js +8 -0
- package/ol/styles/fullTrajectoryDelayStyle.js +11 -15
- package/ol/styles/fullTrajectoryStyle.js +18 -27
- package/ol/styles/index.js +2 -2
- package/package.json +48 -65
- package/types/index.d.ts +9 -0
- package/types/realtime.d.ts +24 -0
- package/types/routing.d.ts +206 -0
- package/types/stops.d.ts +143 -0
- package/api/routing/RoutingAPI.js +0 -44
- package/api/routing/RoutingAPI.test.js +0 -41
- package/api/stops/StopsAPI.js +0 -41
- package/api/stops/StopsAPI.test.js +0 -34
- package/api/tralis/TralisAPI.js +0 -731
- package/api/tralis/TralisAPI.test.js +0 -75
- package/api/tralis/WebSocketConnector.js +0 -338
- package/api/tralis/typedefs.js +0 -81
- package/common/Tracker.js +0 -197
- package/common/api/api.js +0 -64
- package/common/api/api.test.js +0 -68
- package/common/trackerConfig.js +0 -190
- package/common/trackerConfig.test.js +0 -25
- package/common/utils/getMapboxStyleUrl.js +0 -32
- package/index.js.map +0 -1
- package/module.js +0 -23
- package/ol/controls/snapshots/RoutingControlRouteGen10.json +0 -58
- package/ol/controls/snapshots/RoutingControlRouteGen100.json +0 -292
- package/ol/controls/snapshots/RoutingControlRouteGen30.json +0 -69
- package/ol/controls/snapshots/RoutingControlRouteGen5.json +0 -58
- package/ol/controls/snapshots/RoutingControlRouteOSM.json +0 -759
- package/ol/controls/snapshots/RoutingControlStation1.json +0 -60
- package/ol/controls/snapshots/RoutingControlStation2.json +0 -49
package/README.md
CHANGED
|
@@ -8,13 +8,16 @@ The tools in this library have been inspired by many projects realized for publi
|
|
|
8
8
|
[](https://app.netlify.com/sites/mobility-toolbox-js/deploys)
|
|
9
9
|
|
|
10
10
|
## Main Features
|
|
11
|
+
|
|
11
12
|
* Display [real-time vehicle positions and prognosis data](http://tracker.geops.ch/) on a map.
|
|
12
13
|
* Search for [stops and stations](https://maps2.trafimage.ch) all over the world.
|
|
13
14
|
* Get [precise geographic courses](https://geops.github.io/geops-routing-demo/) for all modes of transport.
|
|
14
15
|
* Generate beautiful maps for public transport, mobility and logistics
|
|
15
16
|
|
|
16
17
|
## Documentation and examples
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
Visit https://master--mobility-toolbox-js.netlify.app/
|
|
18
20
|
|
|
19
21
|
## Version 1.x.x
|
|
22
|
+
|
|
20
23
|
The master branch is now open for the version 2 development. The version 1 is still available in [1.x.x](https://github.com/geops/mobility-toolbox-js/tree/1.x.x) branch.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import HttpAPI from "../common/api/HttpAPI";
|
|
2
|
+
class RoutingAPI extends HttpAPI {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
super({
|
|
5
|
+
url: "https://api.geops.io/routing/v1/",
|
|
6
|
+
...options
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
route(params, abortController = new AbortController()) {
|
|
10
|
+
return this.fetch("", params, {
|
|
11
|
+
signal: abortController.signal
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export default RoutingAPI;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fetch from "jest-fetch-mock";
|
|
2
|
+
import RoutingAPI from "./RoutingAPI";
|
|
3
|
+
let api;
|
|
4
|
+
describe("RoutingAPI", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
global.fetch = fetch;
|
|
7
|
+
fetch.resetMocks();
|
|
8
|
+
api = new RoutingAPI({ apiKey: "apiKey" });
|
|
9
|
+
});
|
|
10
|
+
describe("#route", () => {
|
|
11
|
+
test("should success", (done) => {
|
|
12
|
+
fetch.mockResponseOnce(JSON.stringify(global.fetchRouteResponse));
|
|
13
|
+
return api.route({
|
|
14
|
+
mot: "bus",
|
|
15
|
+
via: "47.3739194713294,8.538274823394632|47.37595378493421,8.537490375951839"
|
|
16
|
+
}).then((featureCollection) => {
|
|
17
|
+
expect(fetch.mock.calls[0][0]).toEqual("https://api.geops.io/routing/v1/?key=apiKey&mot=bus&via=47.3739194713294%2C8.538274823394632%7C47.37595378493421%2C8.537490375951839");
|
|
18
|
+
expect(featureCollection.features[0].geometry.type).toEqual("LineString");
|
|
19
|
+
expect(featureCollection.features[0].properties.lines).toBeDefined();
|
|
20
|
+
expect(featureCollection.features[0].properties.station_to).toBeDefined();
|
|
21
|
+
done();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
package/api/StopsAPI.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HttpAPI from "../common/api/HttpAPI";
|
|
2
|
+
class StopsAPI extends HttpAPI {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
super({ url: "https://api.geops.io/stops/v1/", ...options });
|
|
5
|
+
}
|
|
6
|
+
search(params, abortController = {}) {
|
|
7
|
+
return this.fetch("", params, {
|
|
8
|
+
signal: abortController.signal
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export default StopsAPI;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fetch from "jest-fetch-mock";
|
|
2
|
+
import StopsAPI from "./StopsAPI";
|
|
3
|
+
let api;
|
|
4
|
+
describe("StopsAPI", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
global.fetch = fetch;
|
|
7
|
+
fetch.resetMocks();
|
|
8
|
+
api = new StopsAPI({ apiKey: "apiKey" });
|
|
9
|
+
});
|
|
10
|
+
describe("#search", () => {
|
|
11
|
+
test("should success", (done) => {
|
|
12
|
+
fetch.mockResponseOnce(JSON.stringify(global.stopsSearchResponse));
|
|
13
|
+
return api.search({
|
|
14
|
+
q: "Bern"
|
|
15
|
+
}).then((featureCollection) => {
|
|
16
|
+
expect(fetch.mock.calls[0][0]).toEqual("https://api.geops.io/stops/v1/?key=apiKey&q=Bern");
|
|
17
|
+
expect(featureCollection.features[0].properties.name).toEqual("Bern");
|
|
18
|
+
done();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
package/api/TralisAPI.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import WebSocketAPI from "../common/api/WebSocketAPI";
|
|
2
|
+
import {
|
|
3
|
+
getModeSuffix,
|
|
4
|
+
cleanStopTime,
|
|
5
|
+
compareDepartures
|
|
6
|
+
} from "./TralisAPIUtils";
|
|
7
|
+
export const TralisModes = {
|
|
8
|
+
RAW: "raw",
|
|
9
|
+
TOPOGRAPHIC: "topographic",
|
|
10
|
+
SCHEMATIC: "schematic"
|
|
11
|
+
};
|
|
12
|
+
class TralisAPI {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.defineProperties(options);
|
|
15
|
+
this.subscribedStationUic = null;
|
|
16
|
+
this.departureUpdateTimeout = null;
|
|
17
|
+
this.maxDepartureAge = 30;
|
|
18
|
+
this.extraGeoms = {};
|
|
19
|
+
this.prefix = options.prefix || "";
|
|
20
|
+
this.onOpen = this.onOpen.bind(this);
|
|
21
|
+
}
|
|
22
|
+
defineProperties(options) {
|
|
23
|
+
let opt = options;
|
|
24
|
+
if (typeof options === "string") {
|
|
25
|
+
opt = { url: options };
|
|
26
|
+
}
|
|
27
|
+
const { apiKey } = opt;
|
|
28
|
+
let { url, projection, bbox, buffer = [100, 100] } = opt;
|
|
29
|
+
const wsApi = new WebSocketAPI();
|
|
30
|
+
if (apiKey) {
|
|
31
|
+
url = `${url || "wss://api.geops.io/tracker-ws/v1/"}?key=${apiKey}`;
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperties(this, {
|
|
34
|
+
url: {
|
|
35
|
+
get: () => url,
|
|
36
|
+
set: (newUrl) => {
|
|
37
|
+
url = newUrl;
|
|
38
|
+
this.open();
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
projection: {
|
|
42
|
+
get: () => projection,
|
|
43
|
+
set: (newProjection) => {
|
|
44
|
+
if (newProjection !== projection) {
|
|
45
|
+
projection = newProjection;
|
|
46
|
+
if (this.wsApi) {
|
|
47
|
+
this.wsApi.send(`PROJECTION ${projection}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
bbox: {
|
|
53
|
+
get: () => bbox,
|
|
54
|
+
set: (newBbox) => {
|
|
55
|
+
if (JSON.stringify(newBbox) !== JSON.stringify(bbox)) {
|
|
56
|
+
bbox = newBbox;
|
|
57
|
+
if (this.wsApi) {
|
|
58
|
+
this.wsApi.send(`BBOX ${bbox.join(" ")}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
buffer: {
|
|
64
|
+
get: () => buffer,
|
|
65
|
+
set: (newBuffer) => {
|
|
66
|
+
if (JSON.stringify(newBuffer) !== JSON.stringify(buffer)) {
|
|
67
|
+
buffer = newBuffer;
|
|
68
|
+
if (this.wsApi) {
|
|
69
|
+
this.wsApi.send(`BUFFER ${buffer.join(" ")}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
wsApi: {
|
|
75
|
+
value: wsApi,
|
|
76
|
+
writable: true
|
|
77
|
+
},
|
|
78
|
+
pingIntervalMs: {
|
|
79
|
+
value: options.pingIntervalMs || 1e4,
|
|
80
|
+
writable: true
|
|
81
|
+
},
|
|
82
|
+
reconnectTimeoutMs: {
|
|
83
|
+
value: options.pingIntervalMs || 100,
|
|
84
|
+
writable: true
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
open() {
|
|
89
|
+
this.close();
|
|
90
|
+
this.wsApi.connect(this.url, this.onOpen);
|
|
91
|
+
this.wsApi.websocket.onclose = () => {
|
|
92
|
+
this.onClose();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
close() {
|
|
96
|
+
this.wsApi.close();
|
|
97
|
+
}
|
|
98
|
+
reset() {
|
|
99
|
+
this.wsApi.send("RESET");
|
|
100
|
+
}
|
|
101
|
+
onOpen() {
|
|
102
|
+
if (this.projection) {
|
|
103
|
+
this.wsApi.send(`PROJECTION ${this.projection}`);
|
|
104
|
+
}
|
|
105
|
+
if (this.bbox) {
|
|
106
|
+
this.wsApi.send(`BBOX ${this.bbox.join(" ")}`);
|
|
107
|
+
}
|
|
108
|
+
if (this.buffer) {
|
|
109
|
+
this.wsApi.send(`BUFFER ${this.buffer.join(" ")}`);
|
|
110
|
+
}
|
|
111
|
+
if (this.pingIntervalMs) {
|
|
112
|
+
window.clearInterval(this.pingInterval);
|
|
113
|
+
this.pingInterval = setInterval(() => {
|
|
114
|
+
this.wsApi.send("PING");
|
|
115
|
+
}, this.pingIntervalMs);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
onClose() {
|
|
119
|
+
window.clearTimeout(this.pingInterval);
|
|
120
|
+
window.clearTimeout(this.reconnectTimeout);
|
|
121
|
+
if (this.reconnectTimeoutMs) {
|
|
122
|
+
this.reconnectTimeout = window.setTimeout(() => this.open(), this.reconnectTimeoutMs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
subscribe(channel, onSuccess, onError, quiet = false) {
|
|
126
|
+
this.wsApi.subscribe({ channel }, onSuccess, onError, quiet);
|
|
127
|
+
}
|
|
128
|
+
unsubscribe(channel, suffix, cb) {
|
|
129
|
+
this.wsApi.unsubscribe(`${channel}${getModeSuffix(TralisModes.SCHEMATIC, TralisModes)}${suffix}`, cb);
|
|
130
|
+
this.wsApi.unsubscribe(`${channel}${getModeSuffix(TralisModes.TOPOGRAPHIC, TralisModes)}${suffix || ""}`, cb);
|
|
131
|
+
}
|
|
132
|
+
filterDepartures(depObject, sortByMinArrivalTime = false) {
|
|
133
|
+
const departures = Object.keys(depObject).map((k) => depObject[k]);
|
|
134
|
+
departures.sort((a, b) => compareDepartures(a, b, sortByMinArrivalTime));
|
|
135
|
+
let future = new Date();
|
|
136
|
+
future.setMinutes(future.getMinutes() + this.maxDepartureAge);
|
|
137
|
+
future = future.getTime();
|
|
138
|
+
let past = new Date();
|
|
139
|
+
past.setMinutes(past.getMinutes() - this.maxDepartureAge);
|
|
140
|
+
past = past.getTime();
|
|
141
|
+
const departureArray = [];
|
|
142
|
+
const platformsBoarding = [];
|
|
143
|
+
let previousDeparture = null;
|
|
144
|
+
for (let i = departures.length - 1; i >= 0; i -= 1) {
|
|
145
|
+
const d = departures[i];
|
|
146
|
+
const t = new Date(d.time).getTime();
|
|
147
|
+
if (t > past && t < future) {
|
|
148
|
+
if (d.state === "BOARDING") {
|
|
149
|
+
if (platformsBoarding.indexOf(d.platform) === -1) {
|
|
150
|
+
platformsBoarding.push(d.platform);
|
|
151
|
+
} else {
|
|
152
|
+
d.state = "HIDDEN";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (previousDeparture && d.to[0] === previousDeparture.to[0] && Math.abs(t - previousDeparture.time) < 1e3 && d.line.name === previousDeparture.line.name) {
|
|
156
|
+
d.state = "HIDDEN";
|
|
157
|
+
}
|
|
158
|
+
if (/(STOP_CANCELLED|JOURNEY_CANCELLED)/.test(d.state)) {
|
|
159
|
+
d.cancelled = true;
|
|
160
|
+
}
|
|
161
|
+
previousDeparture = d;
|
|
162
|
+
previousDeparture.time = t;
|
|
163
|
+
departureArray.unshift(d);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return departureArray;
|
|
167
|
+
}
|
|
168
|
+
subscribeDepartures(stationId, sortByMinArrivalTime, onMessage) {
|
|
169
|
+
window.clearTimeout(this.departureUpdateTimeout);
|
|
170
|
+
this.unsubscribeDepartures();
|
|
171
|
+
this.subscribedStationUic = stationId;
|
|
172
|
+
const channel = stationId ? `timetable_${stationId}` : null;
|
|
173
|
+
const departureObject = {};
|
|
174
|
+
this.subscribe(channel, (data) => {
|
|
175
|
+
if (data.source === channel) {
|
|
176
|
+
const content = data.content || {};
|
|
177
|
+
const tDiff = new Date(content.timestamp).getTime() - Date.now();
|
|
178
|
+
content.timediff = tDiff;
|
|
179
|
+
departureObject[content.call_id] = content;
|
|
180
|
+
window.clearTimeout(this.departureUpdateTimeout);
|
|
181
|
+
this.departureUpdateTimeout = window.setTimeout(() => {
|
|
182
|
+
const departures = this.filterDepartures(departureObject, sortByMinArrivalTime || false);
|
|
183
|
+
onMessage(departures);
|
|
184
|
+
}, 100);
|
|
185
|
+
}
|
|
186
|
+
}, () => {
|
|
187
|
+
onMessage([]);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
unsubscribeDepartures(cb) {
|
|
191
|
+
if (this.subscribedStationUic) {
|
|
192
|
+
this.unsubscribe(`timetable_${this.subscribedStationUic}`, "", cb);
|
|
193
|
+
this.subscribedStationUic = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
subscribeDisruptions(onMessage) {
|
|
197
|
+
this.subscribe(`${this.prefix}newsticker`, (data) => {
|
|
198
|
+
onMessage(data.content);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
unsubscribeDisruptions(cb) {
|
|
202
|
+
this.unsubscribe(`${this.prefix}newsticker`, "", cb);
|
|
203
|
+
}
|
|
204
|
+
getStation(uic, mode) {
|
|
205
|
+
const params = {
|
|
206
|
+
channel: `station${getModeSuffix(mode, TralisModes)}`,
|
|
207
|
+
args: uic
|
|
208
|
+
};
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
this.wsApi.get(params, (data) => {
|
|
211
|
+
if (data.content) {
|
|
212
|
+
resolve(data.content);
|
|
213
|
+
} else {
|
|
214
|
+
reject();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
getStations(mode) {
|
|
220
|
+
const stations = [];
|
|
221
|
+
const params = {
|
|
222
|
+
channel: `station${getModeSuffix(mode, TralisModes)}`
|
|
223
|
+
};
|
|
224
|
+
window.clearTimeout(this.stationUpdateTimeout);
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
this.wsApi.get(params, (data) => {
|
|
227
|
+
if (data.content) {
|
|
228
|
+
stations.push(data.content);
|
|
229
|
+
window.clearTimeout(this.stationUpdateTimeout);
|
|
230
|
+
this.stationUpdateTimeout = window.setTimeout(() => {
|
|
231
|
+
resolve(stations);
|
|
232
|
+
}, 50);
|
|
233
|
+
} else {
|
|
234
|
+
reject(data.content);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
subscribeStations(mode, onMessage) {
|
|
240
|
+
this.unsubscribeStations();
|
|
241
|
+
this.subscribe(`station${getModeSuffix(mode, TralisModes)}`, (data) => {
|
|
242
|
+
if (data.content) {
|
|
243
|
+
onMessage(data.content);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
unsubscribeStations(cb) {
|
|
248
|
+
window.clearTimeout(this.stationUpdateTimeout);
|
|
249
|
+
this.unsubscribe("station", "", cb);
|
|
250
|
+
}
|
|
251
|
+
subscribeExtraGeoms(onMessage) {
|
|
252
|
+
this.subscribe("extra_geoms", (data) => {
|
|
253
|
+
const extraGeom = data.content;
|
|
254
|
+
if (extraGeom) {
|
|
255
|
+
const { ref } = extraGeom.properties;
|
|
256
|
+
if (extraGeom.type === "Feature") {
|
|
257
|
+
this.extraGeoms[ref] = extraGeom;
|
|
258
|
+
} else {
|
|
259
|
+
delete this.extraGeoms[ref];
|
|
260
|
+
}
|
|
261
|
+
onMessage(Object.keys(this.extraGeoms).map((key) => this.extraGeoms[key]));
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
unsubscribeExtraGeoms(cb) {
|
|
266
|
+
this.unsubscribe("extra_geoms", "", cb);
|
|
267
|
+
}
|
|
268
|
+
subscribeTrajectory(mode, onMessage, quiet = false) {
|
|
269
|
+
this.unsubscribeTrajectory(onMessage);
|
|
270
|
+
this.subscribe(`trajectory${getModeSuffix(mode, TralisModes)}`, onMessage, null, quiet);
|
|
271
|
+
}
|
|
272
|
+
unsubscribeTrajectory(cb) {
|
|
273
|
+
this.unsubscribe(`trajectory`, "", cb);
|
|
274
|
+
}
|
|
275
|
+
subscribeDeletedVehicles(mode, onMessage, quiet = false) {
|
|
276
|
+
this.unsubscribeDeletedVehicles(onMessage);
|
|
277
|
+
this.subscribe(`deleted_vehicles${getModeSuffix(mode, TralisModes)}`, onMessage, null, quiet);
|
|
278
|
+
}
|
|
279
|
+
unsubscribeDeletedVehicles(cb) {
|
|
280
|
+
this.unsubscribe("deleted_vehicles", "", cb);
|
|
281
|
+
}
|
|
282
|
+
getFullTrajectory(id, mode, generalizationLevel) {
|
|
283
|
+
const channel = [`full_trajectory${getModeSuffix(mode, TralisModes)}`];
|
|
284
|
+
if (id) {
|
|
285
|
+
channel.push(id);
|
|
286
|
+
}
|
|
287
|
+
if ((!mode || mode === TralisModes.TOPOGRAPHIC) && generalizationLevel) {
|
|
288
|
+
channel.push(`gen${generalizationLevel}`);
|
|
289
|
+
}
|
|
290
|
+
const params = {
|
|
291
|
+
channel: channel.join("_")
|
|
292
|
+
};
|
|
293
|
+
return new Promise((resolve) => {
|
|
294
|
+
this.wsApi.get(params, (data) => {
|
|
295
|
+
if (data.content) {
|
|
296
|
+
resolve(data.content);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
getFullTrajectories(ids, mode, generalizationLevel) {
|
|
302
|
+
const promises = ids.map((id) => this.getFullTrajectory(id, mode, generalizationLevel));
|
|
303
|
+
return Promise.all(promises);
|
|
304
|
+
}
|
|
305
|
+
subscribeFullTrajectory(id, mode) {
|
|
306
|
+
this.unsubscribeFullTrajectory(id);
|
|
307
|
+
this.subscribe(`full_trajectory${getModeSuffix(mode, TralisModes)}_${id}`, (data) => {
|
|
308
|
+
console.log("subscribe full_trajectory", data);
|
|
309
|
+
}, (err) => {
|
|
310
|
+
console.log("subscribe full_trajectory error", err);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
unsubscribeFullTrajectory(id, cb) {
|
|
314
|
+
this.unsubscribe("full_trajectory", `_${id}`, cb);
|
|
315
|
+
}
|
|
316
|
+
getStopSequence(id) {
|
|
317
|
+
const params = {
|
|
318
|
+
channel: `stopsequence_${id}`
|
|
319
|
+
};
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
this.wsApi.get(params, (data) => {
|
|
322
|
+
if (data.content && data.content.length) {
|
|
323
|
+
const content = data.content.map((stopSequence) => cleanStopTime(stopSequence));
|
|
324
|
+
resolve(content);
|
|
325
|
+
}
|
|
326
|
+
resolve([]);
|
|
327
|
+
}, (err) => {
|
|
328
|
+
reject(err);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
getStopSequences(ids) {
|
|
333
|
+
const promises = ids.map((id) => this.getStopSequence(id));
|
|
334
|
+
return Promise.all(promises);
|
|
335
|
+
}
|
|
336
|
+
subscribeStopSequence(id, onMessage) {
|
|
337
|
+
window.clearTimeout(this.fullTrajectoryUpdateTimeout);
|
|
338
|
+
this.unsubscribeStopSequence(id);
|
|
339
|
+
this.subscribe(`stopsequence_${id}`, (data) => {
|
|
340
|
+
if (data.content && data.content.length) {
|
|
341
|
+
const content = data.content.map((stopSequence) => cleanStopTime(stopSequence));
|
|
342
|
+
onMessage(content);
|
|
343
|
+
}
|
|
344
|
+
}, (err) => {
|
|
345
|
+
console.log("subscribe stopsequence error", err);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
unsubscribeStopSequence(id, cb) {
|
|
349
|
+
this.unsubscribe(`stopsequence`, `_${id}`, cb);
|
|
350
|
+
}
|
|
351
|
+
subscribeHealthCheck(onMessage) {
|
|
352
|
+
this.unsubscribeHealthCheck();
|
|
353
|
+
this.subscribe("healthcheck", onMessage);
|
|
354
|
+
}
|
|
355
|
+
unsubscribeHealthCheck() {
|
|
356
|
+
this.unsubscribe("healthcheck");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
export default TralisAPI;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { TralisAPI, TralisModes } from ".";
|
|
2
|
+
describe("TralisAPI", () => {
|
|
3
|
+
let tralisAPI;
|
|
4
|
+
let get;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
get = jest.fn((params, cb) => {
|
|
7
|
+
cb({ content: "content" });
|
|
8
|
+
});
|
|
9
|
+
tralisAPI = new TralisAPI();
|
|
10
|
+
tralisAPI.wsApi = {
|
|
11
|
+
get
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
describe("#getFullTrajectory() calls fullTrajectory channel", () => {
|
|
15
|
+
test("without parameters", (done) => {
|
|
16
|
+
tralisAPI.getFullTrajectory().then(() => {
|
|
17
|
+
expect(get.mock.calls.length).toBe(1);
|
|
18
|
+
expect(get.mock.calls[0][0]).toEqual({
|
|
19
|
+
channel: "full_trajectory"
|
|
20
|
+
});
|
|
21
|
+
done();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
[null, TralisModes.TOPOGRAPHIC].forEach((mode) => {
|
|
25
|
+
describe(`using mode ${mode}`, () => {
|
|
26
|
+
test("using id", (done) => {
|
|
27
|
+
tralisAPI.getFullTrajectory("foo", mode).then(() => {
|
|
28
|
+
expect(get.mock.calls.length).toBe(1);
|
|
29
|
+
expect(get.mock.calls[0][0]).toEqual({
|
|
30
|
+
channel: "full_trajectory_foo"
|
|
31
|
+
});
|
|
32
|
+
done();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
test("using id and generalizationLevel param", (done) => {
|
|
36
|
+
tralisAPI.getFullTrajectory("foo", mode, 5).then(() => {
|
|
37
|
+
expect(get.mock.calls.length).toBe(1);
|
|
38
|
+
expect(get.mock.calls[0][0]).toEqual({
|
|
39
|
+
channel: "full_trajectory_foo_gen5"
|
|
40
|
+
});
|
|
41
|
+
done();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("using schematic mode ", () => {
|
|
47
|
+
test("using id", (done) => {
|
|
48
|
+
tralisAPI.getFullTrajectory("foo", TralisModes.SCHEMATIC).then(() => {
|
|
49
|
+
expect(get.mock.calls.length).toBe(1);
|
|
50
|
+
expect(get.mock.calls[0][0]).toEqual({
|
|
51
|
+
channel: "full_trajectory_schematic_foo"
|
|
52
|
+
});
|
|
53
|
+
done();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
test("doesn't use generalizationLevel param", (done) => {
|
|
57
|
+
tralisAPI.getFullTrajectory("foo", TralisModes.SCHEMATIC, 10).then(() => {
|
|
58
|
+
expect(get.mock.calls.length).toBe(1);
|
|
59
|
+
expect(get.mock.calls[0][0]).toEqual({
|
|
60
|
+
channel: "full_trajectory_schematic_foo"
|
|
61
|
+
});
|
|
62
|
+
done();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1,38 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
* Get the websocket channel suffix, depending on the current mode.
|
|
3
|
-
* @param {String} mode Mode 'topographic' ou 'schematic'.
|
|
4
|
-
* @private
|
|
5
|
-
*/
|
|
6
|
-
export const getModeSuffix = (mode, modes) =>
|
|
7
|
-
mode === modes.SCHEMATIC ? '_schematic' : '';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Compare two given departures for sort alogithm,
|
|
11
|
-
* @param {Object} a First departure.
|
|
12
|
-
* @param {Object} b Second departure.
|
|
13
|
-
* @private
|
|
14
|
-
*/
|
|
1
|
+
export const getModeSuffix = (mode, modes) => mode === modes.SCHEMATIC ? "_schematic" : "";
|
|
15
2
|
export const compareDepartures = (a, b, sortByMinArrivalTime = false) => {
|
|
16
|
-
|
|
17
|
-
const topStates = ['HIDDEN', 'LEAVING', 'BOARDING'];
|
|
3
|
+
const topStates = ["HIDDEN", "LEAVING", "BOARDING"];
|
|
18
4
|
const aTop = a.has_fzo && topStates.indexOf(a.state) > -1;
|
|
19
5
|
const bTop = b.has_fzo && topStates.indexOf(b.state) > -1;
|
|
20
|
-
|
|
21
6
|
if (aTop || bTop) {
|
|
22
7
|
if (aTop !== bTop) {
|
|
23
8
|
return aTop ? -1 : 1;
|
|
24
9
|
}
|
|
25
|
-
|
|
26
10
|
if (a.state !== b.state) {
|
|
27
|
-
// one is leaving
|
|
28
11
|
return topStates.indexOf(a.state) - topStates.indexOf(b.state);
|
|
29
12
|
}
|
|
30
13
|
}
|
|
31
|
-
|
|
32
14
|
let aDuration = null;
|
|
33
15
|
let bDuration = null;
|
|
34
16
|
const now = Date.now();
|
|
35
|
-
|
|
36
17
|
if (sortByMinArrivalTime) {
|
|
37
18
|
aDuration = new Date(a.min_arrival_time || a.time).getTime() - now;
|
|
38
19
|
bDuration = new Date(b.min_arrival_time || b.time).getTime() - now;
|
|
@@ -40,34 +21,23 @@ export const compareDepartures = (a, b, sortByMinArrivalTime = false) => {
|
|
|
40
21
|
aDuration = new Date(a.time).getTime() - now;
|
|
41
22
|
bDuration = new Date(b.time).getTime() - now;
|
|
42
23
|
}
|
|
43
|
-
|
|
44
24
|
return aDuration - bDuration;
|
|
45
25
|
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Remove the delay from arrivalTime and departureTime
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
51
26
|
export const cleanStopTime = (content) => {
|
|
52
27
|
if (!content) {
|
|
53
28
|
return;
|
|
54
29
|
}
|
|
55
30
|
content.stations.forEach((station) => {
|
|
56
|
-
// eslint-disable-next-line no-param-reassign
|
|
57
31
|
station.arrivalTimeWithDelay = station.arrivalTime;
|
|
58
32
|
if (station.departureTime) {
|
|
59
|
-
// eslint-disable-next-line no-param-reassign
|
|
60
33
|
station.departureTimeWithDelay = station.departureTime;
|
|
61
34
|
}
|
|
62
35
|
if (station.arrivalDelay) {
|
|
63
|
-
// eslint-disable-next-line no-param-reassign
|
|
64
36
|
station.arrivalTime -= station.arrivalDelay;
|
|
65
37
|
if (station.departureTime) {
|
|
66
|
-
// eslint-disable-next-line no-param-reassign
|
|
67
38
|
station.departureTime -= station.arrivalDelay;
|
|
68
39
|
}
|
|
69
40
|
}
|
|
70
41
|
});
|
|
71
|
-
// eslint-disable-next-line consistent-return
|
|
72
42
|
return content;
|
|
73
43
|
};
|
package/api/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export { default as StopsAPI } from
|
|
3
|
-
export { default as TralisAPI, TralisModes } from
|
|
1
|
+
export { default as RoutingAPI } from "./RoutingAPI";
|
|
2
|
+
export { default as StopsAPI } from "./StopsAPI";
|
|
3
|
+
export { default as TralisAPI, TralisModes } from "./TralisAPI";
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import BaseObject from "ol/Object";
|
|
2
|
+
import getUrlWithParams from "../utils/getUrlWithParams";
|
|
3
|
+
class HttpAPI extends BaseObject {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
super();
|
|
6
|
+
this.url = options.url;
|
|
7
|
+
this.apiKey = options.apiKey;
|
|
8
|
+
}
|
|
9
|
+
fetch(path, params, config) {
|
|
10
|
+
if (!this.apiKey && !/key=/.test(this.url)) {
|
|
11
|
+
return Promise.reject(new Error(`No apiKey defined for request to ${this.url}`));
|
|
12
|
+
}
|
|
13
|
+
const searchParams = params || {};
|
|
14
|
+
const url = getUrlWithParams(`${this.url}${path || ""}`, {
|
|
15
|
+
key: this.apiKey,
|
|
16
|
+
...searchParams
|
|
17
|
+
});
|
|
18
|
+
return fetch(url.toString(), config).then((response) => {
|
|
19
|
+
try {
|
|
20
|
+
return response.json().then((data) => {
|
|
21
|
+
if (data.error) {
|
|
22
|
+
throw new Error(data.error);
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return Promise.reject(new Error(err));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export default HttpAPI;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fetch from "jest-fetch-mock";
|
|
2
|
+
import API from "./HttpAPI";
|
|
3
|
+
let api;
|
|
4
|
+
describe("HttpAPI", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
global.fetch = fetch;
|
|
7
|
+
fetch.resetMocks();
|
|
8
|
+
api = new API({ url: "https://foo.ch", apiKey: "apiKey" });
|
|
9
|
+
});
|
|
10
|
+
describe("#fetch", () => {
|
|
11
|
+
test("should success", () => {
|
|
12
|
+
fetch.mockResponseOnce(JSON.stringify({ foo: "bar" }));
|
|
13
|
+
return api.fetch("/path", {
|
|
14
|
+
q: "Bern",
|
|
15
|
+
fooUndefined: void 0,
|
|
16
|
+
fooNull: null,
|
|
17
|
+
fooEmpty: ""
|
|
18
|
+
}).then((response) => {
|
|
19
|
+
expect(fetch.mock.calls[0][0]).toEqual("https://foo.ch/path?key=apiKey&q=Bern&fooEmpty=");
|
|
20
|
+
expect(response).toEqual({ foo: "bar" });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("should display error message", () => {
|
|
24
|
+
test("reject error", (done) => {
|
|
25
|
+
fetch.mockRejectOnce(new Error("Fake error message"));
|
|
26
|
+
return api.fetch().catch((err) => {
|
|
27
|
+
expect(err.name).toEqual("Error");
|
|
28
|
+
expect(err.message).toEqual("Fake error message");
|
|
29
|
+
done();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
test("if the response is invalid json", (done) => {
|
|
33
|
+
fetch.mockResponseOnce("invalid json");
|
|
34
|
+
api.fetch().catch((err) => {
|
|
35
|
+
expect(err.name).toEqual("FetchError");
|
|
36
|
+
expect(err.message).toEqual("invalid json response body at reason: Unexpected token i in JSON at position 0");
|
|
37
|
+
done();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
test("if the response contains an error message", (done) => {
|
|
41
|
+
fetch.mockResponseOnce('{"error":"foo2"}');
|
|
42
|
+
api.fetch().catch((err) => {
|
|
43
|
+
expect(err.name).toEqual("Error");
|
|
44
|
+
expect(err.message).toEqual("foo2");
|
|
45
|
+
done();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|