mobility-toolbox-js 1.6.0-beta.2 → 1.6.0-beta.6
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/tralis/TralisAPI.js +4 -1
- package/api/tralis/WebSocketConnector.js +16 -9
- package/common/layers/Layer.js +10 -7
- package/common/layers/Layer.test.js +27 -0
- package/common/mixins/TrajservLayerMixin.js +77 -20
- package/common/mixins/TralisLayerMixin.js +39 -10
- package/common/utils/index.js +1 -0
- package/common/utils/trackerStyle.js +183 -0
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/mapbox/layers/Layer.js +32 -6
- package/mapbox/layers/Layer.test.js +122 -0
- package/mapbox/layers/TrackerLayer.js +3 -3
- package/mapbox/layers/TrajservLayer.js +4 -88
- package/ol/layers/Layer.js +39 -8
- package/ol/layers/Layer.test.js +81 -0
- package/ol/layers/TrackerLayer.js +24 -7
- package/ol/layers/TrackerLayer.test.js +8 -0
- package/ol/layers/TrajservLayer.js +34 -126
- package/ol/layers/TralisLayer.js +92 -0
- package/package.json +1 -1
package/api/tralis/TralisAPI.js
CHANGED
|
@@ -75,11 +75,14 @@ class TralisAPI {
|
|
|
75
75
|
/** @ignore */
|
|
76
76
|
this.prefix = options.prefix || '';
|
|
77
77
|
|
|
78
|
-
this.isUpdateBboxOnMoveEnd = options.
|
|
78
|
+
this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false;
|
|
79
79
|
|
|
80
80
|
/** @ignore */
|
|
81
81
|
this.conn = new WebSocketConnector(wsUrl);
|
|
82
82
|
|
|
83
|
+
this.conn.isSUBAllow = !this.isUpdateBboxOnMoveEnd;
|
|
84
|
+
this.conn.isDELAllow = !this.isUpdateBboxOnMoveEnd;
|
|
85
|
+
|
|
83
86
|
if (!this.isUpdateBboxOnMoveEnd) {
|
|
84
87
|
this.conn.setProjection(options.projection || 'epsg:3857');
|
|
85
88
|
|
|
@@ -11,6 +11,9 @@ class WebSocketConnector {
|
|
|
11
11
|
this.subscriptions = [];
|
|
12
12
|
this.connect(url);
|
|
13
13
|
|
|
14
|
+
this.isSUBAllow = false;
|
|
15
|
+
this.isDELAllow = false;
|
|
16
|
+
|
|
14
17
|
// keep websocket alive
|
|
15
18
|
setInterval(() => {
|
|
16
19
|
this.send('PING');
|
|
@@ -52,13 +55,13 @@ class WebSocketConnector {
|
|
|
52
55
|
// this.setProjection(this.currentProj);
|
|
53
56
|
// }
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
[...this.subscriptions].forEach((s) => {
|
|
59
|
+
this.subscribe(s.params, s.cb, s.errorCb, s.quiet);
|
|
60
|
+
});
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if (this.currentBbox) {
|
|
63
|
+
this.setBbox(this.currentBbox);
|
|
64
|
+
}
|
|
62
65
|
|
|
63
66
|
// reconnect on close
|
|
64
67
|
this.websocket.onclose = () => {
|
|
@@ -205,8 +208,10 @@ class WebSocketConnector {
|
|
|
205
208
|
// if (!newSubscr.quiet) {
|
|
206
209
|
this.send(`GET ${reqStr}`);
|
|
207
210
|
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
if (this.isSUBAllow) {
|
|
212
|
+
this.send(`SUB ${reqStr}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
210
215
|
this.subscribed[reqStr] = true;
|
|
211
216
|
}
|
|
212
217
|
}
|
|
@@ -240,7 +245,9 @@ class WebSocketConnector {
|
|
|
240
245
|
this.subscribed[source] &&
|
|
241
246
|
!this.subscriptions.find((s) => s.params.channel === source)
|
|
242
247
|
) {
|
|
243
|
-
this.
|
|
248
|
+
if (this.isDELAllow) {
|
|
249
|
+
this.send(`DEL ${source}`);
|
|
250
|
+
}
|
|
244
251
|
this.subscribed[source] = false;
|
|
245
252
|
}
|
|
246
253
|
}
|
package/common/layers/Layer.js
CHANGED
|
@@ -13,7 +13,9 @@ import { v4 as uuid } from 'uuid';
|
|
|
13
13
|
* @classproperty {string} key - Identifier of the layer. Must be unique.
|
|
14
14
|
* @classproperty {string[]} copyrights - Array of copyrights.
|
|
15
15
|
* @classproperty {boolean} isBaseLayer - Define if the layer is a base layer. Read-only.
|
|
16
|
-
* @classproperty {boolean} isQueryable - Define if the layer can be queried. Read-only.
|
|
16
|
+
* @classproperty {boolean} isQueryable - Define if the layer can be queried. If false, it will set isHoverActive and isClickActive to false. Read-only.
|
|
17
|
+
* @classproperty {boolean} isClickActive - If true feature information will be queried on user click event. See inherited layers for more informations. Read-only.
|
|
18
|
+
* @classproperty {boolean} isHoverActive - If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
|
|
17
19
|
* @classproperty {boolean} isReactSpatialLayer - Custom property for duck typing since `instanceof` is not working when the instance was created on different bundles. Read-only.
|
|
18
20
|
* @classproperty {Layer[]} children - List of children.
|
|
19
21
|
* @classproperty {boolean} visible - Define if the layer is visible or not.
|
|
@@ -33,9 +35,9 @@ export default class Layer extends Observable {
|
|
|
33
35
|
* @param {Object} [options.properties={}] Application-specific layer properties.
|
|
34
36
|
* @param {boolean} [options.visible=true] If true this layer is visible on the map.
|
|
35
37
|
* @param {boolean} [options.isBaseLayer=false] If true this layer is a baseLayer.
|
|
36
|
-
* @param {boolean} [options.isQueryable=true]
|
|
37
|
-
* @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations.
|
|
38
|
-
* @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations.
|
|
38
|
+
* @param {boolean} [options.isQueryable=true] Define if the layer can be queried. If false, it will also set isHoverActive and isClickActive to false. Read-only.
|
|
39
|
+
* @param {boolean} [options.isClickActive=true] If true feature information will be queried on click event. See inherited layers for more informations. Read-only.
|
|
40
|
+
* @param {boolean} [options.isHoverActive=true] If true feature information will be queried on pointer move event. See inherited layers for more informations. Read-only.
|
|
39
41
|
* @param {number} [options.hitTolerance=5] Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.
|
|
40
42
|
*/
|
|
41
43
|
constructor(options = {}) {
|
|
@@ -109,10 +111,10 @@ export default class Layer extends Observable {
|
|
|
109
111
|
value: !!isQueryable,
|
|
110
112
|
},
|
|
111
113
|
isClickActive: {
|
|
112
|
-
value: !!isClickActive,
|
|
114
|
+
value: !!isQueryable && !!isClickActive,
|
|
113
115
|
},
|
|
114
116
|
isHoverActive: {
|
|
115
|
-
value: !!isHoverActive,
|
|
117
|
+
value: !!isQueryable && !!isHoverActive,
|
|
116
118
|
},
|
|
117
119
|
hitTolerance: {
|
|
118
120
|
value: hitTolerance || 5,
|
|
@@ -307,6 +309,7 @@ export default class Layer extends Observable {
|
|
|
307
309
|
// eslint-disable-next-line no-console
|
|
308
310
|
console.error(
|
|
309
311
|
'getFeatureInfoAtCoordinate must be implemented by inheriting layers',
|
|
312
|
+
this.key,
|
|
310
313
|
);
|
|
311
314
|
|
|
312
315
|
// This layer returns no feature info.
|
|
@@ -427,7 +430,7 @@ export default class Layer extends Observable {
|
|
|
427
430
|
.then((featureInfo) => {
|
|
428
431
|
const { features, layer, coordinate } = featureInfo;
|
|
429
432
|
this.hoverCallbacks.forEach((callback) =>
|
|
430
|
-
callback(
|
|
433
|
+
callback(features, layer, coordinate),
|
|
431
434
|
);
|
|
432
435
|
return featureInfo;
|
|
433
436
|
})
|
|
@@ -63,6 +63,21 @@ describe('Layer', () => {
|
|
|
63
63
|
expect(layer.name).toEqual('Layer');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
test('should set isClickActive and isHoverActive to false if isQueryable is set to false.', () => {
|
|
67
|
+
const options = {
|
|
68
|
+
name: 'Layer',
|
|
69
|
+
isQueryable: false,
|
|
70
|
+
isClickActive: true,
|
|
71
|
+
isHoverActive: true,
|
|
72
|
+
olLayer,
|
|
73
|
+
};
|
|
74
|
+
const layer = new Layer(options);
|
|
75
|
+
expect(layer).toBeInstanceOf(Layer);
|
|
76
|
+
expect(layer.isQueryable).toBe(false);
|
|
77
|
+
expect(layer.isClickActive).toBe(false);
|
|
78
|
+
expect(layer.isHoverActive).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
66
81
|
test('should called terminate on initialization.', () => {
|
|
67
82
|
const layer = new Layer({ name: 'Layer', olLayer });
|
|
68
83
|
const spy = jest.spyOn(layer, 'terminate');
|
|
@@ -465,7 +480,13 @@ describe('Layer', () => {
|
|
|
465
480
|
expect(spy).toHaveBeenCalledWith(evt.coordinate);
|
|
466
481
|
expect(featureInfo).toBe(goodFeatureInfo);
|
|
467
482
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
483
|
+
expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
484
|
+
expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
485
|
+
expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
468
486
|
expect(fn2).toHaveBeenCalledTimes(1);
|
|
487
|
+
expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
488
|
+
expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
489
|
+
expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
469
490
|
done();
|
|
470
491
|
});
|
|
471
492
|
});
|
|
@@ -599,7 +620,13 @@ describe('Layer', () => {
|
|
|
599
620
|
expect(spy).toHaveBeenCalledWith(evt.coordinate);
|
|
600
621
|
expect(featureInfo).toBe(goodFeatureInfo);
|
|
601
622
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
623
|
+
expect(fn.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
624
|
+
expect(fn.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
625
|
+
expect(fn.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
602
626
|
expect(fn2).toHaveBeenCalledTimes(1);
|
|
627
|
+
expect(fn2.mock.calls[0][0]).toBe(goodFeatureInfo.features);
|
|
628
|
+
expect(fn2.mock.calls[0][1]).toBe(goodFeatureInfo.layer);
|
|
629
|
+
expect(fn2.mock.calls[0][2]).toBe(goodFeatureInfo.coordinate);
|
|
603
630
|
done();
|
|
604
631
|
});
|
|
605
632
|
});
|
|
@@ -302,7 +302,6 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
stop() {
|
|
305
|
-
this.journeyId = null;
|
|
306
305
|
this.stopUpdateTrajectories();
|
|
307
306
|
this.abortFetchTrajectories();
|
|
308
307
|
super.stop();
|
|
@@ -314,10 +313,8 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
314
313
|
* @private
|
|
315
314
|
* @override
|
|
316
315
|
*/
|
|
317
|
-
onFeatureHover(
|
|
318
|
-
const
|
|
319
|
-
features: [feature],
|
|
320
|
-
} = featureInfo;
|
|
316
|
+
onFeatureHover(features, layer, coordinate) {
|
|
317
|
+
const [feature] = features;
|
|
321
318
|
let id = null;
|
|
322
319
|
if (feature) {
|
|
323
320
|
id = feature.get('id');
|
|
@@ -327,7 +324,7 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
327
324
|
this.hoverVehicleId = id;
|
|
328
325
|
this.renderTrajectories();
|
|
329
326
|
}
|
|
330
|
-
super.onFeatureHover(
|
|
327
|
+
super.onFeatureHover(features, layer, coordinate);
|
|
331
328
|
}
|
|
332
329
|
|
|
333
330
|
/**
|
|
@@ -336,20 +333,78 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
336
333
|
* @private
|
|
337
334
|
* @override
|
|
338
335
|
*/
|
|
339
|
-
onFeatureClick(
|
|
340
|
-
const
|
|
341
|
-
features: [feature],
|
|
342
|
-
} = featureInfo;
|
|
336
|
+
onFeatureClick(features, layer, coordinate) {
|
|
337
|
+
const [feature] = features;
|
|
343
338
|
if (feature) {
|
|
344
339
|
/** @ignore */
|
|
345
340
|
this.selectedVehicleId = feature.get('id');
|
|
346
341
|
/** @ignore */
|
|
347
342
|
this.journeyId = feature.get('journeyIdentifier');
|
|
348
|
-
this.
|
|
343
|
+
this.highlightTrajectory();
|
|
349
344
|
} else {
|
|
350
345
|
this.selectedVehicleId = null;
|
|
346
|
+
this.journeyId = null;
|
|
351
347
|
}
|
|
352
|
-
super.onFeatureClick(
|
|
348
|
+
super.onFeatureClick(features, layer, coordinate);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Highlight the trajectory of journey.
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
highlightTrajectory() {
|
|
356
|
+
const { selectedVehicleId, journeyId } = this;
|
|
357
|
+
const promises = [
|
|
358
|
+
// Fetch stations information with a trajectory id.
|
|
359
|
+
this.api.fetchTrajectoryStations(
|
|
360
|
+
this.getParams({
|
|
361
|
+
id: selectedVehicleId,
|
|
362
|
+
time: getUTCTimeString(new Date()),
|
|
363
|
+
}),
|
|
364
|
+
),
|
|
365
|
+
// Full trajectory.
|
|
366
|
+
this.api.fetchTrajectoryById(
|
|
367
|
+
this.getParams({
|
|
368
|
+
id: journeyId,
|
|
369
|
+
time: getUTCTimeString(new Date()),
|
|
370
|
+
}),
|
|
371
|
+
),
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
Promise.all(promises)
|
|
375
|
+
.then(([trajStations, fullTraj]) => {
|
|
376
|
+
const stationsCoords = [];
|
|
377
|
+
if (trajStations) {
|
|
378
|
+
trajStations.stations.forEach((station) => {
|
|
379
|
+
stationsCoords.push(station.coordinates);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (fullTraj) {
|
|
384
|
+
const { p: multiLine, t, c: color } = fullTraj;
|
|
385
|
+
const lineCoords = [];
|
|
386
|
+
multiLine.forEach((line) => {
|
|
387
|
+
line.forEach((point) => {
|
|
388
|
+
lineCoords.push([point.x, point.y]);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const lineColor = color ? `#${color}` : getBgColor(t);
|
|
393
|
+
// Don't allow white lines, use red instead.
|
|
394
|
+
const vehiculeColor = /#ffffff/i.test(lineColor)
|
|
395
|
+
? '#ff0000'
|
|
396
|
+
: lineColor;
|
|
397
|
+
|
|
398
|
+
this.drawFullTrajectory(
|
|
399
|
+
stationsCoords,
|
|
400
|
+
lineCoords,
|
|
401
|
+
this.useDelayStyle ? '#a0a0a0' : vehiculeColor,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
.catch(() => {
|
|
406
|
+
this.drawFullTrajectory();
|
|
407
|
+
});
|
|
353
408
|
}
|
|
354
409
|
|
|
355
410
|
updateFilters() {
|
|
@@ -370,14 +425,6 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
370
425
|
}
|
|
371
426
|
}
|
|
372
427
|
|
|
373
|
-
updateTrajectoryStations(trajId) {
|
|
374
|
-
const params = this.getParams({
|
|
375
|
-
id: trajId,
|
|
376
|
-
time: getUTCTimeString(new Date()),
|
|
377
|
-
});
|
|
378
|
-
return this.api.fetchTrajectoryStations(params);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
428
|
getParams(extraParams = {}) {
|
|
382
429
|
// The 5 seconds more are used as a buffer if the request takes too long.
|
|
383
430
|
const requestIntervalInMs = (this.requestIntervalSeconds + 5) * 1000;
|
|
@@ -464,6 +511,16 @@ const TrajservLayerMixin = (TrackerLayer) =>
|
|
|
464
511
|
});
|
|
465
512
|
}
|
|
466
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Draw the trajectory as a line with points for each stop.
|
|
516
|
+
*
|
|
517
|
+
* @param {Array} stationsCoords Array of station coordinates in EPSG:4326.
|
|
518
|
+
* @param {Array<ol/coordinate~Coordinate>} lineCoords A list of coordinates in EPSG:3857.
|
|
519
|
+
* @param {string} color The color of the line.
|
|
520
|
+
* @private
|
|
521
|
+
*/
|
|
522
|
+
drawFullTrajectory(stationsCoords, lineCoords, color) {}
|
|
523
|
+
|
|
467
524
|
/**
|
|
468
525
|
* Define the style of the vehicle.
|
|
469
526
|
*
|
|
@@ -128,10 +128,8 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
128
128
|
* @private
|
|
129
129
|
* @override
|
|
130
130
|
*/
|
|
131
|
-
onFeatureHover(
|
|
132
|
-
const
|
|
133
|
-
features: [feature],
|
|
134
|
-
} = featureInfo;
|
|
131
|
+
onFeatureHover(features, layer, coordinate) {
|
|
132
|
+
const [feature] = features;
|
|
135
133
|
let id = null;
|
|
136
134
|
if (feature) {
|
|
137
135
|
id = feature.get('train_id');
|
|
@@ -141,7 +139,7 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
141
139
|
this.hoverVehicleId = id;
|
|
142
140
|
this.renderTrajectories();
|
|
143
141
|
}
|
|
144
|
-
super.onFeatureHover(
|
|
142
|
+
super.onFeatureHover(features, layer, coordinate);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
145
|
/**
|
|
@@ -150,17 +148,16 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
150
148
|
* @private
|
|
151
149
|
* @override
|
|
152
150
|
*/
|
|
153
|
-
onFeatureClick(
|
|
154
|
-
const
|
|
155
|
-
features: [feature],
|
|
156
|
-
} = featureInfo;
|
|
151
|
+
onFeatureClick(features, layer, coordinate) {
|
|
152
|
+
const [feature] = features;
|
|
157
153
|
if (feature) {
|
|
158
154
|
/** @ignore */
|
|
159
155
|
this.selectedVehicleId = feature.get('train_id');
|
|
156
|
+
this.highlightTrajectory();
|
|
160
157
|
} else {
|
|
161
158
|
this.selectedVehicleId = null;
|
|
162
159
|
}
|
|
163
|
-
super.onFeatureClick(
|
|
160
|
+
super.onFeatureClick(features, layer, coordinate);
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
onMessage(data) {
|
|
@@ -197,6 +194,38 @@ const TralisLayerMixin = (TrackerLayer) =>
|
|
|
197
194
|
}
|
|
198
195
|
}
|
|
199
196
|
|
|
197
|
+
/**
|
|
198
|
+
* When a vehicle is selected, we request the complete stop sequence and the complete full trajectory.
|
|
199
|
+
* Then we combine them in one response and send them to inherited layers.
|
|
200
|
+
*
|
|
201
|
+
* @private
|
|
202
|
+
* @override
|
|
203
|
+
*/
|
|
204
|
+
highlightTrajectory() {
|
|
205
|
+
// When a vehicle is selected, we request the complete stop sequence and the complete full trajectory.
|
|
206
|
+
// Then we combine them in one response and send them to inherited layers.
|
|
207
|
+
const promises = [
|
|
208
|
+
this.api.getStopSequence(this.selectedVehicleId, this.mode),
|
|
209
|
+
this.api.getFullTrajectory(this.selectedVehicleId, this.mode),
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
return Promise.all(promises).then(([stopSequence, fullTrajectory]) => {
|
|
213
|
+
console.log(
|
|
214
|
+
`stopSequence for ${this.selectedVehicleId}:`,
|
|
215
|
+
stopSequence,
|
|
216
|
+
);
|
|
217
|
+
console.log(
|
|
218
|
+
`fullTrajectory for ${this.selectedVehicleId}:`,
|
|
219
|
+
fullTrajectory,
|
|
220
|
+
);
|
|
221
|
+
const response = {
|
|
222
|
+
stopSequence,
|
|
223
|
+
fullTrajectory,
|
|
224
|
+
};
|
|
225
|
+
return response;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
200
229
|
addTrajectory(id, traj, addOnTop) {
|
|
201
230
|
const idx = this.trajectories.findIndex((t) => t.train_id === id);
|
|
202
231
|
const { time_intervals: timeIntervals } = traj;
|
package/common/utils/index.js
CHANGED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRadius,
|
|
3
|
+
getBgColor,
|
|
4
|
+
getDelayColor,
|
|
5
|
+
getDelayText,
|
|
6
|
+
getTextColor,
|
|
7
|
+
getTextSize,
|
|
8
|
+
} from '../trackerConfig';
|
|
9
|
+
|
|
10
|
+
const styleCache = {};
|
|
11
|
+
|
|
12
|
+
const style = (trajectory, viewState, trackerLayer) => {
|
|
13
|
+
const {
|
|
14
|
+
hoverVehicleId,
|
|
15
|
+
selectedVehicleId,
|
|
16
|
+
useDelayStyle,
|
|
17
|
+
delayOutlineColor,
|
|
18
|
+
delayDisplay,
|
|
19
|
+
} = trackerLayer;
|
|
20
|
+
|
|
21
|
+
const { zoom, pixelRatio } = viewState;
|
|
22
|
+
|
|
23
|
+
let { line = {} } = trajectory;
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
id,
|
|
27
|
+
delay,
|
|
28
|
+
type = 'Rail',
|
|
29
|
+
cancelled = false,
|
|
30
|
+
operatorProvidesRealtime = 'no',
|
|
31
|
+
} = trajectory;
|
|
32
|
+
|
|
33
|
+
if (!line) {
|
|
34
|
+
line = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { name = 'I', color = '000000' } = line;
|
|
38
|
+
let { text_color: textColor } = line;
|
|
39
|
+
if (!textColor) {
|
|
40
|
+
textColor = '#ffffff';
|
|
41
|
+
}
|
|
42
|
+
const z = Math.min(Math.floor(zoom || 1), 16);
|
|
43
|
+
const hover = hoverVehicleId === id;
|
|
44
|
+
const selected = selectedVehicleId === id;
|
|
45
|
+
let key = `${z}${type}${name}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`;
|
|
46
|
+
|
|
47
|
+
// Calcul the radius of the circle
|
|
48
|
+
let radius = getRadius(type, z) * pixelRatio;
|
|
49
|
+
const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio;
|
|
50
|
+
if (hover || selected) {
|
|
51
|
+
radius = isDisplayStrokeAndDelay
|
|
52
|
+
? radius + 5 * pixelRatio
|
|
53
|
+
: 14 * pixelRatio;
|
|
54
|
+
}
|
|
55
|
+
const mustDrawText = radius > 10 * pixelRatio;
|
|
56
|
+
|
|
57
|
+
// Optimize the cache key, very important in high zoom level
|
|
58
|
+
if (!mustDrawText) {
|
|
59
|
+
key = `${z}${type}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!styleCache[key]) {
|
|
63
|
+
if (radius === 0) {
|
|
64
|
+
styleCache[key] = null;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const margin = 1 * pixelRatio;
|
|
69
|
+
const radiusDelay = radius + 2;
|
|
70
|
+
const markerSize = radius * 2;
|
|
71
|
+
|
|
72
|
+
const canvas = document.createElement('canvas');
|
|
73
|
+
// add space for delay information
|
|
74
|
+
canvas.width = radiusDelay * 2 + margin * 2 + 100 * pixelRatio;
|
|
75
|
+
canvas.height = radiusDelay * 2 + margin * 2 + 100 * pixelRatio;
|
|
76
|
+
const ctx = canvas.getContext('2d');
|
|
77
|
+
const origin = canvas.width / 2;
|
|
78
|
+
|
|
79
|
+
if (isDisplayStrokeAndDelay && delay !== null) {
|
|
80
|
+
// Draw circle delay background
|
|
81
|
+
ctx.save();
|
|
82
|
+
ctx.beginPath();
|
|
83
|
+
ctx.arc(origin, origin, radiusDelay, 0, 2 * Math.PI, false);
|
|
84
|
+
ctx.fillStyle = getDelayColor(delay, cancelled);
|
|
85
|
+
ctx.filter = 'blur(1px)';
|
|
86
|
+
ctx.fill();
|
|
87
|
+
ctx.restore();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Show delay if feature is hovered or if delay is above 5mins.
|
|
91
|
+
if (
|
|
92
|
+
isDisplayStrokeAndDelay &&
|
|
93
|
+
(hover || delay >= delayDisplay || cancelled)
|
|
94
|
+
) {
|
|
95
|
+
// Draw delay text
|
|
96
|
+
ctx.save();
|
|
97
|
+
ctx.textAlign = 'left';
|
|
98
|
+
ctx.textBaseline = 'middle';
|
|
99
|
+
ctx.font = `bold ${Math.max(
|
|
100
|
+
cancelled ? 19 : 14,
|
|
101
|
+
Math.min(cancelled ? 19 : 17, radius * 1.2),
|
|
102
|
+
)}px arial, sans-serif`;
|
|
103
|
+
ctx.fillStyle = getDelayColor(delay, cancelled, true);
|
|
104
|
+
|
|
105
|
+
ctx.strokeStyle = delayOutlineColor;
|
|
106
|
+
ctx.lineWidth = 1.5 * pixelRatio;
|
|
107
|
+
const delayText = getDelayText(delay, cancelled);
|
|
108
|
+
ctx.strokeText(delayText, origin + radiusDelay + margin, origin);
|
|
109
|
+
ctx.fillText(delayText, origin + radiusDelay + margin, origin);
|
|
110
|
+
ctx.restore();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Draw colored circle with black border
|
|
114
|
+
let circleFillColor;
|
|
115
|
+
if (useDelayStyle) {
|
|
116
|
+
circleFillColor = getDelayColor(delay, cancelled);
|
|
117
|
+
} else {
|
|
118
|
+
circleFillColor = `#${color}` || getBgColor(type);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ctx.save();
|
|
122
|
+
if (isDisplayStrokeAndDelay || hover || selected) {
|
|
123
|
+
ctx.lineWidth = 1 * pixelRatio;
|
|
124
|
+
ctx.strokeStyle = '#000000';
|
|
125
|
+
}
|
|
126
|
+
ctx.fillStyle = circleFillColor;
|
|
127
|
+
ctx.beginPath();
|
|
128
|
+
ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);
|
|
129
|
+
ctx.fill();
|
|
130
|
+
// Dashed outline if a provider provides realtime but we don't use it.
|
|
131
|
+
if (
|
|
132
|
+
isDisplayStrokeAndDelay &&
|
|
133
|
+
useDelayStyle &&
|
|
134
|
+
delay === null &&
|
|
135
|
+
operatorProvidesRealtime === 'yes'
|
|
136
|
+
) {
|
|
137
|
+
ctx.setLineDash([5, 3]);
|
|
138
|
+
}
|
|
139
|
+
if (isDisplayStrokeAndDelay || hover || selected) {
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
}
|
|
142
|
+
ctx.restore();
|
|
143
|
+
|
|
144
|
+
// Draw text in the circle
|
|
145
|
+
if (mustDrawText) {
|
|
146
|
+
const fontSize = Math.max(radius, 10 * pixelRatio);
|
|
147
|
+
const textSize = getTextSize(ctx, markerSize, name, fontSize);
|
|
148
|
+
|
|
149
|
+
// Draw a stroke to the text only if a provider provides realtime but we don't use it.
|
|
150
|
+
if (
|
|
151
|
+
useDelayStyle &&
|
|
152
|
+
delay === null &&
|
|
153
|
+
operatorProvidesRealtime === 'yes'
|
|
154
|
+
) {
|
|
155
|
+
ctx.save();
|
|
156
|
+
ctx.textBaseline = 'middle';
|
|
157
|
+
ctx.textAlign = 'center';
|
|
158
|
+
ctx.font = `bold ${textSize + 2}px Arial`;
|
|
159
|
+
ctx.strokeStyle = circleFillColor;
|
|
160
|
+
ctx.strokeText(name, origin, origin);
|
|
161
|
+
ctx.restore();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Draw a text
|
|
165
|
+
ctx.save();
|
|
166
|
+
ctx.textBaseline = 'middle';
|
|
167
|
+
ctx.textAlign = 'center';
|
|
168
|
+
ctx.fillStyle = !useDelayStyle
|
|
169
|
+
? textColor || getTextColor(type)
|
|
170
|
+
: '#000000';
|
|
171
|
+
ctx.font = `bold ${textSize}px Arial`;
|
|
172
|
+
ctx.strokeStyle = circleFillColor;
|
|
173
|
+
ctx.strokeText(name, origin, origin);
|
|
174
|
+
ctx.fillText(name, origin, origin);
|
|
175
|
+
ctx.restore();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
styleCache[key] = canvas;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return styleCache[key];
|
|
182
|
+
};
|
|
183
|
+
export default style;
|