mobility-toolbox-js 3.0.1-beta.0 → 3.0.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +11 -3
  2. package/api/HttpAPI.js +1 -3
  3. package/api/RealtimeAPI.d.ts +47 -47
  4. package/api/RealtimeAPI.js +74 -74
  5. package/api/WebSocketAPI.js +0 -1
  6. package/common/controls/StopFinderControlCommon.d.ts +1 -1
  7. package/common/controls/StopFinderControlCommon.js +1 -1
  8. package/common/styles/realtimeDefaultStyle.js +0 -5
  9. package/common/styles/realtimeHeadingStyle.js +0 -5
  10. package/common/styles/realtimeSimpleStyle.d.ts +0 -1
  11. package/common/styles/realtimeSimpleStyle.js +0 -1
  12. package/common/typedefs.d.ts +0 -117
  13. package/common/typedefs.js +0 -31
  14. package/common/utils/RealtimeEngine.d.ts +214 -0
  15. package/common/utils/RealtimeEngine.js +554 -0
  16. package/common/utils/getLayersAsFlatArray.d.ts +0 -1
  17. package/common/utils/getLayersAsFlatArray.js +0 -1
  18. package/common/utils/getVehiclePosition.js +1 -4
  19. package/common/utils/realtimeConfig.d.ts +1 -1
  20. package/common/utils/realtimeConfig.js +0 -1
  21. package/common/utils/renderTrajectories.d.ts +1 -0
  22. package/common/utils/renderTrajectories.js +1 -0
  23. package/common/utils/sortAndFilterDepartures.d.ts +1 -0
  24. package/common/utils/sortAndFilterDepartures.js +1 -0
  25. package/maplibre/controls/CopyrightControl.d.ts +9 -6
  26. package/maplibre/controls/CopyrightControl.js +11 -8
  27. package/maplibre/layers/Layer.d.ts +7 -6
  28. package/maplibre/layers/Layer.js +1 -2
  29. package/maplibre/layers/RealtimeLayer.d.ts +54 -111
  30. package/maplibre/layers/RealtimeLayer.js +126 -114
  31. package/maplibre/utils/getSourceCoordinates.d.ts +1 -0
  32. package/maplibre/utils/getSourceCoordinates.js +5 -4
  33. package/mbt.js +6960 -14605
  34. package/mbt.js.map +4 -4
  35. package/mbt.min.js +68 -71
  36. package/mbt.min.js.map +4 -4
  37. package/ol/controls/CopyrightControl.d.ts +13 -5
  38. package/ol/controls/CopyrightControl.js +13 -5
  39. package/ol/controls/RoutingControl.d.ts +29 -18
  40. package/ol/controls/RoutingControl.js +44 -56
  41. package/ol/controls/StopFinderControl.d.ts +21 -2
  42. package/ol/controls/StopFinderControl.js +22 -3
  43. package/ol/index.d.ts +0 -1
  44. package/ol/index.js +0 -1
  45. package/ol/layers/Layer.d.ts +17 -92
  46. package/ol/layers/Layer.js +17 -3
  47. package/ol/layers/MaplibreLayer.d.ts +47 -114
  48. package/ol/layers/MaplibreLayer.js +102 -46
  49. package/ol/layers/MaplibreStyleLayer.d.ts +67 -147
  50. package/ol/layers/MaplibreStyleLayer.js +170 -123
  51. package/ol/layers/RealtimeLayer.d.ts +85 -218
  52. package/ol/layers/RealtimeLayer.js +170 -181
  53. package/ol/layers/VectorLayer.d.ts +1 -2
  54. package/ol/layers/VectorLayer.js +7 -6
  55. package/ol/renderers/MaplibreLayerRenderer.d.ts +9 -0
  56. package/ol/renderers/MaplibreLayerRenderer.js +35 -137
  57. package/ol/renderers/MaplibreStyleLayerRenderer.js +2 -2
  58. package/ol/renderers/RealtimeLayerRenderer.d.ts +1 -1
  59. package/ol/renderers/RealtimeLayerRenderer.js +6 -31
  60. package/ol/styles/fullTrajectoryDelayStyle.js +5 -7
  61. package/ol/styles/fullTrajectoryStyle.d.ts +1 -2
  62. package/ol/styles/fullTrajectoryStyle.js +5 -7
  63. package/ol/styles/routingStyle.d.ts +0 -1
  64. package/ol/styles/routingStyle.js +13 -10
  65. package/ol/utils/defineDeprecatedProperties.d.ts +10 -0
  66. package/ol/utils/defineDeprecatedProperties.js +180 -0
  67. package/package.json +40 -39
  68. package/setupTests.js +14 -0
  69. package/types/common.d.ts +4 -27
  70. package/types/realtime.d.ts +7 -2
  71. package/common/mixins/RealtimeLayerMixin.d.ts +0 -267
  72. package/common/mixins/RealtimeLayerMixin.js +0 -751
  73. package/ol/mixins/MobilityLayerMixin.d.ts +0 -96
  74. package/ol/mixins/MobilityLayerMixin.js +0 -6
  75. package/ol/mixins/PropertiesLayerMixin.d.ts +0 -136
  76. package/ol/mixins/PropertiesLayerMixin.js +0 -178
  77. package/ol/mixins/index.d.ts +0 -1
  78. package/ol/mixins/index.js +0 -2
@@ -0,0 +1,554 @@
1
+ import debounce from 'lodash.debounce';
2
+ import throttle from 'lodash.throttle';
3
+ import { buffer, containsCoordinate, intersects } from 'ol/extent';
4
+ import GeoJSON from 'ol/format/GeoJSON';
5
+ import { fromLonLat } from 'ol/proj';
6
+ import { RealtimeAPI, RealtimeModes } from '../../api';
7
+ import realtimeDefaultStyle from '../styles/realtimeDefaultStyle';
8
+ import * as realtimeConfig from './realtimeConfig';
9
+ import renderTrajectories from './renderTrajectories';
10
+ /**
11
+ * This class is responsible for drawing trajectories from a realtime API in a canvas,
12
+ * depending on the map's view state and at a specific time.
13
+ *
14
+ * This class is totally agnostic from Maplibre or OpenLayers and must stay taht way.
15
+ */
16
+ class RealtimeEngine {
17
+ constructor(options) {
18
+ this.getViewState = () => ({});
19
+ this.shouldRender = () => true;
20
+ this._mode = options.mode || RealtimeModes.TOPOGRAPHIC;
21
+ this._speed = options.speed || 1; // If live property is true. The speed is ignored.
22
+ this._style = options.style || realtimeDefaultStyle;
23
+ this._time = options.time || new Date();
24
+ this.api = options.api || new RealtimeAPI(options);
25
+ this.bboxParameters = options.bboxParameters;
26
+ this.canvas = options.canvas || document.createElement('canvas');
27
+ this.debug = options.debug || false;
28
+ this.filter = options.filter;
29
+ this.hoverVehicleId = options.hoverVehicleId;
30
+ /**
31
+ * If true. The layer will always use Date.now() on the next tick to render the trajectories.
32
+ * When true, setting the time property has no effect.
33
+ */
34
+ this.live = options.live !== false;
35
+ this.minZoomInterpolation = options.minZoomInterpolation || 8; // Min zoom level from which trains positions are not interpolated.
36
+ this.pixelRatio =
37
+ options.pixelRatio ||
38
+ (typeof window !== 'undefined' ? window.devicePixelRatio : 1);
39
+ this.selectedVehicleId = options.selectedVehicleId;
40
+ this.sort = options.sort;
41
+ /**
42
+ * Custom options to pass as last parameter of the style function.
43
+ */
44
+ // @ts-expect-error good type must be defined
45
+ this.styleOptions = Object.assign(Object.assign({}, realtimeConfig), (options.styleOptions || {}));
46
+ this.tenant = options.tenant || ''; // sbb,sbh or sbm
47
+ this.trajectories = {};
48
+ this.useDebounce = options.useDebounce || false;
49
+ this.useRequestAnimationFrame = options.useRequestAnimationFrame || false;
50
+ this.useThrottle = options.useThrottle !== false; // the default behavior
51
+ this.getViewState = options.getViewState || (() => ({}));
52
+ this.shouldRender = options.shouldRender || (() => true);
53
+ this.onRender = options.onRender;
54
+ this.onStart = options.onStart;
55
+ this.onStop = options.onStop;
56
+ this.format = new GeoJSON();
57
+ // Server will block non train before zoom 9
58
+ this.motsByZoom = options.motsByZoom || [
59
+ realtimeConfig.MOTS_ONLY_RAIL,
60
+ realtimeConfig.MOTS_ONLY_RAIL,
61
+ realtimeConfig.MOTS_ONLY_RAIL,
62
+ realtimeConfig.MOTS_ONLY_RAIL,
63
+ realtimeConfig.MOTS_ONLY_RAIL,
64
+ realtimeConfig.MOTS_ONLY_RAIL,
65
+ realtimeConfig.MOTS_ONLY_RAIL,
66
+ realtimeConfig.MOTS_ONLY_RAIL,
67
+ realtimeConfig.MOTS_ONLY_RAIL,
68
+ realtimeConfig.MOTS_WITHOUT_CABLE,
69
+ realtimeConfig.MOTS_WITHOUT_CABLE,
70
+ ];
71
+ // Mots by zoom
72
+ this.getMotsByZoom = (zoom) => {
73
+ if (options.getMotsByZoom) {
74
+ return options.getMotsByZoom(zoom, this.motsByZoom);
75
+ }
76
+ return this.motsByZoom[zoom];
77
+ };
78
+ // Generalization levels by zoom
79
+ this.generalizationLevelByZoom = options.generalizationLevelByZoom || [];
80
+ this.getGeneralizationLevelByZoom = (zoom) => {
81
+ if (options.getGeneralizationLevelByZoom) {
82
+ return options.getGeneralizationLevelByZoom(zoom, this.generalizationLevelByZoom);
83
+ }
84
+ return this.generalizationLevelByZoom[zoom];
85
+ };
86
+ // Render time interval by zoom
87
+ this.renderTimeIntervalByZoom = options.renderTimeIntervalByZoom || [
88
+ 100000, 50000, 40000, 30000, 20000, 15000, 10000, 5000, 2000, 1000, 400,
89
+ 300, 250, 180, 90, 60, 50, 50, 50, 50, 50,
90
+ ];
91
+ this.getRenderTimeIntervalByZoom = (zoom) => {
92
+ if (options.getRenderTimeIntervalByZoom) {
93
+ return options.getRenderTimeIntervalByZoom(zoom, this.renderTimeIntervalByZoom);
94
+ }
95
+ return this.renderTimeIntervalByZoom[zoom];
96
+ };
97
+ // This property will call api.setBbox on each movend event
98
+ this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd !== false;
99
+ // Define throttling and debounce render function
100
+ this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, 50, { leading: false, trailing: true });
101
+ this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, 50, { leading: true, maxWait: 5000, trailing: true });
102
+ this.renderState = {
103
+ center: [0, 0],
104
+ rotation: 0,
105
+ zoom: undefined,
106
+ };
107
+ this.onTrajectoryMessage = this.onTrajectoryMessage.bind(this);
108
+ this.onDeleteTrajectoryMessage = this.onDeleteTrajectoryMessage.bind(this);
109
+ this.onDocumentVisibilityChange =
110
+ this.onDocumentVisibilityChange.bind(this);
111
+ }
112
+ /**
113
+ * Add a trajectory.
114
+ * @param {RealtimeTrajectory} trajectory The trajectory to add.
115
+ * @private
116
+ */
117
+ addTrajectory(trajectory) {
118
+ if (!this.trajectories) {
119
+ this.trajectories = {};
120
+ }
121
+ const id = trajectory.properties.train_id;
122
+ if (id !== undefined) {
123
+ this.trajectories[id] = trajectory;
124
+ }
125
+ this.renderTrajectories();
126
+ }
127
+ attachToMap() {
128
+ // To avoid browser hanging when the tab is not visible for a certain amount of time,
129
+ // We stop the rendering and the websocket when hide and start again when show.
130
+ document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);
131
+ }
132
+ detachFromMap() {
133
+ document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
134
+ this.stop();
135
+ if (this.canvas) {
136
+ const context = this.canvas.getContext('2d');
137
+ if (context) {
138
+ context.clearRect(0, 0, this.canvas.width, this.canvas.height);
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Get the duration before the next update depending on zoom level.
144
+ *
145
+ * @private
146
+ */
147
+ getRefreshTimeInMs() {
148
+ var _a;
149
+ const viewState = this.getViewState();
150
+ const zoom = viewState.zoom || 0;
151
+ const roundedZoom = zoom !== undefined ? Math.round(zoom) : -1;
152
+ const timeStep = this.getRenderTimeIntervalByZoom(roundedZoom) || 25;
153
+ const nextTick = Math.max(25, timeStep / (this.speed || 1));
154
+ const nextThrottleTick = Math.min(nextTick, 500);
155
+ // TODO: see if this should go elsewhere.
156
+ if (this.useThrottle) {
157
+ this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true });
158
+ }
159
+ else if (this.useDebounce) {
160
+ this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, maxWait: 5000, trailing: true });
161
+ }
162
+ if ((_a = this.api) === null || _a === void 0 ? void 0 : _a.buffer) {
163
+ const [, size] = this.api.buffer;
164
+ this.api.buffer = [nextThrottleTick, size];
165
+ }
166
+ return nextTick;
167
+ }
168
+ /**
169
+ * Get vehicle.
170
+ * @param {function} filterFc A function use to filter results.
171
+ * @return {Array<Object>} Array of vehicle.
172
+ */
173
+ getVehicles(filterFc) {
174
+ return ((this.trajectories &&
175
+ // @ts-expect-error good type must be defined
176
+ Object.values(this.trajectories).filter(filterFc)) ||
177
+ []);
178
+ }
179
+ /**
180
+ * Request feature information for a given coordinate.
181
+ *
182
+ * @param {ol/coordinate~Coordinate} coordinate Coordinate.
183
+ * @param {Object} options Options See child classes to see which options are supported.
184
+ * @param {number} [options.resolution=1] The resolution of the map.
185
+ * @param {number} [options.nb=Infinity] The max number of vehicles to return.
186
+ * @return {Promise<FeatureInfo>} Promise with features, layer and coordinate.
187
+ */
188
+ getVehiclesAtCoordinate(coordinate, options) {
189
+ const { resolution } = this.getViewState();
190
+ const { hitTolerance, nb } = options || {};
191
+ const ext = buffer([...coordinate, ...coordinate], (hitTolerance || 5) * (resolution || 1));
192
+ let trajectories = Object.values(this.trajectories || {});
193
+ if (this.sort) {
194
+ // @ts-expect-error good type must be defined
195
+ trajectories = trajectories.sort(this.sort);
196
+ }
197
+ const vehicles = [];
198
+ for (let i = 0; i < trajectories.length; i += 1) {
199
+ const { coordinate: trajcoord } = trajectories[i].properties;
200
+ if (trajcoord && containsCoordinate(ext, trajcoord)) {
201
+ vehicles.push(trajectories[i]);
202
+ }
203
+ if (vehicles.length === nb) {
204
+ break;
205
+ }
206
+ }
207
+ return { features: vehicles, type: 'FeatureCollection' };
208
+ }
209
+ /**
210
+ * Callback on websocket's deleted_vehicles channel events.
211
+ * It removes the trajectory from the list.
212
+ *
213
+ * @private
214
+ * @override
215
+ */
216
+ onDeleteTrajectoryMessage(data) {
217
+ if (!data.content) {
218
+ return;
219
+ }
220
+ this.removeTrajectory(data.content);
221
+ }
222
+ onDocumentVisibilityChange() {
223
+ if (document.hidden) {
224
+ this.stop();
225
+ // Since we don't receive deleted_vehicles event when docuement
226
+ // is hidden. We have to clean all the trajectories for a fresh
227
+ // start when the document is visible again.
228
+ this.trajectories = {};
229
+ }
230
+ else {
231
+ const viewState = this.getViewState();
232
+ if (!viewState.visible) {
233
+ return;
234
+ }
235
+ this.start();
236
+ }
237
+ }
238
+ /**
239
+ * Callback on websocket's trajectory channel events.
240
+ * It adds a trajectory to the list.
241
+ *
242
+ * @private
243
+ */
244
+ onTrajectoryMessage(data) {
245
+ if (!data.content) {
246
+ return;
247
+ }
248
+ const trajectory = data.content;
249
+ const { geometry, properties: { raw_coordinates: rawCoordinates, time_since_update: timeSinceUpdate, }, } = trajectory;
250
+ // ignore old events [SBAHNM-97]
251
+ // @ts-expect-error can be undefined
252
+ if (timeSinceUpdate < 0) {
253
+ return;
254
+ }
255
+ // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);
256
+ if (this.purgeTrajectory(trajectory)) {
257
+ return;
258
+ }
259
+ if (this.debug &&
260
+ this.mode === RealtimeModes.TOPOGRAPHIC &&
261
+ rawCoordinates) {
262
+ // @ts-expect-error missing type definition
263
+ trajectory.properties.olGeometry = this.format.readGeometry({
264
+ coordinates: fromLonLat(rawCoordinates),
265
+ type: 'Point',
266
+ });
267
+ }
268
+ else {
269
+ // @ts-expect-error missing type definition
270
+ trajectory.properties.olGeometry = this.format.readGeometry(geometry);
271
+ }
272
+ // TODO Make sure the timeOffset is useful. May be we can remove it.
273
+ // @ts-expect-error missing type definition
274
+ trajectory.properties.timeOffset = Date.now() - data.timestamp;
275
+ this.addTrajectory(trajectory);
276
+ }
277
+ /**
278
+ * On zoomend we adjust the time interval of the update of vehicles positions.
279
+ *
280
+ * @private
281
+ */
282
+ onZoomEnd() {
283
+ this.startUpdateTime();
284
+ }
285
+ /**
286
+ * Remove all trajectories that are in the past.
287
+ */
288
+ purgeOutOfDateTrajectories() {
289
+ Object.entries(this.trajectories || {}).forEach(([key, trajectory]) => {
290
+ var _a;
291
+ const timeIntervals = (_a = trajectory === null || trajectory === void 0 ? void 0 : trajectory.properties) === null || _a === void 0 ? void 0 : _a.time_intervals;
292
+ if (this.time && (timeIntervals === null || timeIntervals === void 0 ? void 0 : timeIntervals.length)) {
293
+ const lastTimeInterval = timeIntervals[timeIntervals.length - 1][0];
294
+ if (lastTimeInterval < this.time.getTime()) {
295
+ this.removeTrajectory(key);
296
+ }
297
+ }
298
+ });
299
+ }
300
+ /**
301
+ * Determine if the trajectory is useless and should be removed from the list or not.
302
+ * By default, this function exclude vehicles:
303
+ * - that have their trajectory outside the current extent and
304
+ * - that aren't in the MOT list.
305
+ *
306
+ * @param {RealtimeTrajectory} trajectory
307
+ * @return {boolean} if the trajectory must be displayed or not.
308
+ * @private
309
+ */
310
+ purgeTrajectory(trajectory) {
311
+ const viewState = this.getViewState();
312
+ const extent = viewState.extent;
313
+ const { bounds, type } = trajectory.properties;
314
+ if ((this.isUpdateBboxOnMoveEnd && extent && !intersects(extent, bounds)) ||
315
+ (this.mots && !this.mots.includes(type))) {
316
+ this.removeTrajectory(trajectory);
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+ removeTrajectory(trajectoryOrId) {
322
+ var _a;
323
+ let id;
324
+ if (typeof trajectoryOrId !== 'string') {
325
+ id = (_a = trajectoryOrId === null || trajectoryOrId === void 0 ? void 0 : trajectoryOrId.properties) === null || _a === void 0 ? void 0 : _a.train_id;
326
+ }
327
+ else {
328
+ id = trajectoryOrId;
329
+ }
330
+ if (id !== undefined && this.trajectories) {
331
+ delete this.trajectories[id];
332
+ }
333
+ }
334
+ /**
335
+ * Render the trajectories requesting an animation frame and cancelling the previous one.
336
+ * This function must be overrided by children to provide the correct parameters.
337
+ *
338
+ * @param {boolean} noInterpolate If true trajectories are not interpolated but
339
+ * drawn at the last known coordinate. Use this for performance optimization
340
+ * during map navigation.
341
+ * @private
342
+ */
343
+ renderTrajectories(noInterpolate) {
344
+ const viewState = this.getViewState();
345
+ if (this.requestId) {
346
+ cancelAnimationFrame(this.requestId);
347
+ this.requestId = undefined;
348
+ }
349
+ if (!(viewState === null || viewState === void 0 ? void 0 : viewState.center) || !(viewState === null || viewState === void 0 ? void 0 : viewState.extent) || !(viewState === null || viewState === void 0 ? void 0 : viewState.size)) {
350
+ return;
351
+ }
352
+ if (!noInterpolate && this.useRequestAnimationFrame) {
353
+ this.requestId = requestAnimationFrame(() => {
354
+ this.renderTrajectoriesInternal(viewState, noInterpolate);
355
+ });
356
+ }
357
+ else if (!noInterpolate && this.useDebounce) {
358
+ this.debounceRenderTrajectories(viewState, noInterpolate);
359
+ }
360
+ else if (!noInterpolate && this.useThrottle) {
361
+ this.throttleRenderTrajectories(viewState, noInterpolate);
362
+ }
363
+ else {
364
+ this.renderTrajectoriesInternal(viewState, noInterpolate);
365
+ }
366
+ }
367
+ /**
368
+ * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method.
369
+ *
370
+ * @param {object} viewState The view state of the map.
371
+ * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.
372
+ * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.
373
+ * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.
374
+ * @param {number} [viewState.rotation = 0] Rotation of the map to render.
375
+ * @param {number} viewState.resolution Resolution of the map to render.
376
+ * @param {boolean} noInterpolate If true trajectories are not interpolated but
377
+ * drawn at the last known coordinate. Use this for performance optimization
378
+ * during map navigation.
379
+ * @private
380
+ */
381
+ renderTrajectoriesInternal(viewState, noInterpolate = false) {
382
+ var _a, _b;
383
+ if (!this.trajectories || !this.shouldRender()) {
384
+ return false;
385
+ }
386
+ const time = this.live ? Date.now() : (_a = this.time) === null || _a === void 0 ? void 0 : _a.getTime();
387
+ const trajectories = Object.values(this.trajectories);
388
+ // console.time('sort');
389
+ if (this.sort) {
390
+ // @ts-expect-error type problem
391
+ trajectories.sort(this.sort);
392
+ }
393
+ // console.timeEnd('sort');
394
+ if (!this.canvas || !this.style) {
395
+ return true;
396
+ }
397
+ 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
398
+ ? true
399
+ : noInterpolate, selectedVehicleId: this.selectedVehicleId }, this.styleOptions));
400
+ (_b = this.onRender) === null || _b === void 0 ? void 0 : _b.call(this, this.renderState, viewState);
401
+ // console.timeEnd('render');
402
+ return true;
403
+ }
404
+ setBbox() {
405
+ const viewState = this.getViewState();
406
+ const extent = viewState.extent;
407
+ const zoom = viewState.zoom || 0;
408
+ if (!extent || Number.isNaN(zoom)) {
409
+ return;
410
+ }
411
+ // Clean trajectories before sending the new bbox
412
+ // Purge trajectories:
413
+ // - which are outside the extent
414
+ // - when it's bus and zoom level is too low for them
415
+ if (this.trajectories && extent && zoom) {
416
+ const keys = Object.keys(this.trajectories);
417
+ for (let i = keys.length - 1; i >= 0; i -= 1) {
418
+ this.purgeTrajectory(this.trajectories[keys[i]]);
419
+ }
420
+ }
421
+ // The backend only supports non float value
422
+ const zoomFloor = Math.floor(zoom);
423
+ if (!extent || Number.isNaN(zoomFloor)) {
424
+ return;
425
+ }
426
+ // The extent does not need to be precise under meter, so we round floor/ceil the values.
427
+ const [minX, minY, maxX, maxY] = extent;
428
+ const bbox = [
429
+ Math.floor(minX),
430
+ Math.floor(minY),
431
+ Math.ceil(maxX),
432
+ Math.ceil(maxY),
433
+ zoomFloor,
434
+ ];
435
+ /* @private */
436
+ this.generalizationLevel = this.getGeneralizationLevelByZoom(zoomFloor);
437
+ if (this.generalizationLevel) {
438
+ bbox.push(`gen=${this.generalizationLevel}`);
439
+ }
440
+ /* @private */
441
+ this.mots = this.getMotsByZoom(zoomFloor);
442
+ if (this.mots) {
443
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
444
+ bbox.push(`mots=${this.mots}`);
445
+ }
446
+ if (this.tenant) {
447
+ bbox.push(`tenant=${this.tenant}`);
448
+ }
449
+ if (this.mode !== 'topographic') {
450
+ bbox.push(`channel_prefix=${this.mode}`);
451
+ }
452
+ if (this.bboxParameters) {
453
+ Object.entries(this.bboxParameters).forEach(([key, value]) => {
454
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
455
+ bbox.push(`${key}=${value}`);
456
+ });
457
+ }
458
+ // Extent and zoom level are mandatory.
459
+ this.api.bbox = bbox;
460
+ }
461
+ start() {
462
+ this.stop();
463
+ // Before starting to update trajectories, we remove trajectories that have
464
+ // a time_intervals in the past, it will
465
+ // avoid phantom train that are at the end of their route because we never
466
+ // received the deleted_vehicle event because we have changed the browser tab.
467
+ this.purgeOutOfDateTrajectories();
468
+ this.renderTrajectories();
469
+ this.startUpdateTime();
470
+ this.api.open();
471
+ this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
472
+ this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);
473
+ // Update the bbox on each move end
474
+ if (this.isUpdateBboxOnMoveEnd) {
475
+ this.setBbox();
476
+ }
477
+ if (this.onStart) {
478
+ this.onStart(this);
479
+ }
480
+ }
481
+ /**
482
+ * Start the clock.
483
+ * @private
484
+ */
485
+ startUpdateTime() {
486
+ this.stopUpdateTime();
487
+ this.updateTimeDelay = this.getRefreshTimeInMs() || 0;
488
+ this.updateTimeInterval = window.setInterval(() => {
489
+ // When live=true, we update the time with new Date();
490
+ if (this.live) {
491
+ this.time = new Date();
492
+ }
493
+ else if (this.time && this.updateTimeDelay && this.speed) {
494
+ this.time = new Date(this.time.getTime() + this.updateTimeDelay * this.speed);
495
+ }
496
+ }, this.updateTimeDelay);
497
+ }
498
+ stop() {
499
+ this.api.unsubscribeTrajectory(this.onTrajectoryMessage);
500
+ this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);
501
+ this.api.close();
502
+ if (this.onStop) {
503
+ this.onStop(this);
504
+ }
505
+ }
506
+ /**
507
+ * Stop the clock.
508
+ * @private
509
+ */
510
+ stopUpdateTime() {
511
+ if (this.updateTimeInterval) {
512
+ clearInterval(this.updateTimeInterval);
513
+ this.updateTimeInterval = undefined;
514
+ }
515
+ }
516
+ get mode() {
517
+ return this._mode;
518
+ }
519
+ set mode(newMode) {
520
+ var _a, _b;
521
+ if (newMode === this._mode) {
522
+ return;
523
+ }
524
+ this._mode = newMode;
525
+ if ((_b = (_a = this.api) === null || _a === void 0 ? void 0 : _a.wsApi) === null || _b === void 0 ? void 0 : _b.open) {
526
+ this.stop();
527
+ this.start();
528
+ }
529
+ }
530
+ get speed() {
531
+ return this._speed;
532
+ }
533
+ set speed(newSpeed) {
534
+ this._speed = newSpeed;
535
+ this.start();
536
+ }
537
+ get style() {
538
+ return this._style;
539
+ }
540
+ set style(newStyle) {
541
+ this._style = newStyle;
542
+ this.renderTrajectories();
543
+ }
544
+ get time() {
545
+ return this._time;
546
+ }
547
+ set time(newTime) {
548
+ this._time = (newTime === null || newTime === void 0 ? void 0 : newTime.getTime)
549
+ ? newTime
550
+ : new Date(newTime);
551
+ this.renderTrajectories();
552
+ }
553
+ }
554
+ export default RealtimeEngine;
@@ -1,3 +1,2 @@
1
- /** @private */
2
1
  declare const getLayersAsFlatArray: (layersOrLayer: any | any[]) => any[];
3
2
  export default getLayersAsFlatArray;
@@ -1,5 +1,4 @@
1
1
  // TODO: I use any to avoid circular dependency with common/layers/layer
2
- /** @private */
3
2
  const getLayersAsFlatArray = (layersOrLayer) => {
4
3
  let layers = layersOrLayer;
5
4
  if (!Array.isArray(layers)) {
@@ -9,12 +9,9 @@ import { LineString } from 'ol/geom';
9
9
  * @private
10
10
  */
11
11
  const getVehiclePosition = (now, trajectory, noInterpolate) => {
12
- const {
13
- // @ts-expect-error coordinate is added by the RealtimeLayer
14
- coordinate,
12
+ const { coordinate,
15
13
  // @ts-expect-error olGeometry is added by the RealtimeLayer
16
14
  olGeometry, time_intervals: timeIntervals, } = trajectory.properties;
17
- // @ts-expect-error
18
15
  let { coordinates, type } = trajectory.geometry;
19
16
  let geometry = olGeometry;
20
17
  let coord;
@@ -57,7 +57,7 @@ export declare const getTextSize: (ctx: AnyCanvasContext, markerSize: number, te
57
57
  * @param {boolean} cancelled true if the journey is cancelled.
58
58
  * @param {boolean} isDelayText true if the color is used for delay text of the symbol.
59
59
  */
60
- export declare const getDelayColor: (delayInMs: number | null, cancelled?: boolean, isDelayText?: boolean) => string;
60
+ export declare const getDelayColor: (delayInMs: null | number, cancelled?: boolean, isDelayText?: boolean) => string;
61
61
  /**
62
62
  * @private
63
63
  */
@@ -1,4 +1,3 @@
1
- /** @private */
2
1
  const radiusMapping = [
3
2
  [0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
4
3
  [0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
@@ -5,6 +5,7 @@ import { AnyCanvas, RealtimeRenderState, RealtimeStyleFunction, RealtimeStyleOpt
5
5
  * @param {ViewState} trajectories An array of trajectories.
6
6
  * @param {Function} style A function that returns a canvas representing a vehicle of a specific trajectory.
7
7
  * @param {ViewState} viewState The view state of the map.
8
+ * @param {Object} options The options.
8
9
  * @param {boolean} options.hoverVehicleId The id of the vehicle to highlight.
9
10
  * @param {boolean} options.selectedVehicleId The id of the vehicle to select.
10
11
  * @param {boolean} options.noInterpolate If true trajectories are not interpolated but
@@ -6,6 +6,7 @@ import getVehiclePosition from './getVehiclePosition';
6
6
  * @param {ViewState} trajectories An array of trajectories.
7
7
  * @param {Function} style A function that returns a canvas representing a vehicle of a specific trajectory.
8
8
  * @param {ViewState} viewState The view state of the map.
9
+ * @param {Object} options The options.
9
10
  * @param {boolean} options.hoverVehicleId The id of the vehicle to highlight.
10
11
  * @param {boolean} options.selectedVehicleId The id of the vehicle to select.
11
12
  * @param {boolean} options.noInterpolate If true trajectories are not interpolated but
@@ -8,6 +8,7 @@ import type { RealtimeDepartureExtended } from '../../types';
8
8
  *
9
9
  * @param {Object} depObject The object containing departures by id.
10
10
  * @param {boolean} [sortByMinArrivalTime=false] If true sort departures by arrival time.
11
+ * @param {number} [maxDepartureAge=30] The maximum departure age in minutes.
11
12
  * @return {RealtimeDeparture[]} Return departures array.
12
13
  * @private
13
14
  */
@@ -7,6 +7,7 @@ import compareDepartures from './compareDepartures';
7
7
  *
8
8
  * @param {Object} depObject The object containing departures by id.
9
9
  * @param {boolean} [sortByMinArrivalTime=false] If true sort departures by arrival time.
10
+ * @param {number} [maxDepartureAge=30] The maximum departure age in minutes.
10
11
  * @return {RealtimeDeparture[]} Return departures array.
11
12
  * @private
12
13
  */
@@ -1,10 +1,10 @@
1
1
  import { ControlPosition, IControl } from 'maplibre-gl';
2
2
  /**
3
- * Display layer's copyrights.
3
+ * Display layer's attributions trying to remove duplicated ones.
4
4
  *
5
5
  * @example
6
- * import { Map } from 'Maplibre-gl';
7
- * import { CopyrightControl } from 'mobility-toolbox-js/Maplibre';
6
+ * import { Map } from 'maplibre-gl';
7
+ * import { CopyrightControl } from 'mobility-toolbox-js/maplibre';
8
8
  *
9
9
  * const map = new Map({
10
10
  * container: 'map',
@@ -15,21 +15,24 @@ import { ControlPosition, IControl } from 'maplibre-gl';
15
15
  * map.addControl(control);
16
16
  *
17
17
  *
18
- * @see <a href="/example/mb-copyright">Maplibre copyright example</a>
18
+ * @see <a href="/example/mb-realtime>MapLibre Realtime layer example</a>
19
19
  *
20
+ * @implements {maplibregl.IControl}
21
+ *
22
+ * @public
20
23
  */
21
24
  declare class CopyrightControl implements IControl {
22
- map?: maplibregl.Map;
23
25
  container?: HTMLElement;
24
26
  content?: string;
27
+ map?: maplibregl.Map;
25
28
  options?: {
26
29
  customAttribution?: string | string[];
27
30
  separator?: string;
28
31
  };
29
32
  constructor(options?: {});
33
+ getDefaultPosition(): ControlPosition;
30
34
  onAdd(map: maplibregl.Map): HTMLElement;
31
35
  onRemove(): HTMLElement | undefined;
32
- getDefaultPosition(): ControlPosition;
33
36
  render(): void;
34
37
  }
35
38
  export default CopyrightControl;