mobility-toolbox-js 1.7.8-beta.7 → 1.7.8-beta.8
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/api/trajserv/TrajservAPI.js +24 -0
- package/api/trajserv/TrajservAPIUtils.js +1 -3
- package/api/trajserv/fetchTrajectories.worker.js +23 -0
- package/api/tralis/TralisAPI.js +2 -0
- package/api/tralis/WebSocketConnector.js +3 -1
- package/common/interpolate.js +67 -0
- package/common/mixins/TrackerLayerMixin.js +3 -0
- package/common/mixins/TrajservLayerMixin.js +40 -2
- package/common/mixins/TralisLayerMixin.js +16 -1
- package/common/tracker.worker.js +93 -0
- package/common/trackerStyleConfig.js +134 -0
- package/common/utils/delayTrackerStyle.js +6 -4
- package/common/utils/index.js +1 -0
- package/common/utils/simpleTrackerStyle.js +4 -3
- package/fetchTrajectories.worker.worker.js +2 -0
- package/fetchTrajectories.worker.worker.js.map +1 -0
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/ol/layers/TrackerLayer.js +119 -34
- package/package.json +2 -1
- package/tracker.worker.worker.js +2 -0
- package/tracker.worker.worker.js.map +1 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import qs from 'query-string';
|
|
1
2
|
import {
|
|
2
3
|
translateTrajCollResponse,
|
|
3
4
|
translateTrajStationsResp,
|
|
4
5
|
} from './TrajservAPIUtils';
|
|
5
6
|
import API from '../../common/api/api';
|
|
7
|
+
import FetchTrajectoriesWorker from './fetchTrajectories.worker';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Access to the [Realtime service](https://developer.geops.io/apis/5dcbd5c9a256d90001cf1360/).
|
|
@@ -26,6 +28,7 @@ class TrajservAPI extends API {
|
|
|
26
28
|
*/
|
|
27
29
|
constructor(options = {}) {
|
|
28
30
|
super({ url: 'https://api.geops.io/tracker/v1', ...options });
|
|
31
|
+
this.fetchTrajectoriesWorker = new FetchTrajectoriesWorker();
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -41,6 +44,27 @@ class TrajservAPI extends API {
|
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Fetch trajectories using the worker.
|
|
49
|
+
*
|
|
50
|
+
* @param {GetTrajectoriesParams} params Request parameters. See [Realtime service documentation](https://developer.geops.io/apis/5dcbd5c9a256d90001cf1360/#/default/get_trajectory_collection).
|
|
51
|
+
* @param {AbortController} abortController Abort controller used to cancel the request.
|
|
52
|
+
// * @returns {Promise<Trajectory[]>} A list of trajectories.
|
|
53
|
+
*/
|
|
54
|
+
// eslint-disable-next-line class-methods-use-this
|
|
55
|
+
fetchTrajectoriesWorkerr(params) {
|
|
56
|
+
// Clean requets parameters, removing undefined and null values.
|
|
57
|
+
const urlParams = { ...params, key: this.apiKey };
|
|
58
|
+
const clone = { ...urlParams };
|
|
59
|
+
Object.keys(urlParams).forEach(
|
|
60
|
+
(key) =>
|
|
61
|
+
(clone[key] === undefined || clone[key] === null) && delete clone[key],
|
|
62
|
+
);
|
|
63
|
+
this.fetchTrajectoriesWorker.postMessage(
|
|
64
|
+
`${this.url}/trajectory_collection?${qs.stringify(clone)}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
44
68
|
/**
|
|
45
69
|
* Fetch trajectories.
|
|
46
70
|
*
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { LineString } from 'ol/geom';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Translate the response date object into a readable object.
|
|
5
3
|
* @return {Date[]}
|
|
@@ -132,7 +130,7 @@ export const translateTrajCollResponse = (features = []) => {
|
|
|
132
130
|
const trajectories = [];
|
|
133
131
|
for (let i = 0; i < features.length; i += 1) {
|
|
134
132
|
const traj = features[i];
|
|
135
|
-
const geometry =
|
|
133
|
+
const { geometry } = traj;
|
|
136
134
|
const {
|
|
137
135
|
ID: id,
|
|
138
136
|
ProductIdentifier: type,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { translateTrajCollResponse } from './TrajservAPIUtils';
|
|
2
|
+
|
|
3
|
+
let abortController = new AbortController();
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line no-restricted-globals, func-names
|
|
6
|
+
self.onmessage = function (evt) {
|
|
7
|
+
// console.log('Worker: Message received from main script', evt.data);
|
|
8
|
+
abortController.abort();
|
|
9
|
+
abortController = new AbortController();
|
|
10
|
+
fetch(evt.data, {
|
|
11
|
+
signal: abortController.signal,
|
|
12
|
+
})
|
|
13
|
+
.then((res) => res.json())
|
|
14
|
+
.then((data) => {
|
|
15
|
+
const a = translateTrajCollResponse(data.features);
|
|
16
|
+
// eslint-disable-next-line no-restricted-globals
|
|
17
|
+
self.postMessage(a);
|
|
18
|
+
})
|
|
19
|
+
.catch(() => {
|
|
20
|
+
// eslint-disable-next-line no-restricted-globals
|
|
21
|
+
// self.postMessage(null);
|
|
22
|
+
});
|
|
23
|
+
};
|
package/api/tralis/TralisAPI.js
CHANGED
|
@@ -312,7 +312,9 @@ class WebSocketConnector {
|
|
|
312
312
|
!this.subscriptions.find((s) => s.params.channel === source) &&
|
|
313
313
|
toRemove.find((subscr) => !subscr.quiet)
|
|
314
314
|
) {
|
|
315
|
-
this.
|
|
315
|
+
if (this.isDELAllow) {
|
|
316
|
+
this.send(`DEL ${source}`);
|
|
317
|
+
}
|
|
316
318
|
this.subscribed[source] = false;
|
|
317
319
|
}
|
|
318
320
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import GeomType from 'ol/geom/GeometryType';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interpolate a position along a geometry at a specific date.
|
|
5
|
+
*
|
|
6
|
+
* @param {number} now Current date to interpolate a position with. In ms.
|
|
7
|
+
* @param {ol/geom/LineString~LineString} geometry The geoemtry used to interpolate a position.
|
|
8
|
+
* @param {Array<Array<number,number,number>>} timeIntervals The time intervals used to interpolate a position, ex: [[dateInMs, fraction, rotation]...].
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
const interpolate = (now, geometry, timeIntervals) => {
|
|
12
|
+
let coord;
|
|
13
|
+
let start;
|
|
14
|
+
let end;
|
|
15
|
+
let startFrac;
|
|
16
|
+
let endFrac;
|
|
17
|
+
let timeFrac;
|
|
18
|
+
let rotation;
|
|
19
|
+
|
|
20
|
+
// Search th time interval.
|
|
21
|
+
for (let j = 0; j < timeIntervals.length - 1; j += 1) {
|
|
22
|
+
// Rotation only available in tralis layer.
|
|
23
|
+
[start, startFrac, rotation] = timeIntervals[j];
|
|
24
|
+
[end, endFrac] = timeIntervals[j + 1];
|
|
25
|
+
|
|
26
|
+
if (start <= now && now <= end) {
|
|
27
|
+
break;
|
|
28
|
+
} else {
|
|
29
|
+
start = null;
|
|
30
|
+
end = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// The geometry can also be a Point
|
|
34
|
+
if (geometry.getType() === GeomType.POINT) {
|
|
35
|
+
coord = geometry.getCoordinates();
|
|
36
|
+
} else if (geometry.getType() === GeomType.LINE_STRING) {
|
|
37
|
+
if (start && end) {
|
|
38
|
+
// interpolate position inside the time interval.
|
|
39
|
+
timeFrac = interpolate ? Math.min((now - start) / (end - start), 1) : 0;
|
|
40
|
+
|
|
41
|
+
const geomFrac = interpolate
|
|
42
|
+
? timeFrac * (endFrac - startFrac) + startFrac
|
|
43
|
+
: 0;
|
|
44
|
+
|
|
45
|
+
coord = geometry.getCoordinateAt(geomFrac);
|
|
46
|
+
|
|
47
|
+
// It happens that the now date was some ms before the first timeIntervals we have.
|
|
48
|
+
} else if (now < timeIntervals[0][0]) {
|
|
49
|
+
[[, , rotation]] = timeIntervals;
|
|
50
|
+
timeFrac = 0;
|
|
51
|
+
coord = geometry.getFirstCoordinate();
|
|
52
|
+
} else if (now > timeIntervals[timeIntervals.length - 1][0]) {
|
|
53
|
+
[, , rotation] = timeIntervals[timeIntervals.length - 1];
|
|
54
|
+
timeFrac = 1;
|
|
55
|
+
coord = geometry.getLastCoordinate();
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.error(
|
|
60
|
+
'This geometry type is not supported. Only Point or LineString are. Current geometry: ',
|
|
61
|
+
geometry,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return { coord, rotation, timeFrac };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default interpolate;
|
|
@@ -111,6 +111,19 @@ export class TrajservLayerInterface {
|
|
|
111
111
|
*/
|
|
112
112
|
const TrajservLayerMixin = (TrackerLayer) =>
|
|
113
113
|
class extends TrackerLayer {
|
|
114
|
+
constructor(options) {
|
|
115
|
+
super(options);
|
|
116
|
+
|
|
117
|
+
if (this.api.fetchTrajectoriesWorkerr) {
|
|
118
|
+
const that = this;
|
|
119
|
+
this.api.fetchTrajectoriesWorker.onmessage = (evt) => {
|
|
120
|
+
if (evt.data) {
|
|
121
|
+
that.onReceiveTrajectories(evt.data);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
/**
|
|
115
128
|
* Define layer's properties.
|
|
116
129
|
*
|
|
@@ -347,6 +360,11 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
347
360
|
this.abortFetchTrajectories();
|
|
348
361
|
this.abortController = new AbortController();
|
|
349
362
|
|
|
363
|
+
if (this.api.fetchTrajectoriesWorkerr) {
|
|
364
|
+
this.api.fetchTrajectoriesWorkerr(this.getParams({ attr_det: 1 }));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
350
368
|
this.api
|
|
351
369
|
.fetchTrajectories(
|
|
352
370
|
this.getParams({
|
|
@@ -364,12 +382,32 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
364
382
|
.then((trajectories) => {
|
|
365
383
|
// Don't set trajectories when the user has aborted the request.
|
|
366
384
|
if (trajectories) {
|
|
367
|
-
this.
|
|
368
|
-
this.
|
|
385
|
+
if (this.worker) {
|
|
386
|
+
this.onReceiveTrajectories(trajectories);
|
|
387
|
+
} else {
|
|
388
|
+
this.trajectories = trajectories;
|
|
389
|
+
this.renderTrajectories();
|
|
390
|
+
}
|
|
369
391
|
}
|
|
370
392
|
});
|
|
371
393
|
}
|
|
372
394
|
|
|
395
|
+
onReceiveTrajectories(trajectories) {
|
|
396
|
+
// Don't set trajectories when the user has aborted the request.
|
|
397
|
+
if (trajectories) {
|
|
398
|
+
this.tracker.setTrajectories(trajectories);
|
|
399
|
+
|
|
400
|
+
if (this.worker) {
|
|
401
|
+
this.worker.postMessage({
|
|
402
|
+
action: 'sendData',
|
|
403
|
+
trajectories,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.renderTrajectories();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
373
411
|
/**
|
|
374
412
|
* Draw the trajectory as a line with points for each stop.
|
|
375
413
|
*
|
|
@@ -243,12 +243,26 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
243
243
|
if (this.filter && !this.filter(trajectory)) {
|
|
244
244
|
return;
|
|
245
245
|
}
|
|
246
|
+
|
|
246
247
|
this.trajectories[trajectory.properties.train_id] = trajectory;
|
|
247
248
|
this.renderTrajectories();
|
|
249
|
+
|
|
250
|
+
if (this.worker) {
|
|
251
|
+
this.worker.postMessage({
|
|
252
|
+
action: 'addTrajectory',
|
|
253
|
+
trajectory,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
removeTrajectory(id) {
|
|
251
259
|
delete this.trajectories[id];
|
|
260
|
+
if (this.worker) {
|
|
261
|
+
this.worker.postMessage({
|
|
262
|
+
action: 'removeTrajectory',
|
|
263
|
+
trajectoryId: id,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
252
266
|
}
|
|
253
267
|
|
|
254
268
|
// getRefreshTimeInMs() {
|
|
@@ -298,7 +312,8 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
298
312
|
this.map.getView().getProjection(),
|
|
299
313
|
),
|
|
300
314
|
};
|
|
301
|
-
} else {
|
|
315
|
+
} else if (!this.worker) {
|
|
316
|
+
// We can't pass the olGeometry to the worker because it's not serializable.
|
|
302
317
|
trajectory.properties.olGeometry = this.format.readGeometry(geometry);
|
|
303
318
|
}
|
|
304
319
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import stringify from 'json-stringify-safe';
|
|
2
|
+
import GeoJSON from 'ol/format/GeoJSON';
|
|
3
|
+
import { delayTrackerStyle } from './utils';
|
|
4
|
+
import Tracker from './Tracker';
|
|
5
|
+
|
|
6
|
+
const debug = false;
|
|
7
|
+
|
|
8
|
+
const trajectories = {};
|
|
9
|
+
let renderTimeout;
|
|
10
|
+
let count = 0;
|
|
11
|
+
const format = new GeoJSON();
|
|
12
|
+
const tracker = new Tracker({
|
|
13
|
+
canvas: new OffscreenCanvas(1, 1),
|
|
14
|
+
width: 1,
|
|
15
|
+
height: 1,
|
|
16
|
+
style: delayTrackerStyle,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const render = (evt) => {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
if (debug) console.time('render');
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
if (debug) console.log('render', evt.data.frameState);
|
|
24
|
+
count = 0;
|
|
25
|
+
const { frameState, viewState, options } = evt.data;
|
|
26
|
+
|
|
27
|
+
const { nbTrajectoriesRendered } = tracker.renderTrajectories(
|
|
28
|
+
Object.values(trajectories),
|
|
29
|
+
viewState,
|
|
30
|
+
options,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (debug) console.timeEnd('render');
|
|
34
|
+
if (debug) console.log('NUMBER OF STYLES CREATED', count);
|
|
35
|
+
|
|
36
|
+
const imageData = tracker.canvas.transferToImageBitmap();
|
|
37
|
+
const state = { ...frameState };
|
|
38
|
+
delete state.layerStatesArray;
|
|
39
|
+
delete state.viewState.projection;
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line no-restricted-globals
|
|
42
|
+
self.postMessage(
|
|
43
|
+
{
|
|
44
|
+
action: 'rendered',
|
|
45
|
+
imageData,
|
|
46
|
+
// transform: rendererTransform,
|
|
47
|
+
nbRenderedTrajectories: nbTrajectoriesRendered,
|
|
48
|
+
frameState: JSON.parse(stringify(state)),
|
|
49
|
+
},
|
|
50
|
+
[imageData],
|
|
51
|
+
);
|
|
52
|
+
renderTimeout = null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line no-restricted-globals
|
|
56
|
+
self.onmessage = (evt) => {
|
|
57
|
+
// debugger;
|
|
58
|
+
if (evt.data.action === 'addTrajectory') {
|
|
59
|
+
const { trajectory } = evt.data;
|
|
60
|
+
const id = trajectory.properties.train_id;
|
|
61
|
+
trajectories[id] = trajectory;
|
|
62
|
+
trajectories[id].properties.olGeometry = format.readGeometry(
|
|
63
|
+
trajectory.geometry,
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (evt.data.action === 'removeTrajectory') {
|
|
69
|
+
delete trajectories[evt.data.trajectoryId];
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// if (evt.data.action === 'sendData') {
|
|
74
|
+
// // eslint-disable-next-line no-console
|
|
75
|
+
// if (debug) console.log('sendData', evt.data);
|
|
76
|
+
// if (debug) console.time('sendData');
|
|
77
|
+
// trajectories = evt.data.trajectories;
|
|
78
|
+
// if (debug) console.timeEnd('sendData');
|
|
79
|
+
// return;
|
|
80
|
+
// }
|
|
81
|
+
|
|
82
|
+
if (evt.data.action !== 'render') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (renderTimeout) {
|
|
87
|
+
clearTimeout(renderTimeout);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
renderTimeout = setTimeout(() => {
|
|
91
|
+
render(evt);
|
|
92
|
+
}, 0);
|
|
93
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const cacheDelayBg = {};
|
|
2
|
+
|
|
3
|
+
// Draw circle delay background
|
|
4
|
+
export const getDelayBgCanvas = (origin, radius, color) => {
|
|
5
|
+
const key = `${origin}, ${radius}, ${color}`;
|
|
6
|
+
if (!cacheDelayBg[key]) {
|
|
7
|
+
// console.log('cacheDelayBg');
|
|
8
|
+
const canvas = new OffscreenCanvas(origin * 2, origin * 2);
|
|
9
|
+
const ctx = canvas.getContext('2d');
|
|
10
|
+
ctx.beginPath();
|
|
11
|
+
ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);
|
|
12
|
+
ctx.fillStyle = color;
|
|
13
|
+
ctx.filter = 'blur(1px)';
|
|
14
|
+
ctx.fill();
|
|
15
|
+
cacheDelayBg[key] = canvas;
|
|
16
|
+
}
|
|
17
|
+
return cacheDelayBg[key];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Draw delay text
|
|
21
|
+
const cacheDelayText = {};
|
|
22
|
+
export const getDelayTextCanvas = (
|
|
23
|
+
width,
|
|
24
|
+
text,
|
|
25
|
+
fontSize,
|
|
26
|
+
font,
|
|
27
|
+
delayColor,
|
|
28
|
+
delayOutlineColor,
|
|
29
|
+
pixelRatio,
|
|
30
|
+
) => {
|
|
31
|
+
const key = `${width}, ${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`;
|
|
32
|
+
if (!cacheDelayText[key]) {
|
|
33
|
+
const canvas = new OffscreenCanvas(width, fontSize + 8);
|
|
34
|
+
const ctx = canvas.getContext('2d');
|
|
35
|
+
ctx.textAlign = 'left';
|
|
36
|
+
ctx.textBaseline = 'middle';
|
|
37
|
+
ctx.font = font;
|
|
38
|
+
ctx.fillStyle = delayColor;
|
|
39
|
+
ctx.strokeStyle = delayOutlineColor;
|
|
40
|
+
ctx.lineWidth = 1.5 * pixelRatio;
|
|
41
|
+
const delayText = text;
|
|
42
|
+
ctx.strokeText(delayText, 0, fontSize);
|
|
43
|
+
ctx.fillText(delayText, 0, fontSize);
|
|
44
|
+
cacheDelayText[key] = canvas;
|
|
45
|
+
}
|
|
46
|
+
return cacheDelayText[key];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Draw colored circle with black border
|
|
50
|
+
const cacheCircle = {};
|
|
51
|
+
export const getCircleCanvas = (
|
|
52
|
+
origin,
|
|
53
|
+
radius,
|
|
54
|
+
color,
|
|
55
|
+
hasStroke,
|
|
56
|
+
hasDash,
|
|
57
|
+
pixelRatio,
|
|
58
|
+
) => {
|
|
59
|
+
const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`;
|
|
60
|
+
if (!cacheCircle[key]) {
|
|
61
|
+
// console.log('cacheDelayBg');
|
|
62
|
+
const canvas = new OffscreenCanvas(origin * 2, origin * 2);
|
|
63
|
+
const ctx = canvas.getContext('2d');
|
|
64
|
+
ctx.fillStyle = color;
|
|
65
|
+
|
|
66
|
+
if (hasStroke) {
|
|
67
|
+
ctx.lineWidth = 1 * pixelRatio;
|
|
68
|
+
ctx.strokeStyle = '#000000';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ctx.beginPath();
|
|
72
|
+
ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);
|
|
73
|
+
ctx.fill();
|
|
74
|
+
|
|
75
|
+
if (hasDash) {
|
|
76
|
+
ctx.setLineDash([5, 3]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (hasStroke) {
|
|
80
|
+
ctx.stroke();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cacheCircle[key] = canvas;
|
|
84
|
+
}
|
|
85
|
+
return cacheCircle[key];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Draw text in the circle
|
|
89
|
+
const cacheText = {};
|
|
90
|
+
export const getTextCanvas = (
|
|
91
|
+
text,
|
|
92
|
+
origin,
|
|
93
|
+
textSize,
|
|
94
|
+
fillColor,
|
|
95
|
+
strokeColor,
|
|
96
|
+
hasStroke,
|
|
97
|
+
pixelRatio,
|
|
98
|
+
) => {
|
|
99
|
+
const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`;
|
|
100
|
+
if (!cacheText[key]) {
|
|
101
|
+
const canvas = new OffscreenCanvas(origin * 2, origin * 2);
|
|
102
|
+
const ctx = canvas.getContext('2d');
|
|
103
|
+
|
|
104
|
+
// Draw a stroke to the text only if a provider provides realtime but we don't use it.
|
|
105
|
+
if (hasStroke) {
|
|
106
|
+
ctx.save();
|
|
107
|
+
ctx.textBaseline = 'middle';
|
|
108
|
+
ctx.textAlign = 'center';
|
|
109
|
+
ctx.font = `bold ${textSize + 2}px Arial`;
|
|
110
|
+
ctx.strokeStyle = strokeColor;
|
|
111
|
+
ctx.strokeText(text, origin, origin);
|
|
112
|
+
ctx.restore();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Draw a text
|
|
116
|
+
ctx.textBaseline = 'middle';
|
|
117
|
+
ctx.textAlign = 'center';
|
|
118
|
+
ctx.fillStyle = fillColor;
|
|
119
|
+
ctx.font = `bold ${textSize}px Arial`;
|
|
120
|
+
ctx.strokeStyle = strokeColor;
|
|
121
|
+
ctx.strokeText(text, origin, origin);
|
|
122
|
+
ctx.fillText(text, origin, origin);
|
|
123
|
+
|
|
124
|
+
cacheText[key] = canvas;
|
|
125
|
+
}
|
|
126
|
+
return cacheText[key];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default {
|
|
130
|
+
getDelayBgCanvas,
|
|
131
|
+
getDelayTextCanvas,
|
|
132
|
+
getCircleCanvas,
|
|
133
|
+
getTextCanvas,
|
|
134
|
+
};
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
} from '../trackerConfig';
|
|
9
9
|
|
|
10
10
|
const createCanvas = (width, height) => {
|
|
11
|
-
|
|
12
|
-
canvas
|
|
13
|
-
canvas.
|
|
14
|
-
|
|
11
|
+
return new OffscreenCanvas(width, height);
|
|
12
|
+
// const canvas = document.createElement('canvas');
|
|
13
|
+
// canvas.width = width;
|
|
14
|
+
// canvas.height = height;
|
|
15
|
+
// return canvas;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
// Draw circle delay background
|
|
@@ -204,6 +205,7 @@ const style = (trajectory, viewState, options) => {
|
|
|
204
205
|
? radius + 5 * pixelRatio
|
|
205
206
|
: 14 * pixelRatio;
|
|
206
207
|
}
|
|
208
|
+
|
|
207
209
|
const mustDrawText = radius > 10 * pixelRatio;
|
|
208
210
|
|
|
209
211
|
// Optimize the cache key, very important in high zoom level
|
package/common/utils/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { default as delayTrackerStyle } from './delayTrackerStyle';
|
|
|
5
5
|
export * from './delayTrackerStyle';
|
|
6
6
|
export { default as simpleTrackerStyle } from './simpleTrackerStyle';
|
|
7
7
|
export * from './timeUtils';
|
|
8
|
+
export { default as getVehiclePosition } from './getVehiclePosition';
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* A very simple tracker style.
|
|
3
3
|
* Display blue point for each train.
|
|
4
4
|
*/
|
|
5
|
-
const canvas = document.createElement('canvas');
|
|
6
|
-
canvas.width = 15;
|
|
7
|
-
canvas.height = 15;
|
|
5
|
+
// const canvas = document.createElement('canvas');
|
|
6
|
+
// canvas.width = 15;
|
|
7
|
+
// canvas.height = 15;
|
|
8
|
+
const canvas = new OffscreenCanvas(15, 15);
|
|
8
9
|
const ctx = canvas.getContext('2d');
|
|
9
10
|
ctx.arc(8, 8, 5, 0, 2 * Math.PI, false);
|
|
10
11
|
ctx.fillStyle = '#8ED6FF';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r,t){"use strict";t.r(r);var n=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],r=[],t=0;t<e.length;t+=1){var n=e[t],o=n.geometry,i=n.properties,l=i.ID,a=i.ProductIdentifier,u=i.PublishedLineName,c=i.RouteIdentifier,f=i.DirectionText,s=i.Operator,d=i.OperatorURL,p=i.Publisher,b=i.PublisherURL,y=i.License,v=i.LicenseUrl,m=i.LicenseNote,g=i.Color,O=i.JourneyIdentifier,h=i.RealtimeAvailable,P=i.OperatorProvidesRealtime,j=i.DayOfOperation,x=i.Delay,I=i.TimeIntervals,C=i.TextColor,L=i.Cancelled;r.push({id:l,type:a,name:u,routeIdentifier:c,directionText:f,operator:s,operatorUrl:d,publisher:p,publisherUrl:b,license:y,licenseUrl:v,licenseNote:m,journeyIdentifier:O,realtimeAvailable:h,operatorProvidesRealtime:P,dayOfOperation:j,delay:x,timeIntervals:I,color:g&&"#".concat(g),textColor:C&&"#".concat(C),geometry:o,cancelled:L})}return r},o=new AbortController;self.onmessage=function(e){o.abort(),o=new AbortController,fetch(e.data,{signal:o.signal}).then((function(e){return e.json()})).then((function(e){var r=n(e.features);self.postMessage(r)})).catch((function(){}))}}]);
|
|
2
|
+
//# sourceMappingURL=fetchTrajectories.worker.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["webpack://mobility-toolbox-js/webpack/bootstrap","webpack://mobility-toolbox-js/./src/api/trajserv/TrajservAPIUtils.js","webpack://mobility-toolbox-js/./src/api/trajserv/fetchTrajectories.worker.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","translateTrajCollResponse","features","trajectories","length","traj","geometry","properties","id","ID","type","ProductIdentifier","PublishedLineName","routeIdentifier","RouteIdentifier","directionText","DirectionText","operator","Operator","operatorUrl","OperatorURL","publisher","Publisher","publisherUrl","PublisherURL","license","License","licenseUrl","LicenseUrl","licenseNote","LicenseNote","color","Color","journeyIdentifier","JourneyIdentifier","realtimeAvailable","RealtimeAvailable","operatorProvidesRealtime","OperatorProvidesRealtime","dayOfOperation","DayOfOperation","delay","Delay","timeIntervals","TimeIntervals","textColor","TextColor","cancelled","Cancelled","push","abortController","AbortController","self","onmessage","evt","abort","fetch","data","signal","then","res","json","a","postMessage","catch"],"mappings":"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","file":"fetchTrajectories.worker.worker.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n","/**\n * Translate the response date object into a readable object.\n * @return {Date[]}\n * @ignore\n */\nconst translateDates = (dates = []) => {\n const newDates = [];\n\n for (let i = 0; i < dates.length; i += 1) {\n const { d: day, m: month, y: year } = dates[i];\n newDates.push({\n day,\n month,\n year,\n });\n }\n return newDates;\n};\n\n/**\n * Translate the trajstations response into a readable object.\n * @return {Object} returns a readable object\n * @private\n */\nexport const translateTrajStationsResp = (data) => {\n const newData = { ...data };\n\n // MAke sure all property exists.\n ['a', 'f', 'tt'].forEach((prop) => {\n if (!newData[prop]) {\n newData[prop] = {};\n }\n });\n\n const {\n id,\n hs: destination,\n t: vehicleType,\n ln: longName,\n sn: shortName,\n wa: wheelchairAccessible,\n ba: bicyclesAllowed,\n rt: realTime,\n fid: feedsId,\n rid: routeIdentifier,\n c: bgColor,\n tc: datacolor,\n a: { n: operator, u: operatorUrl, tz: operatorTimeZone },\n f: { n: publisher, u: publisherUrl, tz: publisherTimeZone },\n tt: {\n n: dateNotOperatingDays,\n p: dateAdditionalOperatingDays,\n t: operatingPeriod,\n },\n sts: dataStations,\n } = newData;\n\n const notOperatingDays = translateDates(dateNotOperatingDays);\n const additionalOperatingDays = translateDates(dateAdditionalOperatingDays);\n const backgroundColor = bgColor && `#${bgColor}`;\n const color = datacolor && `#${datacolor}`;\n\n const stations = [];\n for (let i = 0; i < (dataStations || []).length; i += 1) {\n const {\n sid: stationId,\n n: stationName,\n p: coordinates,\n at: arrivalTime,\n dt: departureTime,\n ap: arrivalDate,\n dp: departureDate,\n ad: arrivalDelay,\n dd: departureDelay,\n dot: noDropOff,\n put: noPickUp,\n c: cancelled,\n wa: stWheelchairAccessible,\n } = dataStations[i];\n\n stations.push({\n stationId,\n stationName,\n coordinates,\n arrivalTime: arrivalTime !== -1 ? arrivalDate * 1000 : null,\n departureTime: departureTime !== -1 ? departureDate * 1000 : null,\n arrivalDelay,\n departureDelay,\n noDropOff: !!noDropOff,\n noPickUp: !!noPickUp,\n cancelled: !!cancelled,\n wheelchairAccessible: !!stWheelchairAccessible,\n });\n }\n\n return {\n id,\n destination,\n backgroundColor,\n color,\n vehicleType,\n routeIdentifier,\n longName,\n shortName,\n stations,\n wheelchairAccessible: !!wheelchairAccessible,\n bicyclesAllowed: !!bicyclesAllowed,\n realTime,\n feedsId,\n operatingInformations: {\n operatingPeriod,\n notOperatingDays,\n additionalOperatingDays,\n },\n operator,\n operatorUrl,\n operatorTimeZone,\n publisher,\n publisherUrl,\n publisherTimeZone,\n };\n};\n\n/**\n * Translate the trajectory_collection response into a js usable object.\n * @return {Array} returns an array of trajectories.\n * @ignore\n */\nexport const translateTrajCollResponse = (features = []) => {\n const trajectories = [];\n for (let i = 0; i < features.length; i += 1) {\n const traj = features[i];\n const { geometry } = traj;\n const {\n ID: id,\n ProductIdentifier: type,\n PublishedLineName: name,\n RouteIdentifier: routeIdentifier,\n DirectionText: directionText,\n Operator: operator,\n OperatorURL: operatorUrl,\n Publisher: publisher,\n PublisherURL: publisherUrl,\n License: license,\n LicenseUrl: licenseUrl,\n LicenseNote: licenseNote,\n Color: color,\n JourneyIdentifier: journeyIdentifier,\n RealtimeAvailable: realtimeAvailable,\n OperatorProvidesRealtime: operatorProvidesRealtime,\n DayOfOperation: dayOfOperation,\n Delay: delay,\n TimeIntervals: timeIntervals,\n TextColor: textColor,\n Cancelled: cancelled,\n } = traj.properties;\n\n trajectories.push({\n id,\n type,\n name,\n routeIdentifier,\n directionText,\n operator,\n operatorUrl,\n publisher,\n publisherUrl,\n license,\n licenseUrl,\n licenseNote,\n journeyIdentifier,\n realtimeAvailable,\n operatorProvidesRealtime,\n dayOfOperation,\n delay,\n timeIntervals,\n color: color && `#${color}`,\n textColor: textColor && `#${textColor}`,\n geometry,\n cancelled,\n });\n }\n return trajectories;\n};\n\nexport default {\n translateTrajCollResponse,\n translateTrajStationsResp,\n};\n","import { translateTrajCollResponse } from './TrajservAPIUtils';\n\nlet abortController = new AbortController();\n\n// eslint-disable-next-line no-restricted-globals, func-names\nself.onmessage = function (evt) {\n // console.log('Worker: Message received from main script', evt.data);\n abortController.abort();\n abortController = new AbortController();\n fetch(evt.data, {\n signal: abortController.signal,\n })\n .then((res) => res.json())\n .then((data) => {\n const a = translateTrajCollResponse(data.features);\n // eslint-disable-next-line no-restricted-globals\n self.postMessage(a);\n })\n .catch(() => {\n // eslint-disable-next-line no-restricted-globals\n // self.postMessage(null);\n });\n};\n"],"sourceRoot":""}
|