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.
Files changed (115) hide show
  1. package/README.md +4 -1
  2. package/api/RoutingAPI.js +15 -0
  3. package/api/RoutingAPI.test.js +25 -0
  4. package/api/StopsAPI.js +12 -0
  5. package/api/StopsAPI.test.js +22 -0
  6. package/api/TralisAPI.js +359 -0
  7. package/api/TralisAPI.test.js +67 -0
  8. package/api/{tralis/TralisAPIUtils.js → TralisAPIUtils.js} +2 -32
  9. package/api/index.js +3 -3
  10. package/{ol/README.md → api/typedefs.js} +0 -0
  11. package/common/api/HttpAPI.js +32 -0
  12. package/common/api/HttpAPI.test.js +50 -0
  13. package/common/api/WebSocketAPI.js +175 -0
  14. package/{api/tralis/WebSocketConnector.test.js → common/api/WebSocketAPI.test.js} +100 -145
  15. package/common/controls/Control.js +26 -91
  16. package/common/controls/Control.test.js +32 -43
  17. package/common/index.js +2 -0
  18. package/common/layers/Layer.js +101 -369
  19. package/common/layers/Layer.test.js +68 -519
  20. package/common/mixins/CopyrightMixin.js +20 -44
  21. package/common/mixins/SearchMixin.js +100 -166
  22. package/common/mixins/TralisLayerMixin.js +398 -895
  23. package/common/mixins/UserInteractionsLayerMixin.js +124 -0
  24. package/common/mixins/UserInteractionsLayerMixin.test.js +199 -0
  25. package/common/styles/index.js +4 -4
  26. package/common/styles/trackerDefaultStyle.js +117 -248
  27. package/common/styles/trackerDelayStyle.js +2 -11
  28. package/common/styles/trackerSimpleStyle.js +4 -8
  29. package/common/typedefs.js +0 -23
  30. package/common/utils/createCanvas.js +17 -0
  31. package/common/utils/createTrackerFilters.js +10 -41
  32. package/common/utils/createTrackerFilters.test.js +40 -56
  33. package/common/utils/getLayersAsFlatArray.js +14 -0
  34. package/common/utils/getMapboxMapCopyrights.js +3 -16
  35. package/common/utils/getMapboxMapCopyrights.test.js +32 -39
  36. package/common/utils/getUrlWithParams.js +11 -0
  37. package/common/utils/getVehiclePosition.js +3 -33
  38. package/common/utils/index.js +9 -6
  39. package/common/utils/removeDuplicate.js +3 -17
  40. package/common/utils/removeDuplicate.test.js +17 -20
  41. package/common/utils/renderTrajectories.js +86 -0
  42. package/common/utils/sortByDelay.js +2 -7
  43. package/common/utils/timeUtils.js +8 -32
  44. package/common/utils/timeUtils.test.js +7 -13
  45. package/common/utils/trackerConfig.js +129 -0
  46. package/common/utils/trackerConfig.test.js +23 -0
  47. package/index.js +8 -2
  48. package/mapbox/controls/CopyrightControl.js +9 -38
  49. package/mapbox/controls/index.js +1 -0
  50. package/mapbox/index.js +4 -3
  51. package/mapbox/layers/Layer.js +28 -90
  52. package/mapbox/layers/Layer.test.js +85 -105
  53. package/mapbox/layers/TralisLayer.js +46 -199
  54. package/mapbox/layers/TralisLayer.test.js +4 -34
  55. package/mapbox/layers/index.js +2 -0
  56. package/mapbox/utils.js +7 -21
  57. package/mbt.js +50073 -0
  58. package/mbt.js.map +7 -0
  59. package/mbt.min.js +1008 -0
  60. package/mbt.min.js.map +7 -0
  61. package/ol/controls/CopyrightControl.js +8 -46
  62. package/ol/controls/CopyrightControl.test.js +77 -123
  63. package/ol/controls/RoutingControl.js +168 -532
  64. package/ol/controls/RoutingControl.test.js +94 -164
  65. package/ol/controls/StopFinderControl.js +3 -31
  66. package/ol/controls/StopFinderControl.test.js +18 -29
  67. package/ol/controls/index.js +3 -0
  68. package/ol/index.js +5 -13
  69. package/ol/layers/Layer.js +40 -150
  70. package/ol/layers/Layer.test.js +84 -106
  71. package/ol/layers/MapboxLayer.js +69 -243
  72. package/ol/layers/MapboxLayer.test.js +58 -84
  73. package/ol/layers/MapboxStyleLayer.js +38 -268
  74. package/ol/layers/MapboxStyleLayer.test.js +97 -133
  75. package/ol/layers/MaplibreLayer.js +53 -193
  76. package/ol/layers/RoutingLayer.js +21 -51
  77. package/ol/layers/RoutingLayer.test.js +15 -25
  78. package/ol/layers/TralisLayer.js +102 -278
  79. package/ol/layers/TralisLayer.test.js +27 -57
  80. package/ol/layers/VectorLayer.js +3 -24
  81. package/ol/layers/VectorLayer.test.js +31 -53
  82. package/ol/layers/WMSLayer.js +15 -57
  83. package/ol/layers/WMSLayer.test.js +27 -54
  84. package/ol/layers/index.js +8 -0
  85. package/ol/styles/fullTrajectoryDelayStyle.js +11 -15
  86. package/ol/styles/fullTrajectoryStyle.js +18 -27
  87. package/ol/styles/index.js +2 -2
  88. package/package.json +48 -65
  89. package/types/index.d.ts +9 -0
  90. package/types/realtime.d.ts +24 -0
  91. package/types/routing.d.ts +206 -0
  92. package/types/stops.d.ts +143 -0
  93. package/api/routing/RoutingAPI.js +0 -44
  94. package/api/routing/RoutingAPI.test.js +0 -41
  95. package/api/stops/StopsAPI.js +0 -41
  96. package/api/stops/StopsAPI.test.js +0 -34
  97. package/api/tralis/TralisAPI.js +0 -731
  98. package/api/tralis/TralisAPI.test.js +0 -75
  99. package/api/tralis/WebSocketConnector.js +0 -338
  100. package/api/tralis/typedefs.js +0 -81
  101. package/common/Tracker.js +0 -197
  102. package/common/api/api.js +0 -64
  103. package/common/api/api.test.js +0 -68
  104. package/common/trackerConfig.js +0 -190
  105. package/common/trackerConfig.test.js +0 -25
  106. package/common/utils/getMapboxStyleUrl.js +0 -32
  107. package/index.js.map +0 -1
  108. package/module.js +0 -23
  109. package/ol/controls/snapshots/RoutingControlRouteGen10.json +0 -58
  110. package/ol/controls/snapshots/RoutingControlRouteGen100.json +0 -292
  111. package/ol/controls/snapshots/RoutingControlRouteGen30.json +0 -69
  112. package/ol/controls/snapshots/RoutingControlRouteGen5.json +0 -58
  113. package/ol/controls/snapshots/RoutingControlRouteOSM.json +0 -759
  114. package/ol/controls/snapshots/RoutingControlStation1.json +0 -60
  115. 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
  [![Netlify Status](https://api.netlify.com/api/v1/badges/b368ab18-9dbf-416c-91f6-a82076b02c10/deploy-status)](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
- Visit https://mobility-toolbox-js.geops.io
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
+ });
@@ -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
+ });
@@ -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
- // First LEAVING and HIDDEN, then BOARDING and then sorted by time.
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
- // eslint-disable-next-line import/prefer-default-export
2
- export { default as StopsAPI } from './stops/StopsAPI';
3
- export { default as TralisAPI, TralisModes } from './tralis/TralisAPI';
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
+ });