mobility-toolbox-js 3.0.0-beta.19 → 3.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/api/HttpAPI.d.ts +5 -5
  2. package/api/RealtimeAPI.d.ts +204 -171
  3. package/api/RealtimeAPI.js +306 -258
  4. package/api/RoutingAPI.d.ts +4 -4
  5. package/api/StopsAPI.d.ts +4 -4
  6. package/api/WebSocketAPI.d.ts +60 -66
  7. package/api/WebSocketAPI.js +164 -164
  8. package/api/index.js +1 -1
  9. package/common/controls/StopFinderControlCommon.d.ts +11 -11
  10. package/common/controls/StopFinderControlCommon.js +30 -30
  11. package/common/index.d.ts +1 -1
  12. package/common/index.js +1 -1
  13. package/common/mixins/RealtimeLayerMixin.d.ts +149 -155
  14. package/common/mixins/RealtimeLayerMixin.js +395 -395
  15. package/common/styles/realtimeDefaultStyle.js +6 -6
  16. package/common/styles/realtimeHeadingStyle.js +5 -5
  17. package/common/utils/getMapGlCopyrights.d.ts +1 -1
  18. package/common/utils/getMapGlCopyrights.js +3 -3
  19. package/common/utils/getVehiclePosition.d.ts +2 -2
  20. package/common/utils/getVehiclePosition.js +7 -7
  21. package/common/utils/renderTrajectories.js +5 -5
  22. package/common/utils/sortByDelay.js +5 -5
  23. package/maplibre/layers/RealtimeLayer.d.ts +59 -64
  24. package/maplibre/layers/RealtimeLayer.js +8 -8
  25. package/maplibre/utils/getSourceCoordinates.js +5 -5
  26. package/mbt.js +7205 -7031
  27. package/mbt.js.map +4 -4
  28. package/mbt.min.js +25 -25
  29. package/mbt.min.js.map +4 -4
  30. package/ol/controls/RoutingControl.d.ts +81 -87
  31. package/ol/controls/RoutingControl.js +216 -218
  32. package/ol/layers/Layer.d.ts +9 -9
  33. package/ol/layers/MaplibreLayer.d.ts +10 -10
  34. package/ol/layers/MaplibreLayer.js +9 -3
  35. package/ol/layers/MaplibreStyleLayer.d.ts +77 -76
  36. package/ol/layers/MaplibreStyleLayer.js +237 -238
  37. package/ol/layers/RealtimeLayer.d.ts +92 -96
  38. package/ol/layers/RealtimeLayer.js +139 -131
  39. package/ol/mixins/MobilityLayerMixin.d.ts +9 -9
  40. package/ol/mixins/PropertiesLayerMixin.d.ts +33 -36
  41. package/ol/mixins/PropertiesLayerMixin.js +73 -72
  42. package/ol/renderers/MaplibreLayerRenderer.js +3 -3
  43. package/ol/renderers/MaplibreStyleLayerRenderer.d.ts +6 -6
  44. package/ol/renderers/MaplibreStyleLayerRenderer.js +14 -17
  45. package/ol/renderers/RealtimeLayerRenderer.d.ts +6 -6
  46. package/ol/renderers/RealtimeLayerRenderer.js +54 -52
  47. package/ol/utils/getFeatureInfoAtCoordinate.d.ts +1 -1
  48. package/ol/utils/getFeatureInfoAtCoordinate.js +10 -16
  49. package/package.json +6 -5
  50. package/setupTests.js +3 -4
  51. package/types/common.d.ts +53 -49
  52. package/types/index.d.ts +1 -1
  53. package/types/realtime.d.ts +91 -93
  54. package/types/routing.d.ts +60 -60
  55. package/types/stops.d.ts +62 -62
@@ -2,30 +2,34 @@
2
2
  /* eslint-disable no-useless-constructor,@typescript-eslint/no-useless-constructor */
3
3
  /* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */
4
4
  /* eslint-disable class-methods-use-this */
5
+ import debounce from 'lodash.debounce';
6
+ import throttle from 'lodash.throttle';
5
7
  /* eslint-disable max-classes-per-file */
6
8
  import { buffer, containsCoordinate, intersects } from 'ol/extent';
7
- import { unByKey } from 'ol/Observable';
8
9
  import GeoJSON from 'ol/format/GeoJSON';
9
- import debounce from 'lodash.debounce';
10
- import throttle from 'lodash.throttle';
10
+ import { unByKey } from 'ol/Observable';
11
11
  import { fromLonLat } from 'ol/proj';
12
- import realtimeDefaultStyle from '../styles/realtimeDefaultStyle';
13
12
  import { RealtimeAPI, RealtimeModes } from '../../api';
14
- import renderTrajectories from '../utils/renderTrajectories';
13
+ import realtimeDefaultStyle from '../styles/realtimeDefaultStyle';
15
14
  import * as realtimeConfig from '../utils/realtimeConfig';
15
+ import renderTrajectories from '../utils/renderTrajectories';
16
16
  /**
17
17
  * RealtimeLayerInterface.
18
18
  * @private
19
19
  */
20
20
  export class RealtimeLayerInterface {
21
21
  /**
22
- * Start the clock.
22
+ * Request the stopSequence and the fullTrajectory informations for a vehicle.
23
+ *
24
+ * @param {string} id The vehicle identifier (the train_id property).
25
+ * @param {RealtimeMode} mode The mode to request. If not defined, the layer´s mode propetrty will be used.
26
+ * @return {Promise<{stopSequence: RealtimeStopSequence, fullTrajectory: RealtimeFullTrajectory>} A promise that will be resolved with the trajectory informations.
23
27
  */
24
- start() { }
28
+ getTrajectoryInfos(id, mode) { }
25
29
  /**
26
- * Stop the clock.
30
+ * Render the trajectories
27
31
  */
28
- stop() { }
32
+ renderTrajectories() { }
29
33
  /**
30
34
  * Set the Realtime api's bbox.
31
35
  *
@@ -34,17 +38,13 @@ export class RealtimeLayerInterface {
34
38
  */
35
39
  setBbox(extent, zoom) { }
36
40
  /**
37
- * Render the trajectories
41
+ * Start the clock.
38
42
  */
39
- renderTrajectories() { }
43
+ start() { }
40
44
  /**
41
- * Request the stopSequence and the fullTrajectory informations for a vehicle.
42
- *
43
- * @param {string} id The vehicle identifier (the train_id property).
44
- * @param {RealtimeMode} mode The mode to request. If not defined, the layer´s mode propetrty will be used.
45
- * @return {Promise<{stopSequence: RealtimeStopSequence, fullTrajectory: RealtimeFullTrajectory>} A promise that will be resolved with the trajectory informations.
45
+ * Stop the clock.
46
46
  */
47
- getTrajectoryInfos(id, mode) { }
47
+ stop() { }
48
48
  }
49
49
  /**
50
50
  * Mixin for RealtimeLayerInterface.
@@ -54,7 +54,7 @@ export class RealtimeLayerInterface {
54
54
  * @private
55
55
  */
56
56
  function RealtimeLayerMixin(Base) {
57
- // @ts-ignore
57
+ // @ts-expect-error
58
58
  return class Mixin extends Base {
59
59
  constructor(options) {
60
60
  super(Object.assign({ hitTolerance: 10 }, options));
@@ -110,7 +110,7 @@ function RealtimeLayerMixin(Base) {
110
110
  this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd !== false;
111
111
  // Define throttling and debounce render function
112
112
  this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, 50, { leading: false, trailing: true });
113
- this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, 50, { leading: true, trailing: true, maxWait: 5000 });
113
+ this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, 50, { leading: true, maxWait: 5000, trailing: true });
114
114
  // Bind callbacks
115
115
  // this.onFeatureHover = this.onFeatureHover.bind(this);
116
116
  // this.onFeatureClick = this.onFeatureClick.bind(this);
@@ -122,6 +122,28 @@ function RealtimeLayerMixin(Base) {
122
122
  this.onDocumentVisibilityChange =
123
123
  this.onDocumentVisibilityChange.bind(this);
124
124
  }
125
+ /**
126
+ * Add a trajectory.
127
+ * @param {RealtimeTrajectory} trajectory The trajectory to add.
128
+ * @private
129
+ */
130
+ addTrajectory(trajectory) {
131
+ if (!this.trajectories) {
132
+ this.trajectories = {};
133
+ }
134
+ const id = trajectory.properties.train_id;
135
+ if (id !== undefined) {
136
+ this.trajectories[id] = trajectory;
137
+ }
138
+ // @ts-expect-error the parameter are set by subclasses
139
+ this.renderTrajectories();
140
+ }
141
+ attachToMap(map) {
142
+ super.attachToMap(map);
143
+ // To avoid browser hanging when the tab is not visible for a certain amount of time,
144
+ // We stop the rendering and the websocket when hide and start again when show.
145
+ document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);
146
+ }
125
147
  /**
126
148
  * Define layer's properties.
127
149
  *
@@ -129,14 +151,20 @@ function RealtimeLayerMixin(Base) {
129
151
  */
130
152
  defineProperties(options) {
131
153
  (super.defineProperties || (() => { }))(options);
132
- const { style, speed, pixelRatio, hoverVehicleId, selectedVehicleId, filter, sort, time, live, canvas, styleOptions, mode, bboxParameters, } = options;
154
+ const { bboxParameters, canvas, filter, hoverVehicleId, live, mode, pixelRatio, selectedVehicleId, sort, speed, style, styleOptions, time, } = options;
133
155
  let currCanvas = canvas;
134
156
  let currSpeed = speed || 1;
135
157
  let currTime = time || new Date();
136
158
  let currMode = mode || RealtimeModes.TOPOGRAPHIC;
137
159
  let currStyle = style || realtimeDefaultStyle;
138
160
  Object.defineProperties(this, {
139
- isTrackerLayer: { value: true },
161
+ /**
162
+ * Custom parameters to send on each BBOX request.
163
+ */
164
+ bboxParameters: {
165
+ value: bboxParameters,
166
+ writable: true,
167
+ },
140
168
  canvas: {
141
169
  get: () => {
142
170
  if (!currCanvas) {
@@ -148,6 +176,29 @@ function RealtimeLayerMixin(Base) {
148
176
  currCanvas = cnvas;
149
177
  },
150
178
  },
179
+ /**
180
+ * Function to filter which vehicles to display.
181
+ */
182
+ filter: {
183
+ value: filter,
184
+ writable: true,
185
+ },
186
+ /**
187
+ * Id of the hovered vehicle.
188
+ */
189
+ hoverVehicleId: {
190
+ value: hoverVehicleId,
191
+ writable: true,
192
+ },
193
+ isTrackerLayer: { value: true },
194
+ /**
195
+ * If true. The layer will always use Date.now() on the next tick to render the trajectories.
196
+ * When true, setting the time property has no effect.
197
+ */
198
+ live: {
199
+ value: live === false ? live : true,
200
+ writable: true,
201
+ },
151
202
  /**
152
203
  * Style function used to render a vehicle.
153
204
  */
@@ -166,21 +217,26 @@ function RealtimeLayerMixin(Base) {
166
217
  },
167
218
  },
168
219
  /**
169
- * Style function used to render a vehicle.
220
+ * Id of the selected vehicle.
170
221
  */
171
- style: {
172
- get: () => currStyle,
173
- set: (newStyle) => {
174
- currStyle = newStyle;
175
- // @ts-ignore function without parameters is defined in subclasses
176
- this.renderTrajectories();
177
- },
222
+ pixelRatio: {
223
+ value: pixelRatio ||
224
+ (typeof window !== 'undefined' ? window.devicePixelRatio : 1),
225
+ writable: true,
178
226
  },
179
227
  /**
180
- * Custom options to pass as last parameter of the style function.
228
+ * Id of the selected vehicle.
181
229
  */
182
- styleOptions: {
183
- value: Object.assign(Object.assign({}, realtimeConfig), (styleOptions || {})),
230
+ selectedVehicleId: {
231
+ value: selectedVehicleId,
232
+ writable: true,
233
+ },
234
+ /**
235
+ * Function to sort the vehicles to display.
236
+ */
237
+ sort: {
238
+ value: sort,
239
+ writable: true,
184
240
  },
185
241
  /**
186
242
  * Speed of the wheel of time.
@@ -194,33 +250,21 @@ function RealtimeLayerMixin(Base) {
194
250
  },
195
251
  },
196
252
  /**
197
- * Custom parameters to send on each BBOX request.
198
- */
199
- bboxParameters: {
200
- value: bboxParameters,
201
- writable: true,
202
- },
203
- /**
204
- * Function to filter which vehicles to display.
205
- */
206
- filter: {
207
- value: filter,
208
- writable: true,
209
- },
210
- /**
211
- * Function to sort the vehicles to display.
253
+ * Style function used to render a vehicle.
212
254
  */
213
- sort: {
214
- value: sort,
215
- writable: true,
255
+ style: {
256
+ get: () => currStyle,
257
+ set: (newStyle) => {
258
+ currStyle = newStyle;
259
+ // @ts-expect-error function without parameters is defined in subclasses
260
+ this.renderTrajectories();
261
+ },
216
262
  },
217
263
  /**
218
- * If true. The layer will always use Date.now() on the next tick to render the trajectories.
219
- * When true, setting the time property has no effect.
264
+ * Custom options to pass as last parameter of the style function.
220
265
  */
221
- live: {
222
- value: live === false ? live : true,
223
- writable: true,
266
+ styleOptions: {
267
+ value: Object.assign(Object.assign({}, realtimeConfig), (styleOptions || {})),
224
268
  },
225
269
  /**
226
270
  * Time used to display the trajectories. Can be a Date or a number in ms representing a Date.
@@ -229,8 +273,8 @@ function RealtimeLayerMixin(Base) {
229
273
  time: {
230
274
  get: () => currTime,
231
275
  set: (newTime) => {
232
- currTime = newTime && newTime.getTime ? newTime : new Date(newTime);
233
- // @ts-ignore function without parameters is defined in subclasses
276
+ currTime = (newTime === null || newTime === void 0 ? void 0 : newTime.getTime) ? newTime : new Date(newTime);
277
+ // @ts-expect-error function without parameters is defined in subclasses
234
278
  this.renderTrajectories();
235
279
  },
236
280
  },
@@ -242,25 +286,10 @@ function RealtimeLayerMixin(Base) {
242
286
  writable: true,
243
287
  },
244
288
  /**
245
- * Id of the hovered vehicle.
246
- */
247
- hoverVehicleId: {
248
- value: hoverVehicleId,
249
- writable: true,
250
- },
251
- /**
252
- * Id of the selected vehicle.
253
- */
254
- selectedVehicleId: {
255
- value: selectedVehicleId,
256
- writable: true,
257
- },
258
- /**
259
- * Id of the selected vehicle.
289
+ * If true, encapsulates the renderTrajectories calls in a debounce function.
260
290
  */
261
- pixelRatio: {
262
- value: pixelRatio ||
263
- (typeof window !== 'undefined' ? window.devicePixelRatio : 1),
291
+ useDebounce: {
292
+ value: options.useDebounce || false,
264
293
  writable: true,
265
294
  },
266
295
  /**
@@ -277,13 +306,6 @@ function RealtimeLayerMixin(Base) {
277
306
  value: options.useThrottle !== false,
278
307
  writable: true,
279
308
  },
280
- /**
281
- * If true, encapsulates the renderTrajectories calls in a debounce function.
282
- */
283
- useDebounce: {
284
- value: options.useDebounce || false,
285
- writable: true,
286
- },
287
309
  /**
288
310
  * Debug properties.
289
311
  */
@@ -297,12 +319,6 @@ function RealtimeLayerMixin(Base) {
297
319
  // },
298
320
  });
299
321
  }
300
- attachToMap(map) {
301
- super.attachToMap(map);
302
- // To avoid browser hanging when the tab is not visible for a certain amount of time,
303
- // We stop the rendering and the websocket when hide and start again when show.
304
- document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);
305
- }
306
322
  detachFromMap() {
307
323
  document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
308
324
  this.stop();
@@ -315,192 +331,45 @@ function RealtimeLayerMixin(Base) {
315
331
  super.detachFromMap();
316
332
  }
317
333
  }
318
- start() {
319
- this.stop();
320
- // Before starting to update trajectories, we remove trajectories that have
321
- // a time_intervals in the past, it will
322
- // avoid phantom train that are at the end of their route because we never
323
- // received the deleted_vehicle event because we have changed the browser tab.
324
- this.purgeOutOfDateTrajectories();
325
- // @ts-ignore function without parameters must be define in subclasses
326
- this.renderTrajectories();
327
- this.startUpdateTime();
328
- this.api.open();
329
- this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
330
- this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
331
- if (this.isUpdateBboxOnMoveEnd) {
332
- // Update the bbox on each move end
333
- // @ts-ignore function without parameters defined by subclasses
334
- this.setBbox();
335
- }
336
- if (this.onStart) {
337
- this.onStart(this);
338
- }
339
- }
340
334
  /**
341
- * Start the clock.
342
- * @private
335
+ * Request feature information for a given coordinate.
336
+ *
337
+ * @param {ol/coordinate~Coordinate} coordinate Coordinate.
338
+ * @param {Object} options Options See child classes to see which options are supported.
339
+ * @param {number} [options.resolution=1] The resolution of the map.
340
+ * @param {number} [options.nb=Infinity] The max number of vehicles to return.
341
+ * @return {Promise<FeatureInfo>} Promise with features, layer and coordinate.
343
342
  */
344
- startUpdateTime() {
345
- this.stopUpdateTime();
346
- this.updateTimeDelay = this.getRefreshTimeInMs() || 0;
347
- this.updateTimeInterval = window.setInterval(() => {
348
- // When live=true, we update the time with new Date();
349
- if (this.live) {
350
- this.time = new Date();
343
+ getFeatureInfoAtCoordinate(coordinate, options) {
344
+ const { nb, resolution } = options;
345
+ const ext = buffer([...coordinate, ...coordinate], this.hitTolerance * resolution);
346
+ let trajectories = Object.values(this.trajectories || {});
347
+ if (this.sort) {
348
+ // @ts-expect-error good type must be defined
349
+ trajectories = trajectories.sort(this.sort);
350
+ }
351
+ const vehicles = [];
352
+ for (let i = 0; i < trajectories.length; i += 1) {
353
+ // @ts-expect-error coordinate is added by the RealtimeLayer
354
+ const { coordinate: trajcoord } = trajectories[i].properties;
355
+ if (trajcoord && containsCoordinate(ext, trajcoord)) {
356
+ vehicles.push(trajectories[i]);
351
357
  }
352
- else if (this.time && this.updateTimeDelay && this.speed) {
353
- this.time = new Date(this.time.getTime() + this.updateTimeDelay * this.speed);
358
+ if (vehicles.length === nb) {
359
+ break;
354
360
  }
355
- }, this.updateTimeDelay);
356
- }
357
- stop() {
358
- this.api.unsubscribeTrajectory(this.onTrajectoryMessage);
359
- this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);
360
- this.api.close();
361
- if (this.onStop) {
362
- this.onStop(this);
363
361
  }
362
+ return Promise.resolve({
363
+ coordinate,
364
+ features: vehicles.map((vehicle) => this.format.readFeature(vehicle)),
365
+ layer: this,
366
+ });
364
367
  }
365
368
  /**
366
- * Stop the clock.
369
+ * Get the duration before the next update depending on zoom level.
370
+ *
367
371
  * @private
368
- */
369
- stopUpdateTime() {
370
- if (this.updateTimeInterval) {
371
- clearInterval(this.updateTimeInterval);
372
- this.updateTimeInterval = undefined;
373
- }
374
- }
375
- /**
376
- * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method.
377
- *
378
- * @param {object} viewState The view state of the map.
379
- * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.
380
- * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.
381
- * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.
382
- * @param {number} [viewState.rotation = 0] Rotation of the map to render.
383
- * @param {number} viewState.resolution Resolution of the map to render.
384
- * @param {boolean} noInterpolate If true trajectories are not interpolated but
385
- * drawn at the last known coordinate. Use this for performance optimization
386
- * during map navigation.
387
- * @private
388
- */
389
- renderTrajectoriesInternal(viewState, noInterpolate = false) {
390
- var _a;
391
- if (!this.map || !this.trajectories) {
392
- return false;
393
- }
394
- const time = this.live ? Date.now() : (_a = this.time) === null || _a === void 0 ? void 0 : _a.getTime();
395
- const trajectories = Object.values(this.trajectories);
396
- // console.time('sort');
397
- if (this.sort) {
398
- // @ts-ignore
399
- trajectories.sort(this.sort);
400
- }
401
- // console.timeEnd('sort');
402
- if (!this.canvas || !this.style) {
403
- return true;
404
- }
405
- // console.time('render');
406
- this.renderState = renderTrajectories(this.canvas, trajectories, this.style, Object.assign(Object.assign({}, viewState), { pixelRatio: this.pixelRatio || 1, time }), Object.assign({ filter: this.filter, noInterpolate: (viewState.zoom || 0) < this.minZoomInterpolation
407
- ? true
408
- : noInterpolate, hoverVehicleId: this.hoverVehicleId, selectedVehicleId: this.selectedVehicleId }, this.styleOptions));
409
- // console.timeEnd('render');
410
- return true;
411
- }
412
- /**
413
- * Render the trajectories requesting an animation frame and cancelling the previous one.
414
- * This function must be overrided by children to provide the correct parameters.
415
- *
416
- * @param {object} viewState The view state of the map.
417
- * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.
418
- * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.
419
- * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.
420
- * @param {number} [viewState.rotation = 0] Rotation of the map to render.
421
- * @param {number} viewState.resolution Resolution of the map to render.
422
- * @param {boolean} noInterpolate If true trajectories are not interpolated but
423
- * drawn at the last known coordinate. Use this for performance optimization
424
- * during map navigation.
425
- * @private
426
- */
427
- renderTrajectories(viewState, noInterpolate) {
428
- if (this.requestId) {
429
- cancelAnimationFrame(this.requestId);
430
- this.requestId = undefined;
431
- }
432
- if (!viewState) {
433
- return;
434
- }
435
- if (!noInterpolate && this.useRequestAnimationFrame) {
436
- this.requestId = requestAnimationFrame(() => {
437
- this.renderTrajectoriesInternal(viewState, noInterpolate);
438
- });
439
- }
440
- else if (!noInterpolate && this.useDebounce) {
441
- this.debounceRenderTrajectories(viewState, noInterpolate);
442
- }
443
- else if (!noInterpolate && this.useThrottle) {
444
- this.throttleRenderTrajectories(viewState, noInterpolate);
445
- }
446
- else {
447
- this.renderTrajectoriesInternal(viewState, noInterpolate);
448
- }
449
- }
450
- setBbox(extent, zoom) {
451
- // Clean trajectories before sending the new bbox
452
- // Purge trajectories:
453
- // - which are outside the extent
454
- // - when it's bus and zoom level is too low for them
455
- if (this.trajectories && extent && zoom) {
456
- const keys = Object.keys(this.trajectories);
457
- for (let i = keys.length - 1; i >= 0; i -= 1) {
458
- this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom);
459
- }
460
- }
461
- // The backend only supports non float value
462
- const zoomFloor = Math.floor(zoom);
463
- if (!extent || Number.isNaN(zoomFloor)) {
464
- return;
465
- }
466
- // The extent does not need to be precise under meter, so we round floor/ceil the values.
467
- const [minX, minY, maxX, maxY] = extent;
468
- const bbox = [
469
- Math.floor(minX),
470
- Math.floor(minY),
471
- Math.ceil(maxX),
472
- Math.ceil(maxY),
473
- zoomFloor,
474
- ];
475
- /* @private */
476
- this.generalizationLevel = this.getGeneralizationLevelByZoom(zoomFloor);
477
- if (this.generalizationLevel) {
478
- bbox.push(`gen=${this.generalizationLevel}`);
479
- }
480
- /* @private */
481
- this.mots = this.getMotsByZoom(zoomFloor);
482
- if (this.mots) {
483
- bbox.push(`mots=${this.mots}`);
484
- }
485
- if (this.tenant) {
486
- bbox.push(`tenant=${this.tenant}`);
487
- }
488
- if (this.mode !== 'topographic') {
489
- bbox.push(`channel_prefix=${this.mode}`);
490
- }
491
- if (this.bboxParameters) {
492
- Object.entries(this.bboxParameters).forEach(([key, value]) => {
493
- bbox.push(`${key}=${value}`);
494
- });
495
- }
496
- // Extent and zoom level are mandatory.
497
- this.api.bbox = bbox;
498
- }
499
- /**
500
- * Get the duration before the next update depending on zoom level.
501
- *
502
- * @private
503
- * @param {number} zoom
372
+ * @param {number} zoom
504
373
  */
505
374
  getRefreshTimeInMs(zoom = 0) {
506
375
  var _a;
@@ -513,7 +382,7 @@ function RealtimeLayerMixin(Base) {
513
382
  this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true });
514
383
  }
515
384
  else if (this.useDebounce) {
516
- this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true, maxWait: 5000 });
385
+ this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, maxWait: 5000, trailing: true });
517
386
  }
518
387
  if ((_a = this.api) === null || _a === void 0 ? void 0 : _a.buffer) {
519
388
  const [, size] = this.api.buffer;
@@ -521,51 +390,6 @@ function RealtimeLayerMixin(Base) {
521
390
  }
522
391
  return nextTick;
523
392
  }
524
- /**
525
- * Get vehicle.
526
- * @param {function} filterFc A function use to filter results.
527
- * @return {Array<Object>} Array of vehicle.
528
- */
529
- getVehicle(filterFc) {
530
- return ((this.trajectories &&
531
- // @ts-ignore
532
- Object.values(this.trajectories).filter(filterFc)) ||
533
- []);
534
- }
535
- /**
536
- * Request feature information for a given coordinate.
537
- *
538
- * @param {ol/coordinate~Coordinate} coordinate Coordinate.
539
- * @param {Object} options Options See child classes to see which options are supported.
540
- * @param {number} [options.resolution=1] The resolution of the map.
541
- * @param {number} [options.nb=Infinity] The max number of vehicles to return.
542
- * @return {Promise<FeatureInfo>} Promise with features, layer and coordinate.
543
- */
544
- getFeatureInfoAtCoordinate(coordinate, options) {
545
- const { resolution, nb } = options;
546
- const ext = buffer([...coordinate, ...coordinate], this.hitTolerance * resolution);
547
- let trajectories = Object.values(this.trajectories || {});
548
- if (this.sort) {
549
- // @ts-ignore
550
- trajectories = trajectories.sort(this.sort);
551
- }
552
- const vehicles = [];
553
- for (let i = 0; i < trajectories.length; i += 1) {
554
- // @ts-expect-error coordinate is added by the RealtimeLayer
555
- const { coordinate: trajcoord } = trajectories[i].properties;
556
- if (trajcoord && containsCoordinate(ext, trajcoord)) {
557
- vehicles.push(trajectories[i]);
558
- }
559
- if (vehicles.length === nb) {
560
- break;
561
- }
562
- }
563
- return Promise.resolve({
564
- layer: this,
565
- features: vehicles.map((vehicle) => this.format.readFeature(vehicle)),
566
- coordinate,
567
- });
568
- }
569
393
  /**
570
394
  * Request the stopSequence and the fullTrajectory informations for a vehicle.
571
395
  *
@@ -582,12 +406,108 @@ function RealtimeLayerMixin(Base) {
582
406
  ];
583
407
  return Promise.all(promises).then(([stopSequence, fullTrajectory]) => {
584
408
  const response = {
585
- stopSequence,
586
409
  fullTrajectory,
410
+ stopSequence,
587
411
  };
588
412
  return response;
589
413
  });
590
414
  }
415
+ /**
416
+ * Get vehicle.
417
+ * @param {function} filterFc A function use to filter results.
418
+ * @return {Array<Object>} Array of vehicle.
419
+ */
420
+ getVehicle(filterFc) {
421
+ return ((this.trajectories &&
422
+ // @ts-expect-error good type must be defined
423
+ Object.values(this.trajectories).filter(filterFc)) ||
424
+ []);
425
+ }
426
+ highlightVehicle(id) {
427
+ if (this.hoverVehicleId !== id) {
428
+ /** @private */
429
+ this.hoverVehicleId = id;
430
+ // @ts-expect-error good type must be defined
431
+ this.renderTrajectories(true);
432
+ }
433
+ }
434
+ /**
435
+ * Callback on websocket's deleted_vehicles channel events.
436
+ * It removes the trajectory from the list.
437
+ *
438
+ * @private
439
+ * @override
440
+ */
441
+ onDeleteTrajectoryMessage(data) {
442
+ if (!data.content) {
443
+ return;
444
+ }
445
+ this.removeTrajectory(data.content);
446
+ }
447
+ onDocumentVisibilityChange() {
448
+ if (document.hidden) {
449
+ this.stop();
450
+ // Since we don't receive deleted_vehicles event when docuement
451
+ // is hidden. We have to clean all the trajectories for a fresh
452
+ // start when the document is visible again.
453
+ this.trajectories = {};
454
+ }
455
+ else {
456
+ if (this.getVisible() === false) {
457
+ return;
458
+ }
459
+ this.start();
460
+ }
461
+ }
462
+ /**
463
+ * Callback on websocket's trajectory channel events.
464
+ * It adds a trajectory to the list.
465
+ *
466
+ * @private
467
+ */
468
+ onTrajectoryMessage(data) {
469
+ if (!data.content) {
470
+ return;
471
+ }
472
+ const trajectory = data.content;
473
+ const { geometry, properties: { raw_coordinates: rawCoordinates, time_since_update: timeSinceUpdate, train_id: id, }, } = trajectory;
474
+ // ignore old events [SBAHNM-97]
475
+ // @ts-expect-error can be undefined
476
+ if (timeSinceUpdate < 0) {
477
+ return;
478
+ }
479
+ // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);
480
+ // @ts-expect-error default value for extentand zoom are provided by subclasses
481
+ if (this.purgeTrajectory(trajectory)) {
482
+ return;
483
+ }
484
+ if (this.debug &&
485
+ this.mode === RealtimeModes.TOPOGRAPHIC &&
486
+ rawCoordinates) {
487
+ // @ts-expect-error
488
+ trajectory.properties.olGeometry = this.format.readGeometry({
489
+ coordinates: fromLonLat(rawCoordinates, this.map.getView().getProjection()),
490
+ type: 'Point',
491
+ });
492
+ }
493
+ else {
494
+ // @ts-expect-error
495
+ trajectory.properties.olGeometry = this.format.readGeometry(geometry);
496
+ }
497
+ // TODO Make sure the timeOffset is useful. May be we can remove it.
498
+ // @ts-expect-error
499
+ trajectory.properties.timeOffset = Date.now() - data.timestamp;
500
+ this.addTrajectory(trajectory);
501
+ }
502
+ /**
503
+ * On zoomend we adjust the time interval of the update of vehicles positions.
504
+ *
505
+ * @param evt Event that triggered the function.
506
+ * @private
507
+ */
508
+ onZoomEnd() {
509
+ this.startUpdateTime();
510
+ }
591
511
  /**
592
512
  * Remove all trajectories that are in the past.
593
513
  */
@@ -616,7 +536,7 @@ function RealtimeLayerMixin(Base) {
616
536
  * @private
617
537
  */
618
538
  purgeTrajectory(trajectory, extent, zoom) {
619
- const { type, bounds } = trajectory.properties;
539
+ const { bounds, type } = trajectory.properties;
620
540
  if ((this.isUpdateBboxOnMoveEnd && !intersects(extent, bounds)) ||
621
541
  (this.mots && !this.mots.includes(type))) {
622
542
  this.removeTrajectory(trajectory);
@@ -624,22 +544,6 @@ function RealtimeLayerMixin(Base) {
624
544
  }
625
545
  return false;
626
546
  }
627
- /**
628
- * Add a trajectory.
629
- * @param {RealtimeTrajectory} trajectory The trajectory to add.
630
- * @private
631
- */
632
- addTrajectory(trajectory) {
633
- if (!this.trajectories) {
634
- this.trajectories = {};
635
- }
636
- const id = trajectory.properties.train_id;
637
- if (id !== undefined) {
638
- this.trajectories[id] = trajectory;
639
- }
640
- // @ts-ignore the parameter are set by subclasses
641
- this.renderTrajectories();
642
- }
643
547
  removeTrajectory(trajectoryOrId) {
644
548
  var _a;
645
549
  let id;
@@ -654,96 +558,192 @@ function RealtimeLayerMixin(Base) {
654
558
  }
655
559
  }
656
560
  /**
657
- * On zoomend we adjust the time interval of the update of vehicles positions.
561
+ * Render the trajectories requesting an animation frame and cancelling the previous one.
562
+ * This function must be overrided by children to provide the correct parameters.
658
563
  *
659
- * @param evt Event that triggered the function.
564
+ * @param {object} viewState The view state of the map.
565
+ * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.
566
+ * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.
567
+ * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.
568
+ * @param {number} [viewState.rotation = 0] Rotation of the map to render.
569
+ * @param {number} viewState.resolution Resolution of the map to render.
570
+ * @param {boolean} noInterpolate If true trajectories are not interpolated but
571
+ * drawn at the last known coordinate. Use this for performance optimization
572
+ * during map navigation.
660
573
  * @private
661
574
  */
662
- onZoomEnd() {
663
- this.startUpdateTime();
664
- }
665
- onDocumentVisibilityChange() {
666
- if (document.hidden) {
667
- this.stop();
668
- // Since we don't receive deleted_vehicles event when docuement
669
- // is hidden. We have to clean all the trajectories for a fresh
670
- // start when the document is visible again.
671
- this.trajectories = {};
575
+ renderTrajectories(viewState, noInterpolate) {
576
+ if (this.requestId) {
577
+ cancelAnimationFrame(this.requestId);
578
+ this.requestId = undefined;
579
+ }
580
+ if (!viewState) {
581
+ return;
582
+ }
583
+ if (!noInterpolate && this.useRequestAnimationFrame) {
584
+ this.requestId = requestAnimationFrame(() => {
585
+ this.renderTrajectoriesInternal(viewState, noInterpolate);
586
+ });
587
+ }
588
+ else if (!noInterpolate && this.useDebounce) {
589
+ this.debounceRenderTrajectories(viewState, noInterpolate);
590
+ }
591
+ else if (!noInterpolate && this.useThrottle) {
592
+ this.throttleRenderTrajectories(viewState, noInterpolate);
672
593
  }
673
594
  else {
674
- if (this.visible === false) {
675
- return;
676
- }
677
- this.start();
595
+ this.renderTrajectoriesInternal(viewState, noInterpolate);
678
596
  }
679
597
  }
680
598
  /**
681
- * Callback on websocket's trajectory channel events.
682
- * It adds a trajectory to the list.
599
+ * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method.
683
600
  *
601
+ * @param {object} viewState The view state of the map.
602
+ * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.
603
+ * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.
604
+ * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.
605
+ * @param {number} [viewState.rotation = 0] Rotation of the map to render.
606
+ * @param {number} viewState.resolution Resolution of the map to render.
607
+ * @param {boolean} noInterpolate If true trajectories are not interpolated but
608
+ * drawn at the last known coordinate. Use this for performance optimization
609
+ * during map navigation.
684
610
  * @private
685
611
  */
686
- onTrajectoryMessage(data) {
687
- if (!data.content) {
688
- return;
612
+ renderTrajectoriesInternal(viewState, noInterpolate = false) {
613
+ var _a;
614
+ if (!this.map || !this.trajectories) {
615
+ return false;
689
616
  }
690
- const trajectory = data.content;
691
- const { geometry, properties: { train_id: id, time_since_update: timeSinceUpdate, raw_coordinates: rawCoordinates, }, } = trajectory;
692
- // ignore old events [SBAHNM-97]
693
- // @ts-ignore
694
- if (timeSinceUpdate < 0) {
695
- return;
617
+ const time = this.live ? Date.now() : (_a = this.time) === null || _a === void 0 ? void 0 : _a.getTime();
618
+ const trajectories = Object.values(this.trajectories);
619
+ // console.time('sort');
620
+ if (this.sort) {
621
+ // @ts-expect-error type problem
622
+ trajectories.sort(this.sort);
696
623
  }
697
- // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);
698
- // @ts-ignore default value for extentand zoom are provided by subclasses
699
- if (this.purgeTrajectory(trajectory)) {
624
+ // console.timeEnd('sort');
625
+ if (!this.canvas || !this.style) {
626
+ return true;
627
+ }
628
+ // console.time('render');
629
+ this.renderState = renderTrajectories(this.canvas, trajectories, this.style, Object.assign(Object.assign({}, viewState), { pixelRatio: this.pixelRatio || 1, time }), Object.assign({ filter: this.filter, hoverVehicleId: this.hoverVehicleId, noInterpolate: (viewState.zoom || 0) < this.minZoomInterpolation
630
+ ? true
631
+ : noInterpolate, selectedVehicleId: this.selectedVehicleId }, this.styleOptions));
632
+ // console.timeEnd('render');
633
+ return true;
634
+ }
635
+ selectVehicle(id) {
636
+ if (this.selectedVehicleId !== id) {
637
+ /** @private */
638
+ this.selectedVehicleId = id;
639
+ // @ts-expect-error
640
+ this.renderTrajectories(true);
641
+ }
642
+ }
643
+ setBbox(extent, zoom) {
644
+ // Clean trajectories before sending the new bbox
645
+ // Purge trajectories:
646
+ // - which are outside the extent
647
+ // - when it's bus and zoom level is too low for them
648
+ if (this.trajectories && extent && zoom) {
649
+ const keys = Object.keys(this.trajectories);
650
+ for (let i = keys.length - 1; i >= 0; i -= 1) {
651
+ this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom);
652
+ }
653
+ }
654
+ // The backend only supports non float value
655
+ const zoomFloor = Math.floor(zoom);
656
+ if (!extent || Number.isNaN(zoomFloor)) {
700
657
  return;
701
658
  }
702
- if (this.debug &&
703
- this.mode === RealtimeModes.TOPOGRAPHIC &&
704
- rawCoordinates) {
705
- // @ts-ignore
706
- trajectory.properties.olGeometry = this.format.readGeometry({
707
- type: 'Point',
708
- coordinates: fromLonLat(rawCoordinates, this.map.getView().getProjection()),
659
+ // The extent does not need to be precise under meter, so we round floor/ceil the values.
660
+ const [minX, minY, maxX, maxY] = extent;
661
+ const bbox = [
662
+ Math.floor(minX),
663
+ Math.floor(minY),
664
+ Math.ceil(maxX),
665
+ Math.ceil(maxY),
666
+ zoomFloor,
667
+ ];
668
+ /* @private */
669
+ this.generalizationLevel = this.getGeneralizationLevelByZoom(zoomFloor);
670
+ if (this.generalizationLevel) {
671
+ bbox.push(`gen=${this.generalizationLevel}`);
672
+ }
673
+ /* @private */
674
+ this.mots = this.getMotsByZoom(zoomFloor);
675
+ if (this.mots) {
676
+ bbox.push(`mots=${this.mots}`);
677
+ }
678
+ if (this.tenant) {
679
+ bbox.push(`tenant=${this.tenant}`);
680
+ }
681
+ if (this.mode !== 'topographic') {
682
+ bbox.push(`channel_prefix=${this.mode}`);
683
+ }
684
+ if (this.bboxParameters) {
685
+ Object.entries(this.bboxParameters).forEach(([key, value]) => {
686
+ bbox.push(`${key}=${value}`);
709
687
  });
710
688
  }
711
- else {
712
- // @ts-ignore
713
- trajectory.properties.olGeometry = this.format.readGeometry(geometry);
689
+ // Extent and zoom level are mandatory.
690
+ this.api.bbox = bbox;
691
+ }
692
+ start() {
693
+ this.stop();
694
+ // Before starting to update trajectories, we remove trajectories that have
695
+ // a time_intervals in the past, it will
696
+ // avoid phantom train that are at the end of their route because we never
697
+ // received the deleted_vehicle event because we have changed the browser tab.
698
+ this.purgeOutOfDateTrajectories();
699
+ // @ts-expect-error function without parameters must be define in subclasses
700
+ this.renderTrajectories();
701
+ this.startUpdateTime();
702
+ this.api.open();
703
+ this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
704
+ this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
705
+ if (this.isUpdateBboxOnMoveEnd) {
706
+ // Update the bbox on each move end
707
+ // @ts-expect-error function without parameters defined by subclasses
708
+ this.setBbox();
709
+ }
710
+ if (this.onStart) {
711
+ this.onStart(this);
714
712
  }
715
- // TODO Make sure the timeOffset is useful. May be we can remove it.
716
- // @ts-ignore
717
- trajectory.properties.timeOffset = Date.now() - data.timestamp;
718
- this.addTrajectory(trajectory);
719
713
  }
720
714
  /**
721
- * Callback on websocket's deleted_vehicles channel events.
722
- * It removes the trajectory from the list.
723
- *
715
+ * Start the clock.
724
716
  * @private
725
- * @override
726
717
  */
727
- onDeleteTrajectoryMessage(data) {
728
- if (!data.content) {
729
- return;
730
- }
731
- this.removeTrajectory(data.content);
718
+ startUpdateTime() {
719
+ this.stopUpdateTime();
720
+ this.updateTimeDelay = this.getRefreshTimeInMs() || 0;
721
+ this.updateTimeInterval = window.setInterval(() => {
722
+ // When live=true, we update the time with new Date();
723
+ if (this.live) {
724
+ this.time = new Date();
725
+ }
726
+ else if (this.time && this.updateTimeDelay && this.speed) {
727
+ this.time = new Date(this.time.getTime() + this.updateTimeDelay * this.speed);
728
+ }
729
+ }, this.updateTimeDelay);
732
730
  }
733
- highlightVehicle(id) {
734
- if (this.hoverVehicleId !== id) {
735
- /** @private */
736
- this.hoverVehicleId = id;
737
- // @ts-ignore
738
- this.renderTrajectories(true);
731
+ stop() {
732
+ this.api.unsubscribeTrajectory(this.onTrajectoryMessage);
733
+ this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);
734
+ this.api.close();
735
+ if (this.onStop) {
736
+ this.onStop(this);
739
737
  }
740
738
  }
741
- selectVehicle(id) {
742
- if (this.selectedVehicleId !== id) {
743
- /** @private */
744
- this.selectedVehicleId = id;
745
- // @ts-ignore
746
- this.renderTrajectories(true);
739
+ /**
740
+ * Stop the clock.
741
+ * @private
742
+ */
743
+ stopUpdateTime() {
744
+ if (this.updateTimeInterval) {
745
+ clearInterval(this.updateTimeInterval);
746
+ this.updateTimeInterval = undefined;
747
747
  }
748
748
  }
749
749
  };