mobility-toolbox-js 1.7.6 → 1.7.8-beta.2
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/common/Tracker.js +90 -270
- package/common/mixins/TrackerLayerMixin.js +116 -93
- package/common/mixins/TrajservLayerMixin.js +3 -3
- package/common/mixins/TralisLayerMixin.js +67 -67
- package/common/utils/createTrackerFilters.js +15 -10
- package/common/utils/createTrackerFilters.test.js +18 -12
- package/common/utils/delayTrackerStyle.js +5 -5
- package/common/utils/getVehiclePosition.js +71 -0
- package/common/utils/index.js +1 -0
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/mapbox/layers/TrackerLayer.js +3 -1
- package/mapbox/layers/TralisLayer.js +3 -3
- package/ol/layers/TrackerLayer.js +6 -26
- package/ol/layers/TralisLayer.js +3 -3
- package/ol/layers/TralisLayer.test.js +7 -7
- package/package.json +3 -1
package/common/Tracker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
2
|
import { compose, apply, create } from 'ol/transform';
|
|
3
|
+
import getVehiclePosition from './utils/getVehiclePosition';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Tracker. This class stores and allows to draw trajectories on a canvas.
|
|
@@ -12,142 +13,32 @@ export default class Tracker {
|
|
|
12
13
|
* @private
|
|
13
14
|
*/
|
|
14
15
|
constructor(options) {
|
|
15
|
-
const opts = {
|
|
16
|
-
interpolate: true,
|
|
17
|
-
...options,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Pixel ratio to use to draw the canvas. Default to window.devicePixelRatio
|
|
22
|
-
* @type {Array<trajectory>}
|
|
23
|
-
*/
|
|
24
|
-
this.pixelRatio = options.pixelRatio || window.devicePixelRatio || 1;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Array of trajectories.
|
|
28
|
-
* @type {Array<trajectory>}
|
|
29
|
-
*/
|
|
30
|
-
this.trajectories = [];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Array of trajectories that are currently drawn.
|
|
34
|
-
* @type {Array<key>}
|
|
35
|
-
*/
|
|
36
|
-
this.renderedTrajectories = [];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Active interpolation calculation or not. If false, the train will not move until we receive the next message for the websocket.
|
|
40
|
-
* @type {boolean}
|
|
41
|
-
*/
|
|
42
|
-
this.interpolate = !!opts.interpolate;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Id of the trajectory which is hovered.
|
|
46
|
-
* @type {string}
|
|
47
|
-
*/
|
|
48
|
-
this.hoverVehicleId = opts.hoverVehicleId;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Id of the trajectory which is selected.
|
|
52
|
-
* @type {string}
|
|
53
|
-
*/
|
|
54
|
-
this.selectedVehicleId = opts.selectedVehicleId;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Function use to filter the features displayed.
|
|
58
|
-
* @type {function}
|
|
59
|
-
*/
|
|
60
|
-
this.filter = opts.filter;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Function use to sort the features displayed.
|
|
64
|
-
* @type {function}
|
|
65
|
-
*/
|
|
66
|
-
this.sort = opts.sort;
|
|
67
|
-
|
|
68
16
|
/**
|
|
69
17
|
* Function use to style the features displayed.
|
|
70
18
|
* @type {function}
|
|
71
19
|
*/
|
|
72
|
-
this.style =
|
|
20
|
+
this.style = options.style;
|
|
73
21
|
|
|
74
22
|
// we draw directly on the canvas since openlayers is too slow.
|
|
75
23
|
/**
|
|
76
24
|
* HTML <canvas> element.
|
|
77
25
|
* @type {Canvas}
|
|
78
26
|
*/
|
|
79
|
-
this.canvas =
|
|
80
|
-
this.canvas.width = opts.width * this.pixelRatio;
|
|
81
|
-
this.canvas.height = opts.height * this.pixelRatio;
|
|
82
|
-
this.canvas.setAttribute(
|
|
83
|
-
'style',
|
|
84
|
-
[
|
|
85
|
-
'position: absolute',
|
|
86
|
-
'top: 0',
|
|
87
|
-
'bottom: 0',
|
|
88
|
-
`width: ${opts.width}px`,
|
|
89
|
-
`height: ${opts.height}px`,
|
|
90
|
-
'pointer-events: none',
|
|
91
|
-
'visibility: visible',
|
|
92
|
-
'margin-top: inherit', // for scrolling behavior.
|
|
93
|
-
].join(';'),
|
|
94
|
-
);
|
|
95
|
-
/**
|
|
96
|
-
* 2d drawing context on the canvas.
|
|
97
|
-
* @type {CanvasRenderingContext2D}
|
|
98
|
-
*/
|
|
99
|
-
this.canvasContext = this.canvas.getContext('2d');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Set visibility of the canvas.
|
|
104
|
-
* @param {boolean} visible The visibility of the layer
|
|
105
|
-
*/
|
|
106
|
-
setVisible(visible) {
|
|
107
|
-
if (this.canvas) {
|
|
108
|
-
this.canvas.style.visibility = visible ? 'visible' : 'hidden';
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Define the trajectories.
|
|
114
|
-
* @param {array<ol/Feature~Feature>} trajectories
|
|
115
|
-
*/
|
|
116
|
-
setTrajectories(trajectories = []) {
|
|
117
|
-
if (this.sort) {
|
|
118
|
-
trajectories.sort(this.sort);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.trajectories = trajectories;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Return the trajectories.
|
|
126
|
-
* @return {array<trajectory>} trajectories
|
|
127
|
-
*/
|
|
128
|
-
getTrajectories() {
|
|
129
|
-
return this.trajectories || [];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Clear the canvas.
|
|
134
|
-
* @private
|
|
135
|
-
*/
|
|
136
|
-
clear() {
|
|
137
|
-
if (this.canvasContext) {
|
|
138
|
-
this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
139
|
-
}
|
|
27
|
+
this.canvas = options.canvas || document.createElement('canvas');
|
|
140
28
|
}
|
|
141
29
|
|
|
142
30
|
/**
|
|
143
31
|
* Draw all the trajectories available to the canvas.
|
|
32
|
+
* @param {ViewState} trajectories An array of trajectories.
|
|
144
33
|
* @param {ViewState} viewState The view state of the map.
|
|
145
|
-
* @param {boolean}
|
|
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
|
|
146
37
|
* drawn at the last known coordinate. Use this for performance optimization
|
|
147
38
|
* during map navigation.
|
|
148
39
|
* @private
|
|
149
40
|
*/
|
|
150
|
-
renderTrajectories(viewState,
|
|
41
|
+
renderTrajectories(trajectories, viewState, options) {
|
|
151
42
|
const {
|
|
152
43
|
time = Date.now(),
|
|
153
44
|
size = [],
|
|
@@ -156,18 +47,23 @@ export default class Tracker {
|
|
|
156
47
|
rotation = 0,
|
|
157
48
|
pixelRatio,
|
|
158
49
|
} = viewState;
|
|
159
|
-
|
|
50
|
+
const {
|
|
51
|
+
noInterpolate = false,
|
|
52
|
+
hoverVehicleId,
|
|
53
|
+
selectedVehicleId,
|
|
54
|
+
} = options;
|
|
55
|
+
|
|
56
|
+
const { canvas } = this;
|
|
57
|
+
const context = canvas.getContext('2d');
|
|
58
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
160
59
|
|
|
161
60
|
const [width, height] = size;
|
|
162
61
|
if (
|
|
163
62
|
width &&
|
|
164
63
|
height &&
|
|
165
|
-
(
|
|
64
|
+
(canvas.width !== width || canvas.height !== height)
|
|
166
65
|
) {
|
|
167
|
-
[
|
|
168
|
-
width * pixelRatio,
|
|
169
|
-
height * pixelRatio,
|
|
170
|
-
];
|
|
66
|
+
[canvas.width, canvas.height] = [width * pixelRatio, height * pixelRatio];
|
|
171
67
|
}
|
|
172
68
|
|
|
173
69
|
const coordinateToPixelTransform = compose(
|
|
@@ -181,14 +77,12 @@ export default class Tracker {
|
|
|
181
77
|
-center[1],
|
|
182
78
|
);
|
|
183
79
|
|
|
184
|
-
|
|
185
|
-
|
|
80
|
+
// Offscreen canvas has not style attribute
|
|
81
|
+
if (canvas.style) {
|
|
82
|
+
canvas.style.width = `${canvas.width / pixelRatio}px`;
|
|
83
|
+
canvas.style.height = `${canvas.height / pixelRatio}px`;
|
|
84
|
+
}
|
|
186
85
|
|
|
187
|
-
/**
|
|
188
|
-
* Current resolution.
|
|
189
|
-
* @type {number}
|
|
190
|
-
*/
|
|
191
|
-
this.currResolution = resolution || this.currResolution;
|
|
192
86
|
let hoverVehicleImg;
|
|
193
87
|
let hoverVehiclePx;
|
|
194
88
|
let hoverVehicleWidth;
|
|
@@ -197,157 +91,89 @@ export default class Tracker {
|
|
|
197
91
|
let selectedVehiclePx;
|
|
198
92
|
let selectedVehicleWidth;
|
|
199
93
|
let selectedVehicleHeight;
|
|
94
|
+
let nbRendered = 0;
|
|
200
95
|
|
|
201
|
-
|
|
96
|
+
for (let i = trajectories.length - 1; i >= 0; i -= 1) {
|
|
97
|
+
const trajectory = trajectories[i];
|
|
202
98
|
|
|
203
|
-
|
|
204
|
-
const
|
|
99
|
+
// We simplify the trajectory object
|
|
100
|
+
const { train_id: id, timeOffset } = trajectory.properties;
|
|
205
101
|
|
|
206
|
-
|
|
102
|
+
// We set the rotation and the timeFraction of the trajectory (used by tralis).
|
|
103
|
+
// if rotation === null that seems there is no rotation available.
|
|
104
|
+
const { coord, rotation: rotationIcon } = getVehiclePosition(
|
|
105
|
+
time - (timeOffset || 0),
|
|
106
|
+
trajectory,
|
|
107
|
+
noInterpolate,
|
|
108
|
+
);
|
|
207
109
|
|
|
208
|
-
// We
|
|
209
|
-
|
|
110
|
+
// We store the current vehicle position to the trajectory.
|
|
111
|
+
trajectories[i].properties.coordinate = coord;
|
|
112
|
+
trajectories[i].properties.rotation = rotationIcon;
|
|
210
113
|
|
|
211
|
-
if (
|
|
114
|
+
if (!coord) {
|
|
212
115
|
// eslint-disable-next-line no-continue
|
|
213
116
|
continue;
|
|
214
117
|
}
|
|
215
118
|
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
coord = traj.coordinate;
|
|
221
|
-
} else if (timeIntervals && timeIntervals.length > 1) {
|
|
222
|
-
const now = time - (timeOffset || 0);
|
|
223
|
-
let start;
|
|
224
|
-
let end;
|
|
225
|
-
let startFrac;
|
|
226
|
-
let endFrac;
|
|
227
|
-
let timeFrac;
|
|
228
|
-
|
|
229
|
-
// Search th time interval.
|
|
230
|
-
for (let j = 0; j < timeIntervals.length - 1; j += 1) {
|
|
231
|
-
// Rotation only available in tralis layer.
|
|
232
|
-
[start, startFrac, rotationIcon] = timeIntervals[j];
|
|
233
|
-
[end, endFrac] = timeIntervals[j + 1];
|
|
234
|
-
|
|
235
|
-
if (start <= now && now <= end) {
|
|
236
|
-
break;
|
|
237
|
-
} else {
|
|
238
|
-
start = null;
|
|
239
|
-
end = null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// The geometry can also be a Point
|
|
243
|
-
if (geometry.getType() === GeomType.POINT) {
|
|
244
|
-
coord = geometry.getCoordinates();
|
|
245
|
-
} else if (geometry.getType() === GeomType.LINE_STRING) {
|
|
246
|
-
if (start && end) {
|
|
247
|
-
// interpolate position inside the time interval.
|
|
248
|
-
timeFrac = this.interpolate
|
|
249
|
-
? Math.min((now - start) / (end - start), 1)
|
|
250
|
-
: 0;
|
|
251
|
-
|
|
252
|
-
const geomFrac = this.interpolate
|
|
253
|
-
? timeFrac * (endFrac - startFrac) + startFrac
|
|
254
|
-
: 0;
|
|
255
|
-
|
|
256
|
-
coord = geometry.getCoordinateAt(geomFrac);
|
|
257
|
-
|
|
258
|
-
// We set the rotation and the timeFraction of the trajectory (used by tralis).
|
|
259
|
-
this.trajectories[i].rotation = rotationIcon;
|
|
260
|
-
this.trajectories[i].endFraction = timeFrac;
|
|
261
|
-
|
|
262
|
-
// It happens that the now date was some ms before the first timeIntervals we have.
|
|
263
|
-
} else if (now < timeIntervals[0][0]) {
|
|
264
|
-
[[, , rotationIcon]] = timeIntervals;
|
|
265
|
-
timeFrac = 0;
|
|
266
|
-
coord = geometry.getFirstCoordinate();
|
|
267
|
-
} else if (now > timeIntervals[timeIntervals.length - 1][0]) {
|
|
268
|
-
[, , rotationIcon] = timeIntervals[timeIntervals.length - 1];
|
|
269
|
-
timeFrac = 1;
|
|
270
|
-
coord = geometry.getLastCoordinate();
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
// eslint-disable-next-line no-console
|
|
274
|
-
console.error(
|
|
275
|
-
'This geometry type is not supported. Only Point or LineString are. Current geometry: ',
|
|
276
|
-
geometry,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
// We set the rotation and the timeFraction of the trajectory (used by tralis).
|
|
280
|
-
// if rotation === null that seems there is no rotation available.
|
|
281
|
-
this.trajectories[i].rotation = rotationIcon;
|
|
282
|
-
this.trajectories[i].endFraction = timeFrac || 0;
|
|
119
|
+
let px = apply(coordinateToPixelTransform, [...coord]);
|
|
120
|
+
if (!px) {
|
|
121
|
+
// eslint-disable-next-line no-continue
|
|
122
|
+
continue;
|
|
283
123
|
}
|
|
284
124
|
|
|
285
|
-
|
|
286
|
-
// We set the rotation of the trajectory (used by tralis).
|
|
287
|
-
this.trajectories[i].coordinate = coord;
|
|
288
|
-
let px = apply(coordinateToPixelTransform, [...coord]);
|
|
289
|
-
if (!px) {
|
|
290
|
-
// eslint-disable-next-line no-continue
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
125
|
+
px = px.map((p) => p * pixelRatio);
|
|
293
126
|
|
|
294
|
-
|
|
127
|
+
if (
|
|
128
|
+
px[0] < 0 ||
|
|
129
|
+
px[0] > canvas.width ||
|
|
130
|
+
px[1] < 0 ||
|
|
131
|
+
px[1] > canvas.height
|
|
132
|
+
) {
|
|
133
|
+
// eslint-disable-next-line no-continue
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
295
136
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
) {
|
|
302
|
-
// eslint-disable-next-line no-continue
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
137
|
+
const vehicleImg = this.style(trajectory, viewState, options);
|
|
138
|
+
if (!vehicleImg) {
|
|
139
|
+
// eslint-disable-next-line no-continue
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
305
142
|
|
|
306
|
-
|
|
307
|
-
if (!vehicleImg) {
|
|
308
|
-
// eslint-disable-next-line no-continue
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
143
|
+
nbRendered += 1;
|
|
311
144
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.renderedTrajectories.push(this.trajectories[i]);
|
|
145
|
+
const imgWidth = vehicleImg.width;
|
|
146
|
+
const imgHeight = vehicleImg.height;
|
|
315
147
|
|
|
316
|
-
|
|
317
|
-
|
|
148
|
+
if (hoverVehicleId !== id && selectedVehicleId !== id) {
|
|
149
|
+
context.drawImage(
|
|
150
|
+
vehicleImg,
|
|
151
|
+
px[0] - imgWidth / 2,
|
|
152
|
+
px[1] - imgHeight / 2,
|
|
153
|
+
imgWidth,
|
|
154
|
+
imgHeight,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
318
157
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
px[1] - imgHeight / 2,
|
|
327
|
-
imgWidth,
|
|
328
|
-
imgHeight,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
if (this.hoverVehicleId === traj.id) {
|
|
332
|
-
// Store the canvas to draw it at the end
|
|
333
|
-
hoverVehicleImg = vehicleImg;
|
|
334
|
-
hoverVehiclePx = px;
|
|
335
|
-
hoverVehicleWidth = imgWidth;
|
|
336
|
-
hoverVehicleHeight = imgHeight;
|
|
337
|
-
}
|
|
158
|
+
if (hoverVehicleId && hoverVehicleId === id) {
|
|
159
|
+
// Store the canvas to draw it at the end
|
|
160
|
+
hoverVehicleImg = vehicleImg;
|
|
161
|
+
hoverVehiclePx = px;
|
|
162
|
+
hoverVehicleWidth = imgWidth;
|
|
163
|
+
hoverVehicleHeight = imgHeight;
|
|
164
|
+
}
|
|
338
165
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
166
|
+
if (selectedVehicleId && selectedVehicleId === id) {
|
|
167
|
+
// Store the canvas to draw it at the end
|
|
168
|
+
selectedVehicleImg = vehicleImg;
|
|
169
|
+
selectedVehiclePx = px;
|
|
170
|
+
selectedVehicleWidth = imgWidth;
|
|
171
|
+
selectedVehicleHeight = imgHeight;
|
|
346
172
|
}
|
|
347
173
|
}
|
|
348
174
|
|
|
349
175
|
if (selectedVehicleImg) {
|
|
350
|
-
|
|
176
|
+
context.drawImage(
|
|
351
177
|
selectedVehicleImg,
|
|
352
178
|
selectedVehiclePx[0] - selectedVehicleWidth / 2,
|
|
353
179
|
selectedVehiclePx[1] - selectedVehicleHeight / 2,
|
|
@@ -357,7 +183,7 @@ export default class Tracker {
|
|
|
357
183
|
}
|
|
358
184
|
|
|
359
185
|
if (hoverVehicleImg) {
|
|
360
|
-
|
|
186
|
+
context.drawImage(
|
|
361
187
|
hoverVehicleImg,
|
|
362
188
|
hoverVehiclePx[0] - hoverVehicleWidth / 2,
|
|
363
189
|
hoverVehiclePx[1] - hoverVehicleHeight / 2,
|
|
@@ -365,14 +191,8 @@ export default class Tracker {
|
|
|
365
191
|
hoverVehicleHeight,
|
|
366
192
|
);
|
|
367
193
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
* Clean the canvas and the events the tracker.
|
|
372
|
-
* @private
|
|
373
|
-
*/
|
|
374
|
-
destroy() {
|
|
375
|
-
this.renderedTrajectories = [];
|
|
376
|
-
this.clear();
|
|
194
|
+
return {
|
|
195
|
+
nbTrajectoriesRendered: nbRendered,
|
|
196
|
+
};
|
|
377
197
|
}
|
|
378
198
|
}
|