mobility-toolbox-js 2.0.0 → 2.0.1-beta.13

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 (102) hide show
  1. package/api/RoutingAPI.js +15 -0
  2. package/api/RoutingAPI.test.js +25 -0
  3. package/api/StopsAPI.js +12 -0
  4. package/api/StopsAPI.test.js +22 -0
  5. package/api/TralisAPI.js +359 -0
  6. package/api/TralisAPI.test.js +67 -0
  7. package/api/{tralis/TralisAPIUtils.js → TralisAPIUtils.js} +2 -32
  8. package/api/index.js +3 -3
  9. package/{ol/README.md → api/typedefs.js} +0 -0
  10. package/common/Tracker.js +14 -118
  11. package/common/api/HttpAPI.js +30 -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 +4 -0
  18. package/common/layers/Layer.js +53 -244
  19. package/common/layers/Layer.test.js +185 -244
  20. package/common/mixins/CopyrightMixin.js +20 -44
  21. package/common/mixins/SearchMixin.js +100 -166
  22. package/common/mixins/TralisLayerMixin.js +443 -894
  23. package/common/styles/index.js +4 -4
  24. package/common/styles/trackerDefaultStyle.js +39 -175
  25. package/common/styles/trackerDelayStyle.js +2 -11
  26. package/common/styles/trackerSimpleStyle.js +4 -8
  27. package/common/trackerConfig.js +61 -99
  28. package/common/trackerConfig.test.js +15 -17
  29. package/common/typedefs.js +0 -23
  30. package/common/utils/createTrackerFilters.js +10 -41
  31. package/common/utils/createTrackerFilters.test.js +40 -56
  32. package/common/utils/getMapboxMapCopyrights.js +3 -16
  33. package/common/utils/getMapboxMapCopyrights.test.js +32 -39
  34. package/common/utils/getMapboxStyleUrl.js +3 -13
  35. package/common/utils/getVehiclePosition.js +3 -33
  36. package/common/utils/index.js +5 -6
  37. package/common/utils/removeDuplicate.js +3 -17
  38. package/common/utils/removeDuplicate.test.js +17 -20
  39. package/common/utils/sortByDelay.js +2 -7
  40. package/common/utils/timeUtils.js +8 -32
  41. package/common/utils/timeUtils.test.js +7 -13
  42. package/index.js +8 -2
  43. package/mapbox/controls/CopyrightControl.js +9 -38
  44. package/mapbox/controls/index.js +1 -0
  45. package/mapbox/index.js +4 -3
  46. package/mapbox/layers/Layer.js +15 -76
  47. package/mapbox/layers/Layer.test.js +81 -101
  48. package/mapbox/layers/TralisLayer.js +46 -193
  49. package/mapbox/layers/TralisLayer.test.js +12 -14
  50. package/mapbox/layers/index.js +2 -0
  51. package/mapbox/utils.js +7 -21
  52. package/mbt.js +50444 -0
  53. package/mbt.js.map +7 -0
  54. package/mbt.min.js +1005 -0
  55. package/mbt.min.js.map +7 -0
  56. package/ol/controls/CopyrightControl.js +8 -46
  57. package/ol/controls/CopyrightControl.test.js +75 -121
  58. package/ol/controls/RoutingControl.js +167 -532
  59. package/ol/controls/RoutingControl.test.js +99 -164
  60. package/ol/controls/StopFinderControl.js +3 -31
  61. package/ol/controls/StopFinderControl.test.js +18 -29
  62. package/ol/controls/index.js +3 -0
  63. package/ol/index.js +5 -13
  64. package/ol/layers/Layer.js +23 -128
  65. package/ol/layers/Layer.test.js +79 -102
  66. package/ol/layers/MapboxLayer.js +62 -237
  67. package/ol/layers/MapboxLayer.test.js +58 -84
  68. package/ol/layers/MapboxStyleLayer.js +38 -268
  69. package/ol/layers/MapboxStyleLayer.test.js +97 -128
  70. package/ol/layers/MaplibreLayer.js +46 -187
  71. package/ol/layers/RoutingLayer.js +21 -51
  72. package/ol/layers/RoutingLayer.test.js +15 -24
  73. package/ol/layers/TralisLayer.js +102 -276
  74. package/ol/layers/TralisLayer.test.js +32 -50
  75. package/ol/layers/VectorLayer.js +3 -24
  76. package/ol/layers/VectorLayer.test.js +34 -45
  77. package/ol/layers/WMSLayer.js +15 -57
  78. package/ol/layers/WMSLayer.test.js +35 -43
  79. package/ol/layers/index.js +8 -0
  80. package/ol/styles/fullTrajectoryDelayStyle.js +11 -15
  81. package/ol/styles/fullTrajectoryStyle.js +17 -25
  82. package/ol/styles/index.js +2 -2
  83. package/package.json +35 -62
  84. package/api/routing/RoutingAPI.js +0 -44
  85. package/api/routing/RoutingAPI.test.js +0 -41
  86. package/api/stops/StopsAPI.js +0 -41
  87. package/api/stops/StopsAPI.test.js +0 -34
  88. package/api/tralis/TralisAPI.js +0 -731
  89. package/api/tralis/TralisAPI.test.js +0 -75
  90. package/api/tralis/WebSocketConnector.js +0 -338
  91. package/api/tralis/typedefs.js +0 -81
  92. package/common/api/api.js +0 -64
  93. package/common/api/api.test.js +0 -68
  94. package/index.js.map +0 -1
  95. package/module.js +0 -23
  96. package/ol/controls/snapshots/RoutingControlRouteGen10.json +0 -58
  97. package/ol/controls/snapshots/RoutingControlRouteGen100.json +0 -292
  98. package/ol/controls/snapshots/RoutingControlRouteGen30.json +0 -69
  99. package/ol/controls/snapshots/RoutingControlRouteGen5.json +0 -58
  100. package/ol/controls/snapshots/RoutingControlRouteOSM.json +0 -759
  101. package/ol/controls/snapshots/RoutingControlStation1.json +0 -60
  102. package/ol/controls/snapshots/RoutingControlStation2.json +0 -49
package/common/Tracker.js CHANGED
@@ -1,43 +1,10 @@
1
- /* eslint-disable no-param-reassign */
2
- import { compose, apply, create } from 'ol/transform';
3
- import getVehiclePosition from './utils/getVehiclePosition';
4
-
5
- /**
6
- * Tracker. This class stores and allows to draw trajectories on a canvas.
7
- * @class
8
- * @param {Object} options
9
- * @private
10
- */
1
+ import { compose, apply, create } from "ol/transform";
2
+ import getVehiclePosition from "./utils/getVehiclePosition";
11
3
  export default class Tracker {
12
- /**
13
- * @private
14
- */
15
4
  constructor(options) {
16
- /**
17
- * Function use to style the features displayed.
18
- * @type {function}
19
- */
20
5
  this.style = options.style;
21
-
22
- // we draw directly on the canvas since openlayers is too slow.
23
- /**
24
- * HTML <canvas> element.
25
- * @type {Canvas}
26
- */
27
- this.canvas = options.canvas || document.createElement('canvas');
6
+ this.canvas = options.canvas || document.createElement("canvas");
28
7
  }
29
-
30
- /**
31
- * Draw all the trajectories available to the canvas.
32
- * @param {ViewState} trajectories An array of trajectories.
33
- * @param {ViewState} viewState The view state of the map.
34
- * @param {boolean} options.hoverVehicleId The id of the vehicle to highlight.
35
- * @param {boolean} options.selectedVehicleId The id of the vehicle to select.
36
- * @param {boolean} options.noInterpolate If true trajectories are not interpolated but
37
- * drawn at the last known coordinate. Use this for performance optimization
38
- * during map navigation.
39
- * @private
40
- */
41
8
  renderTrajectories(trajectories, viewState, options) {
42
9
  const {
43
10
  time = Date.now(),
@@ -45,44 +12,25 @@ export default class Tracker {
45
12
  center,
46
13
  resolution,
47
14
  rotation = 0,
48
- pixelRatio,
15
+ pixelRatio
49
16
  } = viewState;
50
17
  const {
51
18
  noInterpolate = false,
52
19
  hoverVehicleId,
53
- selectedVehicleId,
20
+ selectedVehicleId
54
21
  } = options;
55
-
56
22
  const { canvas } = this;
57
- const context = canvas.getContext('2d');
23
+ const context = canvas.getContext("2d");
58
24
  context.clearRect(0, 0, canvas.width, canvas.height);
59
-
60
25
  const [width, height] = size;
61
- if (
62
- width &&
63
- height &&
64
- (canvas.width !== width || canvas.height !== height)
65
- ) {
26
+ if (width && height && (canvas.width !== width || canvas.height !== height)) {
66
27
  [canvas.width, canvas.height] = [width * pixelRatio, height * pixelRatio];
67
28
  }
68
-
69
- const coordinateToPixelTransform = compose(
70
- create(),
71
- size[0] / 2,
72
- size[1] / 2,
73
- 1 / resolution,
74
- -1 / resolution,
75
- -rotation,
76
- -center[0],
77
- -center[1],
78
- );
79
-
80
- // Offscreen canvas has not style attribute
29
+ const coordinateToPixelTransform = compose(create(), size[0] / 2, size[1] / 2, 1 / resolution, -1 / resolution, -rotation, -center[0], -center[1]);
81
30
  if (canvas.style) {
82
31
  canvas.style.width = `${canvas.width / pixelRatio}px`;
83
32
  canvas.style.height = `${canvas.height / pixelRatio}px`;
84
33
  }
85
-
86
34
  let hoverVehicleImg;
87
35
  let hoverVehiclePx;
88
36
  let hoverVehicleWidth;
@@ -92,106 +40,54 @@ export default class Tracker {
92
40
  let selectedVehicleWidth;
93
41
  let selectedVehicleHeight;
94
42
  let nbRendered = 0;
95
-
96
43
  for (let i = trajectories.length - 1; i >= 0; i -= 1) {
97
44
  const trajectory = trajectories[i];
98
-
99
- // We simplify the trajectory object
100
45
  const { train_id: id, timeOffset } = trajectory.properties;
101
- // We set the rotation and the timeFraction of the trajectory (used by tralis).
102
- // if rotation === null that seems there is no rotation available.
103
- const { coord, rotation: rotationIcon } = getVehiclePosition(
104
- time - (timeOffset || 0),
105
- trajectory,
106
- noInterpolate,
107
- );
108
-
109
- // We store the current vehicle position to the trajectory.
46
+ const { coord, rotation: rotationIcon } = getVehiclePosition(time - (timeOffset || 0), trajectory, noInterpolate);
110
47
  trajectories[i].properties.coordinate = coord;
111
48
  trajectories[i].properties.rotation = rotationIcon;
112
-
113
49
  if (!coord) {
114
- // eslint-disable-next-line no-continue
115
50
  continue;
116
51
  }
117
-
118
52
  let px = apply(coordinateToPixelTransform, [...coord]);
119
53
  if (!px) {
120
- // eslint-disable-next-line no-continue
121
54
  continue;
122
55
  }
123
-
124
56
  px = px.map((p) => p * pixelRatio);
125
-
126
- if (
127
- px[0] < 0 ||
128
- px[0] > canvas.width ||
129
- px[1] < 0 ||
130
- px[1] > canvas.height
131
- ) {
132
- // eslint-disable-next-line no-continue
57
+ if (px[0] < 0 || px[0] > canvas.width || px[1] < 0 || px[1] > canvas.height) {
133
58
  continue;
134
59
  }
135
-
136
60
  const vehicleImg = this.style(trajectory, viewState, options);
137
61
  if (!vehicleImg) {
138
- // eslint-disable-next-line no-continue
139
62
  continue;
140
63
  }
141
-
142
64
  nbRendered += 1;
143
-
144
65
  const imgWidth = vehicleImg.width;
145
66
  const imgHeight = vehicleImg.height;
146
-
147
67
  if (hoverVehicleId !== id && selectedVehicleId !== id) {
148
- context.drawImage(
149
- vehicleImg,
150
- px[0] - imgWidth / 2,
151
- px[1] - imgHeight / 2,
152
- imgWidth,
153
- imgHeight,
154
- );
68
+ context.drawImage(vehicleImg, px[0] - imgWidth / 2, px[1] - imgHeight / 2, imgWidth, imgHeight);
155
69
  }
156
-
157
70
  if (hoverVehicleId && hoverVehicleId === id) {
158
- // Store the canvas to draw it at the end
159
71
  hoverVehicleImg = vehicleImg;
160
72
  hoverVehiclePx = px;
161
73
  hoverVehicleWidth = imgWidth;
162
74
  hoverVehicleHeight = imgHeight;
163
75
  }
164
-
165
76
  if (selectedVehicleId && selectedVehicleId === id) {
166
- // Store the canvas to draw it at the end
167
77
  selectedVehicleImg = vehicleImg;
168
78
  selectedVehiclePx = px;
169
79
  selectedVehicleWidth = imgWidth;
170
80
  selectedVehicleHeight = imgHeight;
171
81
  }
172
82
  }
173
-
174
83
  if (selectedVehicleImg) {
175
- context.drawImage(
176
- selectedVehicleImg,
177
- selectedVehiclePx[0] - selectedVehicleWidth / 2,
178
- selectedVehiclePx[1] - selectedVehicleHeight / 2,
179
- selectedVehicleWidth,
180
- selectedVehicleHeight,
181
- );
84
+ context.drawImage(selectedVehicleImg, selectedVehiclePx[0] - selectedVehicleWidth / 2, selectedVehiclePx[1] - selectedVehicleHeight / 2, selectedVehicleWidth, selectedVehicleHeight);
182
85
  }
183
-
184
86
  if (hoverVehicleImg) {
185
- context.drawImage(
186
- hoverVehicleImg,
187
- hoverVehiclePx[0] - hoverVehicleWidth / 2,
188
- hoverVehiclePx[1] - hoverVehicleHeight / 2,
189
- hoverVehicleWidth,
190
- hoverVehicleHeight,
191
- );
87
+ context.drawImage(hoverVehicleImg, hoverVehiclePx[0] - hoverVehicleWidth / 2, hoverVehiclePx[1] - hoverVehicleHeight / 2, hoverVehicleWidth, hoverVehicleHeight);
192
88
  }
193
89
  return {
194
- nbTrajectoriesRendered: nbRendered,
90
+ nbTrajectoriesRendered: nbRendered
195
91
  };
196
92
  }
197
93
  }
@@ -0,0 +1,30 @@
1
+ import qs from "query-string";
2
+ import BaseObject from "ol/Object";
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
+ const urlParams = { ...params || {}, key: this.apiKey };
11
+ const clone = { ...urlParams };
12
+ Object.keys(urlParams).forEach((key) => (clone[key] === void 0 || clone[key] === null) && delete clone[key]);
13
+ if (!this.apiKey) {
14
+ return Promise.reject(new Error(`No apiKey defined for request to ${this.url}`));
15
+ }
16
+ return fetch(`${this.url}${path || ""}?${qs.stringify(clone)}`, config).then((response) => {
17
+ try {
18
+ return response.json().then((data) => {
19
+ if (data.error) {
20
+ throw new Error(data.error);
21
+ }
22
+ return data;
23
+ });
24
+ } catch (err) {
25
+ return Promise.reject(new Error(err));
26
+ }
27
+ });
28
+ }
29
+ }
30
+ 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?fooEmpty=&key=apiKey&q=Bern");
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
+ });
@@ -0,0 +1,175 @@
1
+ class WebSocketApi {
2
+ constructor() {
3
+ this.defineProperties();
4
+ }
5
+ defineProperties() {
6
+ Object.defineProperties(this, {
7
+ closed: {
8
+ get: () => !!(this.websocket && this.websocket.readyState === this.websocket.CLOSED)
9
+ },
10
+ closing: {
11
+ get: () => !!(this.websocket && this.websocket.readyState === this.websocket.CLOSING)
12
+ },
13
+ connecting: {
14
+ get: () => !!(this.websocket && this.websocket.readyState === this.websocket.CONNECTING)
15
+ },
16
+ open: {
17
+ get: () => !!(this.websocket && this.websocket.readyState === this.websocket.OPEN)
18
+ },
19
+ messagesOnOpen: {
20
+ value: [],
21
+ writable: true
22
+ },
23
+ subscriptions: {
24
+ value: [],
25
+ writable: true
26
+ },
27
+ subscribed: {
28
+ value: {},
29
+ writable: true
30
+ }
31
+ });
32
+ }
33
+ static getRequestString(method, params) {
34
+ let reqStr = `${method} ${params.channel}`;
35
+ reqStr += params.args ? ` ${params.args}` : "";
36
+ reqStr += params.id ? ` ${params.id}` : "";
37
+ return reqStr.trim();
38
+ }
39
+ connect(url, onOpen = () => {
40
+ }) {
41
+ if (this.websocket && !this.closed) {
42
+ this.websocket.close();
43
+ }
44
+ this.websocket = new WebSocket(url);
45
+ if (!this.open) {
46
+ this.websocket.addEventListener("open", () => {
47
+ onOpen();
48
+ this.subscribePreviousSubscriptions();
49
+ });
50
+ } else {
51
+ onOpen();
52
+ this.subscribePreviousSubscriptions();
53
+ }
54
+ }
55
+ close() {
56
+ if (this.websocket) {
57
+ this.websocket.onclose = null;
58
+ this.websocket.close();
59
+ this.websocket = null;
60
+ this.messagesOnOpen = [];
61
+ }
62
+ }
63
+ send(message) {
64
+ if (!this.websocket) {
65
+ return;
66
+ }
67
+ const send = () => {
68
+ this.websocket.send(message);
69
+ };
70
+ if (!this.open) {
71
+ if (!this.messagesOnOpen.includes(message)) {
72
+ this.messagesOnOpen.push(message);
73
+ this.websocket.addEventListener("open", () => {
74
+ this.messagesOnOpen = [];
75
+ send();
76
+ });
77
+ this.websocket.addEventListener("close", () => {
78
+ this.messagesOnOpen = [];
79
+ });
80
+ }
81
+ } else if (!this.messagesOnOpen.includes(message)) {
82
+ send();
83
+ }
84
+ }
85
+ listen(params, cb, errorCb) {
86
+ this.unlisten(params, cb);
87
+ const onMessage = (evt) => {
88
+ let data = {};
89
+ try {
90
+ data = JSON.parse(evt.data);
91
+ } catch (err) {
92
+ console.error("WebSocket: unable to parse JSON data", err, evt.data);
93
+ }
94
+ let source = params.channel;
95
+ source += params.args ? ` ${params.args}` : "";
96
+ const contents = data.source === "buffer" ? data.content : [data];
97
+ contents.forEach((content) => {
98
+ if (content?.source === source && (!params.id || params.id === data.client_reference)) {
99
+ cb(content);
100
+ }
101
+ });
102
+ };
103
+ if (this.websocket) {
104
+ this.websocket.addEventListener("message", onMessage);
105
+ if (errorCb) {
106
+ this.websocket.addEventListener("error", errorCb);
107
+ this.websocket.addEventListener("close", errorCb);
108
+ }
109
+ }
110
+ return { onMessageCb: onMessage, onErrorCb: errorCb };
111
+ }
112
+ unlisten(params, cb) {
113
+ if (!this.websocket) {
114
+ return;
115
+ }
116
+ this.subscriptions.filter((s) => s.params.channel === params.channel && (!cb || s.cb === cb)).forEach(({ onMessageCb, onErrorCb }) => {
117
+ if (this.websocket) {
118
+ this.websocket.removeEventListener("message", onMessageCb);
119
+ if (onErrorCb) {
120
+ this.websocket.removeEventListener("error", onErrorCb);
121
+ this.websocket.removeEventListener("close", onErrorCb);
122
+ }
123
+ }
124
+ });
125
+ }
126
+ get(params, cb, errorCb) {
127
+ const reqStr = WebSocketApi.getRequestString("GET", params);
128
+ this.send(reqStr);
129
+ this.listen(params, cb, errorCb);
130
+ }
131
+ subscribe(params, cb, errorCb, quiet = false) {
132
+ const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb);
133
+ const reqStr = WebSocketApi.getRequestString("", params);
134
+ const index = this.subscriptions.findIndex((subcr) => params.channel === subcr.params.channel && cb === subcr.cb);
135
+ const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet };
136
+ if (index > -1) {
137
+ this.subscriptions[index] = newSubscr;
138
+ } else {
139
+ this.subscriptions.push(newSubscr);
140
+ }
141
+ if (!this.subscribed[reqStr]) {
142
+ if (!newSubscr.quiet) {
143
+ this.send(`GET ${reqStr}`);
144
+ this.send(`SUB ${reqStr}`);
145
+ }
146
+ this.subscribed[reqStr] = true;
147
+ }
148
+ }
149
+ unsubscribe(source, cb) {
150
+ const toRemove = this.subscriptions.filter((s) => s.params.channel === source && (!cb || s.cb === cb));
151
+ toRemove.forEach(({ onMessageCb, onErrorCb }) => {
152
+ if (this.websocket) {
153
+ this.websocket.removeEventListener("message", onMessageCb);
154
+ if (onErrorCb) {
155
+ this.websocket.removeEventListener("error", onErrorCb);
156
+ this.websocket.removeEventListener("close", onErrorCb);
157
+ }
158
+ }
159
+ });
160
+ this.subscriptions = this.subscriptions.filter((s) => s.params.channel !== source || cb && s.cb !== cb);
161
+ if (source && this.subscribed[source] && !this.subscriptions.find((s) => s.params.channel === source) && toRemove.find((subscr) => !subscr.quiet)) {
162
+ this.send(`DEL ${source}`);
163
+ this.subscribed[source] = false;
164
+ }
165
+ }
166
+ subscribePreviousSubscriptions() {
167
+ Object.keys(this.subscribed).forEach((key) => {
168
+ this.subscribed[key] = false;
169
+ });
170
+ [...this.subscriptions].forEach((s) => {
171
+ this.subscribe(s.params, s.cb, s.errorCb, s.quiet);
172
+ });
173
+ }
174
+ }
175
+ export default WebSocketApi;