From abf4f34e53e78dbf661f7ac94540e344e65e0711 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 18 Aug 2021 13:50:24 +0200 Subject: [PATCH 001/109] Adapt doc --- src/api/tralis/TralisAPI.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index f2c2a3d6..afc6c008 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -413,7 +413,7 @@ class TralisAPI { /** * Get a full trajectory of a vehicule . * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @param {TralisMode} mode Tralis mode. * @returns {Promise} Return a full trajectory. */ @@ -434,7 +434,7 @@ class TralisAPI { /** * Get full trajectories of a vehicules . * - * @param {number[]} ids List of vehicles ids. + * @param {string[]} ids List of vehicles ids. * @param {TralisMode} mode Tralis mode. * @returns {Promise} Return an array of full trajectories. */ @@ -448,7 +448,7 @@ class TralisAPI { /** * Subscribe to full_trajectory channel of a given vehicle. * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @param {TralisMode} mode Tralis mode. */ subscribeFullTrajectory(id, mode) { @@ -470,7 +470,7 @@ class TralisAPI { /** * Unsubscribe from full_trajectory channel * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @param {function} cb Callback function to unsubscribe. If null all subscriptions for the channel will be unsubscribed. */ unsubscribeFullTrajectory(id, cb) { @@ -480,7 +480,7 @@ class TralisAPI { /** * Get the list of stops for this vehicle. * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @returns {Promise} Returns a stop sequence object. */ getStopSequence(id) { @@ -491,7 +491,7 @@ class TralisAPI { this.conn.get( params, (data) => { - // Remove the delay from arrivalTime nad departureTime + // Remove the delay from arrivalTime nnd departureTime resolve(cleanStopTime(data.content && data.content[0])); }, (err) => { @@ -504,7 +504,7 @@ class TralisAPI { /** * Get a list of stops for a list of vehicles. * - * @param {number[]} ids List of vehicles ids. + * @param {string[]} ids List of vehicles ids. * @returns {Promise} Return an array of stop sequences. */ getStopSequences(ids) { @@ -517,7 +517,7 @@ class TralisAPI { /** * Subscribe to stopsequence channel of a given vehicle. * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @param {function(stopSequence: StopSequence)} onMessage Function called on each message of the channel. */ subscribeStopSequence(id, onMessage) { @@ -540,7 +540,7 @@ class TralisAPI { /** * Unsubscribe from stopsequence channel * - * @param {number} id A vehicle id. + * @param {string} id A vehicle id. * @param {function} cb Callback function to unsubscribe. If null all subscriptions for the channel will be unsubscribed. */ unsubscribeStopSequence(id, cb) { From da3c2edef6348a1d425d6987c5e8afd8efb8758d Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 19 Aug 2021 13:49:55 +0200 Subject: [PATCH 002/109] Clean tracker doc and constructor --- src/common/Tracker.js | 54 ++++++++++++------- src/common/mixins/TrackerLayerMixin.js | 74 ++++++++++++++++++++------ 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 21ca939a..e8cc9b61 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -30,8 +30,8 @@ export default class Tracker { this.renderedTrajectories = []; /** - * Array of ol events key, returned by on() or once(). - * @type {Array} + * Active interpolation calcultion or not. If false, the train will not move until we receive the next message for the websocket. + * @type {boolean} */ this.interpolate = !!opts.interpolate; @@ -45,13 +45,13 @@ export default class Tracker { * Id of the trajectory which is hovered. * @type {string} */ - this.hoverVehicleId = null; + this.hoverVehicleId = opts.hoverVehicleId; /** * Id of the trajectory which is selected. * @type {string} */ - this.selectedVehicleId = null; + this.selectedVehicleId = opts.selectedVehicleId; /** * Scale the vehicle icons with this value. @@ -59,6 +59,24 @@ export default class Tracker { */ this.iconScale = opts.iconScale; + /** + * Function use to filter the features displayed. + * @type {function} + */ + this.filter = opts.filter; + + /** + * Function use to sort the features displayed. + * @type {function} + */ + this.sort = opts.sort; + + /** + * Function use to style the features displayed. + * @type {function} + */ + this.style = opts.style; + // we draw directly on the canvas since openlayers is too slow. /** * HTML element. @@ -140,24 +158,20 @@ export default class Tracker { /** * Set the filter for tracker features. * @param {function} filter Filter function. + * @private + * @deprecated Set the property this.filter directly. */ setFilter(filter) { - /** - * Current filter function. - * @type {function} - */ this.filter = filter; } /** * Set the sort for tracker features. * @param {function} sort Sort function. + * @private + * @deprecated Set the property this.sort directly. */ setSort(sort) { - /** - * The sort function for tracker features. - * @type {function} - */ this.sort = sort; } @@ -165,6 +179,7 @@ export default class Tracker { * Set the id of the trajectory which is hovered. * @param {string} id Id of a vehicle. * @private + * @deprecated Set the property this.hoverVehicleId directly. */ setHoverVehicleId(id) { if (id !== this.hoverVehicleId) { @@ -176,6 +191,7 @@ export default class Tracker { * Set the id of the trajectory which is selected. * @param {string} id Id of a vehicle. * @private + * @deprecated Set the property this.selectedVehicleId directly. */ setSelectedVehicleId(id) { if (id !== this.selectedVehicleId) { @@ -186,6 +202,8 @@ export default class Tracker { /** * set the scale of the vehicle icons. * @param {number} iconScale Scale value. + * @private + * @deprecated Set the property this.iconScale directly. */ setIconScale(iconScale) { this.iconScale = iconScale; @@ -193,14 +211,12 @@ export default class Tracker { /** * Set the tracker style. - * @param {function} s OpenLayers style function. + * @param {function} style OpenLayers style function. + * @private + * @deprecated Set the property this.style directly. */ - setStyle(s) { - /** - * Style function. - * @type {function} - */ - this.style = s; + setStyle(style) { + this.style = style; } /** diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index d8819d62..aedda2f4 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -123,8 +123,33 @@ const TrackerLayerMixin = (Base) => isHoverActive: true, ...options, }; + + // Tracker options use to build the tracker. + const { + interpolate, + hoverVehicleId, + selectedVehicleId, + filter, + sort, + } = options; + const initTrackerOptions = { + interpolate, + hoverVehicleId, + selectedVehicleId, + filter, + sort, + style, + }; + Object.keys(initTrackerOptions).forEach( + (key) => + initTrackerOptions[key] === undefined && + delete initTrackerOptions[key], + ); + let cuurSpeed = speed || 1; + super.defineProperties(options); + Object.defineProperties(this, { isTrackerLayer: { value: true }, isHoverActive: { @@ -143,17 +168,17 @@ const TrackerLayerMixin = (Base) => }, filter: { get: () => this.tracker.filter, - set: (filter) => { + set: (newFilter) => { if (this.tracker) { - this.tracker.setFilter(filter); + this.tracker.filter = newFilter; } }, }, sort: { get: () => this.tracker.sort, - set: (sort) => { + set: (newSort) => { if (this.sort) { - this.tracker.setSort(sort); + this.tracker.sort = newSort; } }, }, @@ -199,8 +224,8 @@ const TrackerLayerMixin = (Base) => get: () => { return this.tracker.hoverVehicleId; }, - set: (hoverVehicleId) => { - this.tracker.hoverVehicleId = hoverVehicleId; + set: (newHoverVehicleId) => { + this.tracker.hoverVehicleId = newHoverVehicleId; }, }, @@ -211,10 +236,18 @@ const TrackerLayerMixin = (Base) => get: () => { return this.tracker.selectedVehicleId; }, - set: (selectedVehicleId) => { - this.tracker.selectedVehicleId = selectedVehicleId; + set: (newSelectedVehicleId) => { + this.tracker.selectedVehicleId = newSelectedVehicleId; }, }, + + /** + * Options used by the constructor of the Tracker class. + */ + initTrackerOptions: { + value: initTrackerOptions, + writable: false, + }, }); } @@ -222,14 +255,25 @@ const TrackerLayerMixin = (Base) => * Initalize the Tracker. * @param {ol/Map~Map} map * @param {Object} options - * @param {Number} [options.width] Canvas's width. - * @param {Number} [options.height] Canvas's height. + * @param {number} [options.width] Canvas's width. + * @param {number} [options.height] Canvas's height. + * @param {bool} [options.interpolate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). + * @param {string} [options.hoverVehicleId] Id of the trajectory which is hovered. + * @param {string} [options.selectedVehicleId] Id of the trajectory which is selected. + * @param {number} [options.iconScale] Scale the vehicle icons with this value. * @param {function} [options.getPixelFromCoordinate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). + * @param {function} [options.filter] Function use to filter the features displayed. + * @param {function} [options.sort] Function use to sort the features displayed. + * @param {function} [options.style] Function use to style the features displayed. */ - init(map, options) { + init(map, options = {}) { super.init(map); - this.tracker = new Tracker(options); - this.tracker.setStyle((props, r) => this.style(props, r)); + console.log(options); + this.tracker = new Tracker({ + style: (props, r) => this.style(props, r), + ...this.initTrackerOptions, + ...options, + }); if (this.visible) { this.start(); @@ -261,8 +305,8 @@ const TrackerLayerMixin = (Base) => * Start the clock. * * @param {Array} size Map's size: [width, height]. - * @param {Number} zoom Map's zoom level. - * @param {Number} resolution Map's resolution. + * @param {number} zoom Map's zoom level. + * @param {number} resolution Map's resolution. */ start(size, zoom, resolution) { this.stop(); From 6c4972555265ffe80cacb0af75c3523709d80835 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 19 Aug 2021 13:52:37 +0200 Subject: [PATCH 003/109] Clean tracker doc and constructor --- src/common/mixins/TrackerLayerMixin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index aedda2f4..6ebfe76e 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -268,7 +268,7 @@ const TrackerLayerMixin = (Base) => */ init(map, options = {}) { super.init(map); - console.log(options); + this.tracker = new Tracker({ style: (props, r) => this.style(props, r), ...this.initTrackerOptions, From ad1d2c28cf3fe07033d45aa356842c2a1fab19c2 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 19 Aug 2021 13:54:13 +0200 Subject: [PATCH 004/109] v1.3.6-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b0af743..8443f7d1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.5", + "version": "1.3.6-beta.0", "main": "index.js", "module": "module.js", "dependencies": { From 229c2f72b6868c624e478e9a9de352a9032d3b9b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 22 Sep 2021 08:46:39 +0200 Subject: [PATCH 005/109] Abort previous requests --- src/common/mixins/SearchMixin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/mixins/SearchMixin.js b/src/common/mixins/SearchMixin.js index 7ae409df..cf5fb56b 100644 --- a/src/common/mixins/SearchMixin.js +++ b/src/common/mixins/SearchMixin.js @@ -54,6 +54,7 @@ const SearchMixin = (Base) => apiOptions.url = url; } this.api = new StopsAPI(apiOptions); + this.abortController = new AbortController(); } render(suggestions = []) { @@ -101,7 +102,9 @@ const SearchMixin = (Base) => this.inputElt.placeholder = this.placeholder; this.inputElt.autoComplete = 'off'; this.inputElt.onkeyup = (evt) => { - this.search(evt.target.value); + this.abortController.abort(); + this.abortController = new AbortController(); + this.search(evt.target.value, this.abortController); }; Object.assign(this.inputElt.style, { padding: '10px 30px 10px 10px', From 3c6d8d1231da02a61c343233d2e4911f5594c5a9 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 22 Sep 2021 09:07:30 +0200 Subject: [PATCH 006/109] Try CODEOWNERS file --- .github/CODEOWNERS | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..1df25a82 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,37 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @danji90 @friedjoff @gevos @oterral + +# Order is important; the last matching pattern takes the most +# precedence. When someone opens a pull request that only +# modifies JS files, only @js-owner and not the global +# owner(s) will be requested for a review. +# *.js @js-owner + +# You can also use email addresses if you prefer. They'll be +# used to look up users just like we do for commit author +# emails. +# *.go docs@example.com + +# In this example, @doctocat owns any files in the build/logs +# directory at the root of the repository and any of its +# subdirectories. +# /build/logs/ @doctocat + +# The `docs/*` pattern will match files like +# `docs/getting-started.md` but not further nested files like +# `docs/build-app/troubleshooting.md`. +# docs/* docs@example.com + +# In this example, @octocat owns any file in an apps directory +# anywhere in your repository. +# apps/ @octocat + +# In this example, @doctocat owns any file in the `/docs` +# directory in the root of your repository. +# /docs/ @doctocat \ No newline at end of file From 571478e4eb162a538b6e8c75e4d726c030821551 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 22 Sep 2021 09:36:30 +0200 Subject: [PATCH 007/109] Remove CODEOWNERS file --- .github/CODEOWNERS | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 1df25a82..00000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,37 +0,0 @@ -# This is a comment. -# Each line is a file pattern followed by one or more owners. - -# These owners will be the default owners for everything in -# the repo. Unless a later match takes precedence, -# @global-owner1 and @global-owner2 will be requested for -# review when someone opens a pull request. -* @danji90 @friedjoff @gevos @oterral - -# Order is important; the last matching pattern takes the most -# precedence. When someone opens a pull request that only -# modifies JS files, only @js-owner and not the global -# owner(s) will be requested for a review. -# *.js @js-owner - -# You can also use email addresses if you prefer. They'll be -# used to look up users just like we do for commit author -# emails. -# *.go docs@example.com - -# In this example, @doctocat owns any files in the build/logs -# directory at the root of the repository and any of its -# subdirectories. -# /build/logs/ @doctocat - -# The `docs/*` pattern will match files like -# `docs/getting-started.md` but not further nested files like -# `docs/build-app/troubleshooting.md`. -# docs/* docs@example.com - -# In this example, @octocat owns any file in an apps directory -# anywhere in your repository. -# apps/ @octocat - -# In this example, @doctocat owns any file in the `/docs` -# directory in the root of your repository. -# /docs/ @doctocat \ No newline at end of file From 0bf509ad6f89c181841988765ab8359350cbcb9d Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 28 Sep 2021 09:58:47 +0200 Subject: [PATCH 008/109] v1.3.13-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c363e541..f5415135 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.12", + "version": "1.3.13-beta.1", "main": "index.js", "module": "module.js", "dependencies": { From ce9b48cb7a19ed9a7efe48bf658093fed26d5f28 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 28 Sep 2021 10:29:02 +0200 Subject: [PATCH 009/109] Unsubscribe previous call with same callback function --- src/api/tralis/TralisAPI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index afc6c008..a6b6abcc 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -395,7 +395,7 @@ class TralisAPI { * @param {function(response: { content: Vehicle })} onMessage Function called on each message of the channel. */ subscribeDeletedVehicles(mode, onMessage) { - this.unsubscribeDeletedVehicles(); + this.unsubscribeDeletedVehicles(onMessage); this.subscribe( `deleted_vehicles${getModeSuffix(mode, TralisModes)}`, onMessage, From 5c4ebdd5a36e6f0e298fcc6b49319269a21729c4 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 28 Sep 2021 11:24:11 +0200 Subject: [PATCH 010/109] v1.3.13-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5415135..7147695d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.1", + "version": "1.3.13-beta.2", "main": "index.js", "module": "module.js", "dependencies": { From 9a5a87a238101c696185064c689d6c3b1d028ace Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 29 Sep 2021 14:41:56 +0200 Subject: [PATCH 011/109] MAke sure setBBox doesn't read event twice --- src/api/tralis/WebSocketConnector.js | 27 ++++++++++++- src/api/tralis/WebSocketConnector.test.js | 47 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index f2af9cee..5e1493bc 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -138,6 +138,9 @@ class WebSocketConnector { * @returns {{onMessage: function, errorCb: function}} Object with onMessage and error callbacks */ listen(params, cb, errorCb) { + // Remove the previous identical callback + this.unlisten(params, cb); + const onMessage = (e) => { const data = JSON.parse(e.data); let source = params.channel; @@ -161,6 +164,20 @@ class WebSocketConnector { return { onMessageCb: onMessage, onErrorCb: errorCb }; } + unlisten(params, cb) { + this.subscriptions + .filter((s) => { + return s.params.channel === params.channel && (!cb || s.cb === cb); + }) + .forEach(({ onMessageCb, onErrorCb }) => { + this.websocket.removeEventListener('message', onMessageCb); + if (onErrorCb) { + this.websocket.removeEventListener('error', onErrorCb); + this.websocket.removeEventListener('close', onErrorCb); + } + }); + } + /** * Subscribe to a given channel. * @private @@ -174,7 +191,15 @@ class WebSocketConnector { const reqStr = WebSocketConnector.getRequestString('', params); if (!quiet) { - this.subscriptions.push({ params, cb, errorCb, onMessageCb, onErrorCb }); + const index = this.subscriptions.findIndex((subcr) => { + return params.channel === subcr.params.channel && cb === subcr.cb; + }); + const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb }; + if (index > -1) { + this.subscriptions[index] = newSubscr; + } else { + this.subscriptions.push(newSubscr); + } } if (!this.subscribed[reqStr]) { diff --git a/src/api/tralis/WebSocketConnector.test.js b/src/api/tralis/WebSocketConnector.test.js index 2f794f05..f113922d 100644 --- a/src/api/tralis/WebSocketConnector.test.js +++ b/src/api/tralis/WebSocketConnector.test.js @@ -55,19 +55,66 @@ describe('WebSocketConnector', () => { test('should unsubscribe all subscriptions related to a channel', () => { // eslint-disable-next-line no-unused-vars const client = new Connector(`ws://foo:1234`); + client.websocket.removeEventListener = jest.fn(); + client.websocket.addEventListener = jest.fn(); const params = { channel: 'foo' }; const params2 = { channel: 'bar' }; const cb = jest.fn(); const cb2 = jest.fn(); client.subscribe(params, cb); + client.subscribe(params, cb); + client.subscribe(params, cb); + client.subscribe(params, cb2); + client.subscribe(params2, cb2); + expect(client.subscriptions.length).toBe(3); + expect(client.websocket.removeEventListener).toBeCalledTimes(2); + expect(client.websocket.addEventListener).toBeCalledTimes(9); + + client.unsubscribe('foo'); + expect(client.subscriptions.length).toBe(1); + expect(client.subscriptions[0].params).toBe(params2); + expect(client.subscriptions[0].cb).toBe(cb2); + }); + }); + + describe('#setBbox', () => { + test.only('should remove subscriptions before re adding it ', () => { + // eslint-disable-next-line no-unused-vars + const client = new Connector(`ws://foo:1234`); + client.websocket.removeEventListener = jest.fn(); + client.websocket.addEventListener = jest.fn(); + client.send = jest.fn(); + const params = { channel: 'foo' }; + const params2 = { channel: 'bar' }; + const cb = jest.fn(); + const cb2 = jest.fn(); + client.subscribe(params, cb); + client.subscribe(params, cb); + client.subscribe(params, cb); client.subscribe(params, cb2); client.subscribe(params2, cb2); expect(client.subscriptions.length).toBe(3); + expect(client.websocket.removeEventListener).toBeCalledTimes(2); + expect(client.websocket.addEventListener).toBeCalledTimes(5); + + client.websocket.removeEventListener.mockReset(); + client.websocket.addEventListener.mockReset(); + + client.setBbox([0, 0, 0, 0]); + + expect(client.subscriptions.length).toBe(3); + expect(client.websocket.removeEventListener).toBeCalledTimes(3); + expect(client.websocket.addEventListener).toBeCalledTimes(3); client.unsubscribe('foo'); expect(client.subscriptions.length).toBe(1); expect(client.subscriptions[0].params).toBe(params2); expect(client.subscriptions[0].cb).toBe(cb2); + client.unsubscribe('bar'); + expect(client.subscriptions.length).toBe(0); + client.send.mockRestore(); + client.websocket.removeEventListener.mockRestore(); + client.websocket.addEventListener.mockRestore(); }); }); }); From f6c14a327ff7c687278dc5cfd692fa6448c3e26a Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 29 Sep 2021 14:42:26 +0200 Subject: [PATCH 012/109] v1.3.13-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7147695d..a576487f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.2", + "version": "1.3.13-beta.3", "main": "index.js", "module": "module.js", "dependencies": { From 84b6e67c625e95913db74c2bb16537dcc2beefa9 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 14:27:36 +0200 Subject: [PATCH 013/109] Fix render in the past activating on new live mode --- src/common/mixins/TrackerLayerMixin.js | 53 +++++++++++++++++++++----- src/mapbox/layers/TrackerLayer.js | 2 +- src/mapbox/layers/TrajservLayer.js | 8 +--- src/mapbox/layers/TralisLayer.js | 6 +-- src/ol/layers/TrackerLayer.js | 11 +----- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 629b623c..c376f397 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -13,6 +13,7 @@ import { timeSteps } from '../trackerConfig'; * @classproperty {function} style - Style of the vehicle. * @classproperty {FilterFunction} filter - Time speed. * @classproperty {function} sort - Set the filter for tracker features. + * @classproperty {boolean} live - If true, the layer will always use Date.now() to render trajectories. Default to true. * @classproperty {boolean} useRequestAnimationFrame - If true, encapsulates the renderTrajectories calls in a requestAnimationFrame. Experimental. */ export class TrackerLayerInterface { @@ -160,6 +161,11 @@ const TrackerLayerMixin = (Base) => style: { value: style || this.defaultStyle, }, + + /** + * Speed of the wheel of time. + * If live property is true. The speed is ignored. + */ speed: { get: () => cuurSpeed, set: (newSpeed) => { @@ -194,8 +200,18 @@ const TrackerLayerMixin = (Base) => */ styleCache: { value: {} }, + /** + * If true. The layer will always use Date.now() on the next tick to render the trajectories. + * When true, setCurrTime will have no effect. + */ + live: { + value: true, + writable: true, + }, + /** * Time used to display the trajectories. + * If live property is true. This function does nothing execpt rerender the trajectories using Date.now(). */ currTime: { value: new Date(), @@ -203,7 +219,7 @@ const TrackerLayerMixin = (Base) => }, /** - * Keep track of the last time used to render trajectories. + * Keep track of the last update of the interval. * Useful when the speed increase. */ lastUpdateTime: { @@ -320,7 +336,7 @@ const TrackerLayerMixin = (Base) => start(size, zoom, resolution) { this.stop(); this.tracker.setVisible(true); - this.renderTrajectories(this.currTime, size, resolution); + this.renderTrajectories(size, resolution); this.startUpdateTime(zoom); } @@ -360,36 +376,55 @@ const TrackerLayerMixin = (Base) => } } + /** + * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. + * @private + */ + renderTrajectoriesInternal(size, resolution, noInterpolate) { + if (!this.tracker) { + return; + } + + const renderTime = this.live ? Date.now() : this.currTime; + this.tracker.renderTrajectories( + renderTime, + size, + resolution, + noInterpolate, + ); + } + /** * Render the trajectories requesting an animation frame and cancelling the previous one * @private */ - renderTrajectories(time, size, resolution) { + renderTrajectories(size, resolution, noInterpolate) { if (this.requestId) { cancelAnimationFrame(this.requestId); } + if (this.useRequestAnimationFrame) { this.requestId = requestAnimationFrame(() => { - this.tracker.renderTrajectories(time, size, resolution); + this.renderTrajectoriesInternal(size, resolution, noInterpolate); }); } else { - this.tracker.renderTrajectories(time, size, resolution); + this.renderTrajectoriesInternal(size, resolution, noInterpolate); } } /** * Set the current time, it triggers a rendering of the trajectories. + * If live is true. This function will have no effect. * @param {dateString | value} time * @param {Array} size * @param {number} resolution * @param {boolean} [mustRender=true] */ setCurrTime(time, size, resolution, mustRender = true) { - const newTime = new Date(time); - this.currTime = newTime; - this.lastUpdateTime = new Date(); + this.currTime = new Date(time); + this.lastUpdateTime = this.currTime; if (mustRender) { - this.renderTrajectories(this.currTime, size, resolution); + this.renderTrajectories(size, resolution); } } diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index fb02a88f..0b6f4a5d 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -162,7 +162,7 @@ class TrackerLayer extends mixin(Layer) { this.map.getContainer().style.cursor = vehicle ? 'pointer' : 'auto'; this.hoverVehicleId = id; // We doesn´t wait the next render, we force it. - this.renderTrajectories(this.currTime); + this.renderTrajectories(); } } } diff --git a/src/mapbox/layers/TrajservLayer.js b/src/mapbox/layers/TrajservLayer.js index 88654d12..58bea539 100644 --- a/src/mapbox/layers/TrajservLayer.js +++ b/src/mapbox/layers/TrajservLayer.js @@ -133,14 +133,8 @@ class TrajservLayer extends mixin(TrackerLayer) { */ onMove() { this.map.getSource(this.key).setCoordinates(getSourceCoordinates(this.map)); - const { width, height } = this.map.getCanvas(); - - this.renderTrajectories( - this.currTime, - [width, height], - getResolution(this.map), - ); + this.renderTrajectories([width, height], getResolution(this.map)); } /** diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 1686669d..5dc9e84b 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -93,11 +93,7 @@ class TralisLayer extends mixin(TrackerLayer) { .getSource('canvas-source') .setCoordinates(getSourceCoordinates(this.map)); const { width, height } = this.map.getCanvas(); - this.renderTrajectories( - this.currTime, - [width, height], - getResolution(this.map), - ); + this.renderTrajectories([width, height], getResolution(this.map)); } /** diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index b57aed34..6aeb8442 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -53,12 +53,7 @@ class TrackerLayer extends mixin(Layer) { return this.tracker.canvas; } - this.renderTrajectories( - this.currTime, - frameState.size, - resolution, - true, - ); + this.renderTrajectories(frameState.size, resolution, true); } else if ( this.renderState.center[0] !== center[0] || this.renderState.center[1] !== center[1] @@ -162,9 +157,7 @@ class TrackerLayer extends mixin(Layer) { ? 'pointer' : 'auto'; this.hoverVehicleId = id; - - // We doesn´t wait the next render, we force it. - this.renderTrajectories(this.currTime); + this.renderTrajectories(); } }), ]; From 1e205b76998cc0ff736f4cd10ac1ad850e2776c8 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 14:28:46 +0200 Subject: [PATCH 014/109] v1.3.13-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a576487f..0ebd8a22 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.3", + "version": "1.3.13-beta.4", "main": "index.js", "module": "module.js", "dependencies": { From 1205ea6fa04089e9b0faa167c2878d25dcf3a57b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 15:51:54 +0200 Subject: [PATCH 015/109] Test pixelRatio --- src/common/Tracker.js | 32 ++++++++++++++++++-------- src/common/mixins/TrackerLayerMixin.js | 2 ++ src/mapbox/layers/TrajservLayer.js | 7 +----- src/mapbox/layers/TralisLayer.js | 4 ---- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index ec46d7d8..acc10d0e 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -17,6 +17,11 @@ export default class Tracker { ...options, }; + /** + * Pixel ratio to use to draw the canvas. Default to window.devicePixelRatio + * @type {Array} + */ + this.pixelRatio = options.pixelRatio || window.devicePixelRatio || 1; /** * Array of trajectories. * @type {Array} @@ -83,16 +88,16 @@ export default class Tracker { * @type {Canvas} */ this.canvas = opts.canvas || document.createElement('canvas'); - this.canvas.width = opts.width; - this.canvas.height = opts.height; + this.canvas.width = opts.width * this.pixelRatio; + this.canvas.height = opts.height * this.pixelRatio; this.canvas.setAttribute( 'style', [ 'position: absolute', 'top: 0', 'bottom: 0', - 'width: 100%', - 'height: 100%', + `width: ${opts.width}px`, + `height: ${opts.height}px`, 'pointer-events: none', 'visibility: visible', 'margin-top: inherit', // for scrolling behavior. @@ -248,8 +253,6 @@ export default class Tracker { noInterpolate = false, ) { this.clear(); - this.canvas.style.left = '0px'; - this.canvas.style.top = '0px'; const [width, height] = size; if ( @@ -257,13 +260,21 @@ export default class Tracker { height && (this.canvas.width !== width || this.canvas.height !== height) ) { - [this.canvas.width, this.canvas.height] = [width, height]; + [this.canvas.width, this.canvas.height] = [ + width * this.pixelRatio, + height * this.pixelRatio, + ]; } + + this.canvas.style.left = '0px'; + this.canvas.style.top = '0px'; + this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; + this.canvas.style.height = `${this.canvas.height / this.pixelRatio}px`; /** * Current resolution. * @type {number} */ - this.currResolution = resolution || this.currResolution; + (this.currResolution = resolution || this.currResolution); let hoverVehicleImg; let hoverVehiclePx; let hoverVehicleWidth; @@ -358,12 +369,15 @@ export default class Tracker { if (coord) { // We set the rotation of the trajectory (used by tralis). this.trajectories[i].coordinate = coord; - const px = this.getPixelFromCoordinate(coord); + let px = this.getPixelFromCoordinate(coord); if (!px) { // eslint-disable-next-line no-continue continue; } + px = px.map((p) => { + return p * this.pixelRatio; + }); // Trajectory with pixel (i.e. within map extent) will be in renderedTrajectories. this.trajectories[i].rendered = true; this.renderedTrajectories.push(this.trajectories[i]); diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index c376f397..5411ae5f 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -128,6 +128,7 @@ const TrackerLayerMixin = (Base) => // Tracker options use to build the tracker. const { + pixelRatio, interpolate, hoverVehicleId, selectedVehicleId, @@ -135,6 +136,7 @@ const TrackerLayerMixin = (Base) => sort, } = options; const initTrackerOptions = { + pixelRatio, interpolate, hoverVehicleId, selectedVehicleId, diff --git a/src/mapbox/layers/TrajservLayer.js b/src/mapbox/layers/TrajservLayer.js index 58bea539..9a2b6b62 100644 --- a/src/mapbox/layers/TrajservLayer.js +++ b/src/mapbox/layers/TrajservLayer.js @@ -45,10 +45,6 @@ class TrajservLayer extends mixin(TrackerLayer) { super.init(map); - const { width, height } = map.getCanvas(); - this.tracker.canvas.width = width; - this.tracker.canvas.height = height; - const source = { type: 'canvas', canvas: this.tracker.canvas, @@ -133,8 +129,7 @@ class TrajservLayer extends mixin(TrackerLayer) { */ onMove() { this.map.getSource(this.key).setCoordinates(getSourceCoordinates(this.map)); - const { width, height } = this.map.getCanvas(); - this.renderTrajectories([width, height], getResolution(this.map)); + this.renderTrajectories(undefined, getResolution(this.map)); } /** diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 5dc9e84b..1db13056 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -46,10 +46,6 @@ class TralisLayer extends mixin(TrackerLayer) { this.map.on('move', this.onMove); this.map.on('moveend', this.onMoveEnd); - const { width, height } = this.map.getCanvas(); - this.tracker.canvas.width = width; - this.tracker.canvas.height = height; - this.map.addSource('canvas-source', { type: 'canvas', canvas: this.tracker.canvas, From 69a0fc039149516fcb923f01388ff65f31aa7d38 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 16:23:00 +0200 Subject: [PATCH 016/109] Fix style with pixelRatio --- src/common/Tracker.js | 11 ++++++++--- src/common/mixins/TrajservLayerMixin.js | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index acc10d0e..4dd66fcd 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -16,6 +16,7 @@ export default class Tracker { interpolate: true, ...options, }; + console.log(window.devicePixelRatio); /** * Pixel ratio to use to draw the canvas. Default to window.devicePixelRatio @@ -381,7 +382,11 @@ export default class Tracker { // Trajectory with pixel (i.e. within map extent) will be in renderedTrajectories. this.trajectories[i].rendered = true; this.renderedTrajectories.push(this.trajectories[i]); - const vehicleImg = this.style(traj, this.currResolution); + const vehicleImg = this.style( + traj, + this.currResolution, + this.pixelRatio, + ); if (!vehicleImg) { // eslint-disable-next-line no-continue @@ -392,8 +397,8 @@ export default class Tracker { let imgHeight = vehicleImg.height; if (this.iconScale) { - imgHeight = Math.floor(imgHeight * this.iconScale); - imgWidth = Math.floor(imgWidth * this.iconScale); + imgHeight = Math.floor(imgHeight * this.iconScale * this.pixelRatio); + imgWidth = Math.floor(imgWidth * this.iconScale * this.pixelRatio); } if ( diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index 3401ef98..a9793552 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -418,7 +418,7 @@ const TrajservLayerMixin = (TrackerLayer) => }); } - defaultStyle(props, zoom) { + defaultStyle(props, zoom, pixelRatio = 3) { const { type, name, @@ -435,8 +435,8 @@ const TrajservLayerMixin = (TrackerLayer) => const key = `${z}${type}${name}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; if (!this.styleCache[key]) { - let radius = getRadius(type, z); - const isDisplayStrokeAndDelay = radius >= 7; + let radius = getRadius(type, z) * pixelRatio; + const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio; if (radius === 0) { this.styleCache[key] = null; @@ -444,16 +444,18 @@ const TrajservLayerMixin = (TrackerLayer) => } if (hover || selected) { - radius = isDisplayStrokeAndDelay ? radius + 5 : 14; + radius = isDisplayStrokeAndDelay + ? radius + 5 * pixelRatio + : 14 * pixelRatio; } - const margin = 1; + const margin = 1 * pixelRatio; const radiusDelay = radius + 2; const markerSize = radius * 2; const canvas = document.createElement('canvas'); // add space for delay information - canvas.width = radiusDelay * 2 + margin * 2 + 100; - canvas.height = radiusDelay * 2 + margin * 2 + 100; + canvas.width = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + canvas.height = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; const ctx = canvas.getContext('2d'); const origin = canvas.width / 2; @@ -484,7 +486,7 @@ const TrajservLayerMixin = (TrackerLayer) => ctx.fillStyle = getDelayColor(delay, cancelled, true); ctx.strokeStyle = this.delayOutlineColor; - ctx.lineWidth = 1.5; + ctx.lineWidth = 1.5 * pixelRatio; const delayText = getDelayText(delay, cancelled); ctx.strokeText(delayText, origin + radiusDelay + margin, origin); ctx.fillText(delayText, origin + radiusDelay + margin, origin); @@ -501,7 +503,7 @@ const TrajservLayerMixin = (TrackerLayer) => ctx.save(); if (isDisplayStrokeAndDelay || hover || selected) { - ctx.lineWidth = 1; + ctx.lineWidth = 1 * pixelRatio; ctx.strokeStyle = '#000000'; } ctx.fillStyle = circleFillColor; From 636cde89c8ed9f958b72a5e931a0e7057781b8f6 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 17:07:15 +0200 Subject: [PATCH 017/109] Fix pixelRatio --- src/common/mixins/TrackerLayerMixin.js | 20 ++++++++++++++++- src/common/mixins/TrajservLayerMixin.js | 20 ++++++++--------- src/mapbox/layers/TrackerLayer.js | 29 +++++-------------------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 5411ae5f..62550c2d 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -136,7 +136,7 @@ const TrackerLayerMixin = (Base) => sort, } = options; const initTrackerOptions = { - pixelRatio, + pixelRatio: pixelRatio || window.devicePixelRatio || 1, interpolate, hoverVehicleId, selectedVehicleId, @@ -260,6 +260,24 @@ const TrackerLayerMixin = (Base) => }, }, + /** + * Pixel ratio use for the rendering. Default to window.devicePixelRatio. + */ + pixelRatio: { + get: () => { + return this.tracker + ? this.tracker.pixelRatio + : this.initTrackerOptions.pixelRatio; + }, + set: (newPixelRatio) => { + if (!this.tracker) { + this.tracker.pixelRatio = newPixelRatio; + } else { + this.initTrackerOptions.pixelRatio = newPixelRatio; + } + }, + }, + /** * Options used by the constructor of the Tracker class. */ diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index a9793552..f0e33d89 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -418,7 +418,7 @@ const TrajservLayerMixin = (TrackerLayer) => }); } - defaultStyle(props, zoom, pixelRatio = 3) { + defaultStyle(props, zoom) { const { type, name, @@ -435,8 +435,8 @@ const TrajservLayerMixin = (TrackerLayer) => const key = `${z}${type}${name}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; if (!this.styleCache[key]) { - let radius = getRadius(type, z) * pixelRatio; - const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio; + let radius = getRadius(type, z) * this.pixelRatio; + const isDisplayStrokeAndDelay = radius >= 7 * this.pixelRatio; if (radius === 0) { this.styleCache[key] = null; @@ -445,17 +445,17 @@ const TrajservLayerMixin = (TrackerLayer) => if (hover || selected) { radius = isDisplayStrokeAndDelay - ? radius + 5 * pixelRatio - : 14 * pixelRatio; + ? radius + 5 * this.pixelRatio + : 14 * this.pixelRatio; } - const margin = 1 * pixelRatio; + const margin = 1 * this.pixelRatio; const radiusDelay = radius + 2; const markerSize = radius * 2; const canvas = document.createElement('canvas'); // add space for delay information - canvas.width = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; - canvas.height = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + canvas.width = radiusDelay * 2 + margin * 2 + 100 * this.pixelRatio; + canvas.height = radiusDelay * 2 + margin * 2 + 100 * this.pixelRatio; const ctx = canvas.getContext('2d'); const origin = canvas.width / 2; @@ -486,7 +486,7 @@ const TrajservLayerMixin = (TrackerLayer) => ctx.fillStyle = getDelayColor(delay, cancelled, true); ctx.strokeStyle = this.delayOutlineColor; - ctx.lineWidth = 1.5 * pixelRatio; + ctx.lineWidth = 1.5 * this.pixelRatio; const delayText = getDelayText(delay, cancelled); ctx.strokeText(delayText, origin + radiusDelay + margin, origin); ctx.fillText(delayText, origin + radiusDelay + margin, origin); @@ -503,7 +503,7 @@ const TrajservLayerMixin = (TrackerLayer) => ctx.save(); if (isDisplayStrokeAndDelay || hover || selected) { - ctx.lineWidth = 1 * pixelRatio; + ctx.lineWidth = 1 * this.pixelRatio; ctx.strokeStyle = '#000000'; } ctx.fillStyle = circleFillColor; diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index 0b6f4a5d..b404c480 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -21,14 +21,6 @@ class TrackerLayer extends mixin(Layer) { this.onMapMouseMove = this.onMapMouseMove.bind(this); } - /** - * Update the icon scale if the window (and probably the canvas) is resized. - * @private - */ - updateIconScale(canvas) { - this.tracker.setIconScale(canvas.width / canvas.clientWidth); - } - /** * Initialize the layer. * @@ -41,29 +33,18 @@ class TrackerLayer extends mixin(Layer) { } const canvas = map.getCanvas(); - const iconScale = canvas.width / canvas.clientWidth; - map.on('resize', this.updateIconScale.bind(this, canvas)); super.init(map, { - width: canvas.width, - height: canvas.height, - iconScale, + width: canvas.width / this.pixelRatio, + height: canvas.height / this.pixelRatio, getPixelFromCoordinate: (coord) => { - const pixelRatio = window.devicePixelRatio || 1; const [lng, lat] = toLonLat(coord); const { x, y } = this.map.project({ lng, lat }); - return [x * pixelRatio, y * pixelRatio]; + return [x, y]; }, }); } - terminate() { - if (this.map) { - this.map.off('resize', this.updateIconScale); - } - return super.terminate(); - } - /** * Set the current time, it triggers a rendering of the trajectories. * @@ -73,7 +54,7 @@ class TrackerLayer extends mixin(Layer) { const canvas = this.map.getCanvas(); super.setCurrTime( time, - [canvas.width, canvas.height], + [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], getResolution(this.map), !this.map.isMoving() && !this.map.isRotating() && !this.map.isZooming(), ); @@ -89,7 +70,7 @@ class TrackerLayer extends mixin(Layer) { start() { const canvas = this.map.getCanvas(); super.start( - [canvas.width, canvas.height], + [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], this.map.getZoom(), getResolution(this.map), ); From a907785699d65844a64c02f93c3aec54eee342e1 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 17:15:02 +0200 Subject: [PATCH 018/109] Fix pixelRatio for tralis --- src/common/Tracker.js | 1 - src/doc/examples/assets/tralis-live-map/s1kreis.svg | 8 ++++---- src/doc/examples/tralis-live-map.js | 2 ++ src/mapbox/layers/TralisLayer.js | 5 ++++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 4dd66fcd..4ee4e920 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -16,7 +16,6 @@ export default class Tracker { interpolate: true, ...options, }; - console.log(window.devicePixelRatio); /** * Pixel ratio to use to draw the canvas. Default to window.devicePixelRatio diff --git a/src/doc/examples/assets/tralis-live-map/s1kreis.svg b/src/doc/examples/assets/tralis-live-map/s1kreis.svg index a0ab07a4..8a588815 100644 --- a/src/doc/examples/assets/tralis-live-map/s1kreis.svg +++ b/src/doc/examples/assets/tralis-live-map/s1kreis.svg @@ -73,7 +73,7 @@ cy="48.799999" r="12.499999" fill="#00b6e2" - style="stroke-width:0.322997" /> + style="stroke-width: 0.322997;" /> + style="stroke-width: 0.32299;" /> + style="stroke-width: 0.32299;" /> + style="stroke-width: 0.32299;" /> diff --git a/src/doc/examples/tralis-live-map.js b/src/doc/examples/tralis-live-map.js index 490724f5..27e699b3 100644 --- a/src/doc/examples/tralis-live-map.js +++ b/src/doc/examples/tralis-live-map.js @@ -21,6 +21,8 @@ export default () => { style: (props) => { const img = new Image(); img.src = LINE_IMAGES[(props.line || {}).name || 'unknown']; + img.width = 25 * tracker.pixelRatio; + img.height = 25 * tracker.pixelRatio; return img; }, }); diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 1db13056..aed7b770 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -89,7 +89,10 @@ class TralisLayer extends mixin(TrackerLayer) { .getSource('canvas-source') .setCoordinates(getSourceCoordinates(this.map)); const { width, height } = this.map.getCanvas(); - this.renderTrajectories([width, height], getResolution(this.map)); + this.renderTrajectories( + [width / this.pixelRatio, height / this.pixelRatio], + getResolution(this.map), + ); } /** From a5a51d658f98452b81625960a70fc8794b023206 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 30 Sep 2021 17:15:42 +0200 Subject: [PATCH 019/109] v1.3.13-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ebd8a22..29dd5e40 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.4", + "version": "1.3.13-beta.5", "main": "index.js", "module": "module.js", "dependencies": { From 201f9b3a11c25f56b37a3ed6c4f15b6d16abbdf6 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 1 Oct 2021 13:53:45 +0200 Subject: [PATCH 020/109] Clean TrackerLayerMixin --- src/common/mixins/TrackerLayerMixin.js | 64 +++++++++++++++++++------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 62550c2d..ef4f5c18 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -156,10 +156,18 @@ const TrackerLayerMixin = (Base) => Object.defineProperties(this, { isTrackerLayer: { value: true }, + + /** + * Active on hover effect. + */ isHoverActive: { value: !!isHoverActive, writable: true, }, + + /** + * Style function used to render a vehicle. + */ style: { value: style || this.defaultStyle, }, @@ -175,19 +183,33 @@ const TrackerLayerMixin = (Base) => this.start(); }, }, + + /** + * Function to filter which vehicles to display. + */ filter: { - get: () => this.tracker.filter, + get: () => + this.tracker ? this.tracker.filter : this.initTrackerOptions.filter, set: (newFilter) => { if (this.tracker) { this.tracker.filter = newFilter; + } else { + this.initTrackerOptions.filter = newFilter; } }, }, + + /** + * Function to sort the vehicles to display. + */ sort: { - get: () => this.tracker.sort, + get: () => + this.tracker ? this.tracker.sort : this.initTrackerOptions.sort, set: (newSort) => { - if (this.sort) { + if (this.tracker) { this.tracker.sort = newSort; + } else { + this.initTrackerOptions.sort = newSort; } }, }, @@ -233,7 +255,7 @@ const TrackerLayerMixin = (Base) => * Keep track of which trajectories are currently drawn. */ renderedTrajectories: { - get: () => this.tracker.renderedTrajectories, + get: () => this.tracker?.renderedTrajectories || [], }, /** @@ -241,10 +263,16 @@ const TrackerLayerMixin = (Base) => */ hoverVehicleId: { get: () => { - return this.tracker.hoverVehicleId; + return this.tracker + ? this.tracker.hoverVehicleId + : this.initTrackerOptions.hoverVehicleId; }, set: (newHoverVehicleId) => { - this.tracker.hoverVehicleId = newHoverVehicleId; + if (this.tracker) { + this.tracker.hoverVehicleId = newHoverVehicleId; + } else { + this.initTrackerOptions.hoverVehicleId = newHoverVehicleId; + } }, }, @@ -252,11 +280,16 @@ const TrackerLayerMixin = (Base) => * Id of the selected vehicle. */ selectedVehicleId: { - get: () => { - return this.tracker.selectedVehicleId; - }, + get: () => + this.tracker + ? this.tracker.selectedVehicleId + : this.initTrackerOptions.selectedVehicleId, set: (newSelectedVehicleId) => { - this.tracker.selectedVehicleId = newSelectedVehicleId; + if (this.tracker) { + this.tracker.selectedVehicleId = newSelectedVehicleId; + } else { + this.initTrackerOptions.selectedVehicleId = newSelectedVehicleId; + } }, }, @@ -264,13 +297,12 @@ const TrackerLayerMixin = (Base) => * Pixel ratio use for the rendering. Default to window.devicePixelRatio. */ pixelRatio: { - get: () => { - return this.tracker + get: () => + this.tracker ? this.tracker.pixelRatio - : this.initTrackerOptions.pixelRatio; - }, + : this.initTrackerOptions.pixelRatio, set: (newPixelRatio) => { - if (!this.tracker) { + if (this.tracker) { this.tracker.pixelRatio = newPixelRatio; } else { this.initTrackerOptions.pixelRatio = newPixelRatio; @@ -287,7 +319,7 @@ const TrackerLayerMixin = (Base) => }, /** - * If true, encapsulates the renderTrajectories calls in a requestAnimationFrame + * If true, encapsulates the renderTrajectories calls in a requestAnimationFrame. */ useRequestAnimationFrame: { default: false, From ab127df27c0dad8a5693fd3a7b38390245937fbd Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 1 Oct 2021 14:02:17 +0200 Subject: [PATCH 021/109] Remove useless property --- src/common/Tracker.js | 88 +------------------------- src/common/mixins/TrackerLayerMixin.js | 1 - 2 files changed, 2 insertions(+), 87 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 4ee4e920..1519bb59 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -58,12 +58,6 @@ export default class Tracker { */ this.selectedVehicleId = opts.selectedVehicleId; - /** - * Scale the vehicle icons with this value. - * @type {number} - */ - this.iconScale = opts.iconScale; - /** * Function use to filter the features displayed. * @type {function} @@ -140,15 +134,6 @@ export default class Tracker { return this.trajectories || []; } - /** - * Return rendered trajectories. - * Use this to avoid race conditions while rendering. - * @returns {array} trajectories - */ - getRenderedTrajectories() { - return this.renderedTrajectories; - } - /** * Clear the canvas. * @private @@ -159,70 +144,6 @@ export default class Tracker { } } - /** - * Set the filter for tracker features. - * @param {function} filter Filter function. - * @private - * @deprecated Set the property this.filter directly. - */ - setFilter(filter) { - this.filter = filter; - } - - /** - * Set the sort for tracker features. - * @param {function} sort Sort function. - * @private - * @deprecated Set the property this.sort directly. - */ - setSort(sort) { - this.sort = sort; - } - - /** - * Set the id of the trajectory which is hovered. - * @param {string} id Id of a vehicle. - * @private - * @deprecated Set the property this.hoverVehicleId directly. - */ - setHoverVehicleId(id) { - if (id !== this.hoverVehicleId) { - this.hoverVehicleId = id; - } - } - - /** - * Set the id of the trajectory which is selected. - * @param {string} id Id of a vehicle. - * @private - * @deprecated Set the property this.selectedVehicleId directly. - */ - setSelectedVehicleId(id) { - if (id !== this.selectedVehicleId) { - this.selectedVehicleId = id; - } - } - - /** - * set the scale of the vehicle icons. - * @param {number} iconScale Scale value. - * @private - * @deprecated Set the property this.iconScale directly. - */ - setIconScale(iconScale) { - this.iconScale = iconScale; - } - - /** - * Set the tracker style. - * @param {function} style OpenLayers style function. - * @private - * @deprecated Set the property this.style directly. - */ - setStyle(style) { - this.style = style; - } - /** * Move the canvas. * @param {number} offsetX Offset X. @@ -392,13 +313,8 @@ export default class Tracker { continue; } - let imgWidth = vehicleImg.width; - let imgHeight = vehicleImg.height; - - if (this.iconScale) { - imgHeight = Math.floor(imgHeight * this.iconScale * this.pixelRatio); - imgWidth = Math.floor(imgWidth * this.iconScale * this.pixelRatio); - } + const imgWidth = vehicleImg.width; + const imgHeight = vehicleImg.height; if ( this.hoverVehicleId !== traj.id && diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index ef4f5c18..757371f7 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -337,7 +337,6 @@ const TrackerLayerMixin = (Base) => * @param {bool} [options.interpolate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). * @param {string} [options.hoverVehicleId] Id of the trajectory which is hovered. * @param {string} [options.selectedVehicleId] Id of the trajectory which is selected. - * @param {number} [options.iconScale] Scale the vehicle icons with this value. * @param {function} [options.getPixelFromCoordinate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). * @param {function} [options.filter] Function use to filter the features displayed. * @param {function} [options.sort] Function use to sort the features displayed. From bfcad675639d8ab1711cc30094b42ae08996c4db Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 1 Oct 2021 15:40:15 +0200 Subject: [PATCH 022/109] Allow rotation on mapbox tralis layer --- __mocks__/mapbox-gl.js | 9 +++++++++ src/common/mixins/TralisLayerMixin.js | 8 -------- src/doc/examples/tralis-live-map.js | 7 +++---- src/mapbox/layers/TralisLayer.js | 1 + src/mapbox/utils.js | 15 ++++++++++----- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/__mocks__/mapbox-gl.js b/__mocks__/mapbox-gl.js index 61182453..80f820f3 100644 --- a/__mocks__/mapbox-gl.js +++ b/__mocks__/mapbox-gl.js @@ -41,6 +41,15 @@ class Map { loaded() {} remove() {} + + unproject() { + return [ + [0, 0], + [0, 0], + [0, 0], + [0, 0], + ]; + } } module.exports = { Map, diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 07319463..229e1b23 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -19,8 +19,6 @@ export class TralisLayerInterface { * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/). * @param {boolean} [options.debug=false] Display additional debug informations. * @param {TralisMode} [options.mode=TralisMode.TOPOGRAPHIC] - Mode. - * @param {number} [options.dfltIconScale=0.6] - Scale of vehicle icons. - * @param {number} [options.dfltIconHighlightScale=0.8] - Scale of vehicle icons when they are highlighted. */ constructor(options = {}) {} @@ -89,12 +87,6 @@ const TralisLayerMixin = (TrackerLayer) => this.onDeleteMessage = this.onDeleteMessage.bind(this); this.api = options.api || new TralisAPI(options); this.format = new GeoJSON(); - - // These scales depends from the size specifed in the svgs. - // For some reason the size must be specified in the svg (../img/lines) for firefox. - this.dfltIconScale = options.dfltIconScale || 0.6; - this.dfltIconHighlightScale = options.dfltIconHighlightScale || 0.8; - this.minIconScale = this.dfltIconScale * 0.75; } init(map) { diff --git a/src/doc/examples/tralis-live-map.js b/src/doc/examples/tralis-live-map.js index 27e699b3..4496afe1 100644 --- a/src/doc/examples/tralis-live-map.js +++ b/src/doc/examples/tralis-live-map.js @@ -11,18 +11,17 @@ export default () => { zoom: 10, touchPitch: false, pitchWithRotate: false, - dragRotate: false, - touchZoomRotate: false, }); const tracker = new TralisLayer({ url: 'wss://api.geops.io/realtime-ws/v1/', apiKey: window.apiKey, + bbox: [1152072, 6048052, 1433666, 6205578], style: (props) => { const img = new Image(); img.src = LINE_IMAGES[(props.line || {}).name || 'unknown']; - img.width = 25 * tracker.pixelRatio; - img.height = 25 * tracker.pixelRatio; + img.width = 25 * window.devicePixelRatio; + img.height = 25 * window.devicePixelRatio; return img; }, }); diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index aed7b770..47afd2ba 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -88,6 +88,7 @@ class TralisLayer extends mixin(TrackerLayer) { this.map .getSource('canvas-source') .setCoordinates(getSourceCoordinates(this.map)); + const { width, height } = this.map.getCanvas(); this.renderTrajectories( [width / this.pixelRatio, height / this.pixelRatio], diff --git a/src/mapbox/utils.js b/src/mapbox/utils.js index adf1f9dc..23ba4c37 100644 --- a/src/mapbox/utils.js +++ b/src/mapbox/utils.js @@ -21,12 +21,17 @@ export const getResolution = (map) => { * @private */ export const getSourceCoordinates = (map) => { - const bounds = map.getBounds().toArray(); + // Requesting getBounds is not enough when we rotate the map, so we request manually each corner. + const { width, height } = map.getCanvas(); + const leftTop = map.unproject({ x: 0, y: 0 }); + const leftBottom = map.unproject({ x: 0, y: height }); // southWest + const rightBottom = map.unproject({ x: width, y: height }); + const rightTop = map.unproject({ x: width, y: 0 }); // north east return [ - [bounds[0][0], bounds[1][1]], - [...bounds[1]], - [bounds[1][0], bounds[0][1]], - [...bounds[0]], + [leftTop.lng, leftTop.lat], + [rightTop.lng, rightTop.lat], + [rightBottom.lng, rightBottom.lat], + [leftBottom.lng, leftBottom.lat], ]; }; From 5ffeb24cc2404711dc963d96e779a2a401e344cf Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 1 Oct 2021 15:44:10 +0200 Subject: [PATCH 023/109] Allow rotation on mapbox tralis layer --- src/mapbox/layers/TrajservLayer.js | 6 ++++-- src/mapbox/layers/TralisLayer.js | 4 ++-- src/mapbox/utils.js | 11 +++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/mapbox/layers/TrajservLayer.js b/src/mapbox/layers/TrajservLayer.js index 9a2b6b62..e9e02be8 100644 --- a/src/mapbox/layers/TrajservLayer.js +++ b/src/mapbox/layers/TrajservLayer.js @@ -48,7 +48,7 @@ class TrajservLayer extends mixin(TrackerLayer) { const source = { type: 'canvas', canvas: this.tracker.canvas, - coordinates: getSourceCoordinates(map), + coordinates: getSourceCoordinates(map, this.pixelRatio), // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance. animate: true, attribution: this.copyrights, @@ -128,7 +128,9 @@ class TrajservLayer extends mixin(TrackerLayer) { * @private */ onMove() { - this.map.getSource(this.key).setCoordinates(getSourceCoordinates(this.map)); + this.map + .getSource(this.key) + .setCoordinates(getSourceCoordinates(this.map, this.pixelRatio)); this.renderTrajectories(undefined, getResolution(this.map)); } diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 47afd2ba..1154b8a7 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -49,7 +49,7 @@ class TralisLayer extends mixin(TrackerLayer) { this.map.addSource('canvas-source', { type: 'canvas', canvas: this.tracker.canvas, - coordinates: getSourceCoordinates(this.map), + coordinates: getSourceCoordinates(this.map, this.pixelRatio), // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance. animate: true, }); @@ -87,7 +87,7 @@ class TralisLayer extends mixin(TrackerLayer) { onMove() { this.map .getSource('canvas-source') - .setCoordinates(getSourceCoordinates(this.map)); + .setCoordinates(getSourceCoordinates(this.map, this.pixelRatio)); const { width, height } = this.map.getCanvas(); this.renderTrajectories( diff --git a/src/mapbox/utils.js b/src/mapbox/utils.js index 23ba4c37..53c80426 100644 --- a/src/mapbox/utils.js +++ b/src/mapbox/utils.js @@ -20,13 +20,16 @@ export const getResolution = (map) => { * @param {mapboxgl.Map} map A map object. * @private */ -export const getSourceCoordinates = (map) => { +export const getSourceCoordinates = (map, pixelRatio) => { // Requesting getBounds is not enough when we rotate the map, so we request manually each corner. const { width, height } = map.getCanvas(); const leftTop = map.unproject({ x: 0, y: 0 }); - const leftBottom = map.unproject({ x: 0, y: height }); // southWest - const rightBottom = map.unproject({ x: width, y: height }); - const rightTop = map.unproject({ x: width, y: 0 }); // north east + const leftBottom = map.unproject({ x: 0, y: height / pixelRatio }); // southWest + const rightBottom = map.unproject({ + x: width / pixelRatio, + y: height / pixelRatio, + }); + const rightTop = map.unproject({ x: width / pixelRatio, y: 0 }); // north east return [ [leftTop.lng, leftTop.lat], [rightTop.lng, rightTop.lat], From 016035d78050d761646ccda171a683318c1ada61 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 1 Oct 2021 16:11:07 +0200 Subject: [PATCH 024/109] Allow rotation for tracke rwith ol --- src/common/Tracker.js | 14 +--------- src/doc/examples/mb-tracker.js | 2 -- src/ol/layers/TrackerLayer.js | 47 +++++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 1519bb59..2347cfcd 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -144,19 +144,6 @@ export default class Tracker { } } - /** - * Move the canvas. - * @param {number} offsetX Offset X. - * @param {number} offsetY Offset Y. - * @private - */ - moveCanvas(offsetX, offsetY) { - const oldLeft = parseFloat(this.canvas.style.left); - const oldTop = parseFloat(this.canvas.style.top); - this.canvas.style.left = `${oldLeft - offsetX}px`; - this.canvas.style.top = `${oldTop - offsetY}px`; - } - /** * Draw all the trajectories available to the canvas. * @param {Date} currTime The date to render. @@ -189,6 +176,7 @@ export default class Tracker { this.canvas.style.left = '0px'; this.canvas.style.top = '0px'; + this.canvas.style.transform = ``; this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; this.canvas.style.height = `${this.canvas.height / this.pixelRatio}px`; /** diff --git a/src/doc/examples/mb-tracker.js b/src/doc/examples/mb-tracker.js index 6798d79f..10852312 100644 --- a/src/doc/examples/mb-tracker.js +++ b/src/doc/examples/mb-tracker.js @@ -10,8 +10,6 @@ export default () => { zoom: 12, touchPitch: false, pitchWithRotate: false, - dragRotate: false, - touchZoomRotate: false, }); const tracker = new TrajservLayer({ diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 6aeb8442..bd710c84 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -27,11 +27,18 @@ class TrackerLayer extends mixin(Layer) { /** * Function to define when allowing the render of trajectories depending on the zoom level. Default the fundtion return true. - * It's useful to avoid rendering the map when the map is animating or interacting + * It's useful to avoid rendering the map when the map is animating or interacting. * @type {function} */ this.renderWhenZooming = options.renderWhenZooming || (() => true); + /** + * Function to define when allowing the render of trajectories depending on the rotation. Default the fundtion return true. + * It's useful to avoid rendering the map when the map is animating or interacting. + * @type {function} + */ + this.renderWhenRotating = options.renderWhenRotating || (() => true); + this.olLayer = options.olLayer || new Group({ @@ -42,13 +49,27 @@ class TrackerLayer extends mixin(Layer) { if (!this.tracker || !this.tracker.canvas) { return null; } - const { zoom, center, resolution } = frameState.viewState; - - if (zoom !== this.renderState.zoom) { + const { + zoom, + center, + resolution, + rotation, + } = frameState.viewState; + + if ( + zoom !== this.renderState.zoom || + rotation !== this.renderState.rotation + ) { this.renderState.zoom = zoom; this.renderState.center = center; - - if (!this.renderWhenZooming(zoom)) { + this.renderState.rotation = rotation; + + if ( + (zoom !== this.renderState.zoom && + !this.renderWhenZooming(zoom)) || + (rotation !== this.renderState.rotation && + !this.renderWhenRotating(rotation)) + ) { this.tracker.clear(); return this.tracker.canvas; } @@ -62,8 +83,19 @@ class TrackerLayer extends mixin(Layer) { const oldPx = this.map.getPixelFromCoordinate( this.renderState.center, ); - this.tracker.moveCanvas(px[0] - oldPx[0], px[1] - oldPx[1]); + + // We move the canvas to avoid re render the trajectories + const oldLeft = parseFloat(this.tracker.canvas.style.left); + const oldTop = parseFloat(this.tracker.canvas.style.top); + this.tracker.canvas.style.left = `${ + oldLeft - (px[0] - oldPx[0]) + }px`; + this.tracker.canvas.style.top = `${ + oldTop - (px[1] - oldPx[1]) + }px`; + this.renderState.center = center; + this.renderState.rotation = rotation; } return this.tracker.canvas; @@ -78,6 +110,7 @@ class TrackerLayer extends mixin(Layer) { this.renderState = { center: [0, 0], zoom: null, + rotation: 0, }; /** From d1e70be3a29f689ead32c05e4950e697862a6975 Mon Sep 17 00:00:00 2001 From: oterral Date: Mon, 4 Oct 2021 10:54:32 +0200 Subject: [PATCH 025/109] Update src/common/Tracker.js --- src/common/Tracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 2347cfcd..9df3c334 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -183,7 +183,7 @@ export default class Tracker { * Current resolution. * @type {number} */ - (this.currResolution = resolution || this.currResolution); + this.currResolution = resolution || this.currResolution; let hoverVehicleImg; let hoverVehiclePx; let hoverVehicleWidth; From b242bd04465b8d73a30ab470dfa10a34ac765142 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 4 Oct 2021 13:29:59 +0200 Subject: [PATCH 026/109] Clean TrackerLayer --- __mocks__/mapbox-gl.js | 2 + src/common/Tracker.js | 2 + src/common/mixins/TrackerLayerMixin.js | 124 ++++++++++++------------ src/common/mixins/TrajservLayerMixin.js | 4 +- src/mapbox/layers/TrackerLayer.js | 44 +++++---- src/mapbox/layers/TrajservLayer.js | 6 +- src/mapbox/layers/TralisLayer.js | 1 + src/ol/layers/TrackerLayer.js | 52 +++++----- 8 files changed, 121 insertions(+), 114 deletions(-) diff --git a/__mocks__/mapbox-gl.js b/__mocks__/mapbox-gl.js index 80f820f3..4644903a 100644 --- a/__mocks__/mapbox-gl.js +++ b/__mocks__/mapbox-gl.js @@ -32,6 +32,8 @@ class Map { getZoom() {} + getBearing() {} + once() {} on() {} diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 2347cfcd..9fc09711 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -284,9 +284,11 @@ export default class Tracker { // eslint-disable-next-line no-continue continue; } + px = px.map((p) => { return p * this.pixelRatio; }); + // Trajectory with pixel (i.e. within map extent) will be in renderedTrajectories. this.trajectories[i].rendered = true; this.renderedTrajectories.push(this.trajectories[i]); diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 757371f7..cb6f4000 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -35,16 +35,11 @@ export class TrackerLayerInterface { /** * Start the clock. - * - * @param {Array} size Map's size: [width, height]. - * @param {number} zoom Map's zoom level. - * @param {number} resolution Map's resolution. */ - // eslint-disable-next-line no-unused-vars - start(size, zoom, resolution) {} + start() {} /** - * Stop the time. + * Start the timeout for the next update. * @private * @param {number} zoom */ @@ -56,17 +51,6 @@ export class TrackerLayerInterface { */ stop() {} - /** - * Set the current time, it triggers a rendering of the trajectories. - * - * @param {Date} time The date to render. - * @param {number[2]} size Size of the canvas to render. - * @param {number} resolution Map's resolution to render. - * @param {boolean} [mustRender=true] If false bypass the rendering of vehicles. - */ - // eslint-disable-next-line no-unused-vars - setCurrTime(time, size, resolution, mustRender = true) {} - /** * Get vehicle. * @param {function} filterFc A function use to filter results. @@ -134,7 +118,9 @@ const TrackerLayerMixin = (Base) => selectedVehicleId, filter, sort, + time, } = options; + const initTrackerOptions = { pixelRatio: pixelRatio || window.devicePixelRatio || 1, interpolate, @@ -144,13 +130,15 @@ const TrackerLayerMixin = (Base) => sort, style, }; + Object.keys(initTrackerOptions).forEach( (key) => initTrackerOptions[key] === undefined && delete initTrackerOptions[key], ); - let cuurSpeed = speed || 1; + let currSpeed = speed || 1; + let currTime = time || new Date(); super.defineProperties(options); @@ -177,9 +165,9 @@ const TrackerLayerMixin = (Base) => * If live property is true. The speed is ignored. */ speed: { - get: () => cuurSpeed, + get: () => currSpeed, set: (newSpeed) => { - cuurSpeed = newSpeed; + currSpeed = newSpeed; this.start(); }, }, @@ -226,7 +214,7 @@ const TrackerLayerMixin = (Base) => /** * If true. The layer will always use Date.now() on the next tick to render the trajectories. - * When true, setCurrTime will have no effect. + * When true, setting the time property has no effect. */ live: { value: true, @@ -234,12 +222,15 @@ const TrackerLayerMixin = (Base) => }, /** - * Time used to display the trajectories. - * If live property is true. This function does nothing execpt rerender the trajectories using Date.now(). + * Time used to display the trajectories. Can be a Date or a number in ms representing a Date. + * If live property is true. The setter does nothing. */ - currTime: { - value: new Date(), - writable: true, + time: { + get: () => currTime, + set: (newTime) => { + currTime = newTime.getTime ? newTime : new Date(newTime); + this.renderTrajectories(); + }, }, /** @@ -378,36 +369,34 @@ const TrackerLayerMixin = (Base) => } /** - * Start the clock. + * Start the trajectories rendering. * * @param {Array} size Map's size: [width, height]. * @param {number} zoom Map's zoom level. * @param {number} resolution Map's resolution. + * @param {number} rotation Map's rotation. */ - start(size, zoom, resolution) { + start() { this.stop(); this.tracker.setVisible(true); - this.renderTrajectories(size, resolution); - this.startUpdateTime(zoom); + this.renderTrajectories(); + this.startUpdateTime(); } /** - * Start the time. + * Start the clock. * @private - * @param {number} zoom */ - startUpdateTime(zoom) { + startUpdateTime() { this.stopUpdateTime(); + this.updateTimeDelay = this.getRefreshTimeInMs(); this.updateTimeInterval = setInterval(() => { - const newTime = - this.currTime.getTime() + - (new Date() - this.lastUpdateTime) * this.speed; - this.setCurrTime(newTime); - }, this.getRefreshTimeInMs(zoom)); + this.time = this.time.getTime() + (new Date() - this.time) * this.speed; + }, this.updateTimeDelay); } /** - * Stop the clock. + * Stop the trajectories rendering. */ stop() { this.stopUpdateTime(); @@ -418,7 +407,7 @@ const TrackerLayerMixin = (Base) => } /** - * Stop the time. + * Stop the clock. * @private */ stopUpdateTime() { @@ -431,12 +420,26 @@ const TrackerLayerMixin = (Base) => * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. * @private */ - renderTrajectoriesInternal(size, resolution, noInterpolate) { + renderTrajectoriesInternal(size, resolution, rotation, noInterpolate) { if (!this.tracker) { return; } - const renderTime = this.live ? Date.now() : this.currTime; + const renderTime = this.live ? Date.now() : this.time; + + // Avoid useless render before the next tick. + if ( + this.live && + resolution === this.lastRenderResolution && + rotation === this.lastRenderRotation && + renderTime - this.lastRenderTime < this.updateTimeDelay + ) { + return; + } + + this.lastRenderTime = renderTime; + this.lastRenderResolution = resolution; + this.tracker.renderTrajectories( renderTime, size, @@ -446,36 +449,31 @@ const TrackerLayerMixin = (Base) => } /** - * Render the trajectories requesting an animation frame and cancelling the previous one + * Render the trajectories requesting an animation frame and cancelling the previous one. + * This function must be overrided by children to provide the correct parameters. * @private */ - renderTrajectories(size, resolution, noInterpolate) { + renderTrajectories(size, resolution, rotation, noInterpolate) { if (this.requestId) { cancelAnimationFrame(this.requestId); } if (this.useRequestAnimationFrame) { this.requestId = requestAnimationFrame(() => { - this.renderTrajectoriesInternal(size, resolution, noInterpolate); + this.renderTrajectoriesInternal( + size, + resolution, + rotation, + noInterpolate, + ); }); } else { - this.renderTrajectoriesInternal(size, resolution, noInterpolate); - } - } - - /** - * Set the current time, it triggers a rendering of the trajectories. - * If live is true. This function will have no effect. - * @param {dateString | value} time - * @param {Array} size - * @param {number} resolution - * @param {boolean} [mustRender=true] - */ - setCurrTime(time, size, resolution, mustRender = true) { - this.currTime = new Date(time); - this.lastUpdateTime = this.currTime; - if (mustRender) { - this.renderTrajectories(size, resolution); + this.renderTrajectoriesInternal( + size, + resolution, + rotation, + noInterpolate, + ); } } diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index f0e33d89..522fcdb5 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -337,7 +337,7 @@ const TrajservLayerMixin = (TrackerLayer) => // The 5 seconds more are used as a buffer if the request takes too long. const requestIntervalInMs = (this.requestIntervalSeconds + 5) * 1000; const intervalMs = this.speed * requestIntervalInMs; - const now = this.currTime; + const now = this.time; let diff = true; @@ -367,7 +367,7 @@ const TrajservLayerMixin = (TrackerLayer) => cd: 1, nm: 1, fl: 1, - // toff: this.currTime.getTime() / 1000, + // toff: this.time.getTime() / 1000, }; // Allow to load only differences between the last request, diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index b404c480..2e6d5b29 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -45,21 +45,6 @@ class TrackerLayer extends mixin(Layer) { }); } - /** - * Set the current time, it triggers a rendering of the trajectories. - * - * @param {Date} time The current time. - */ - setCurrTime(time) { - const canvas = this.map.getCanvas(); - super.setCurrTime( - time, - [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], - getResolution(this.map), - !this.map.isMoving() && !this.map.isRotating() && !this.map.isZooming(), - ); - } - /** * Start updating vehicles position. * @@ -68,12 +53,7 @@ class TrackerLayer extends mixin(Layer) { * @override */ start() { - const canvas = this.map.getCanvas(); - super.start( - [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], - this.map.getZoom(), - getResolution(this.map), - ); + super.start(); this.map.on('zoomend', this.onMapZoomEnd); @@ -95,6 +75,28 @@ class TrackerLayer extends mixin(Layer) { } } + /** + * Render the trajectories using current map's size, resolution and rotation. + * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions. + * @overrides + */ + renderTrajectories(noInterpolate) { + const canvas = this.map.getCanvas(); + super.renderTrajectories( + [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], + getResolution(this.map), + this.map.getBearing(), + noInterpolate, + ); + } + + /** + * Return the delay in ms before the next rendering. + */ + getRefreshTimeInMs() { + return super.getRefreshTimeInMs(this.map.getZoom()); + } + /** * Returns an array of vehicles located at the given coordinate. * diff --git a/src/mapbox/layers/TrajservLayer.js b/src/mapbox/layers/TrajservLayer.js index e9e02be8..1ca39071 100644 --- a/src/mapbox/layers/TrajservLayer.js +++ b/src/mapbox/layers/TrajservLayer.js @@ -131,7 +131,11 @@ class TrajservLayer extends mixin(TrackerLayer) { this.map .getSource(this.key) .setCoordinates(getSourceCoordinates(this.map, this.pixelRatio)); - this.renderTrajectories(undefined, getResolution(this.map)); + this.renderTrajectories( + undefined, + getResolution(this.map), + this.map.getBearing(), + ); } /** diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 1154b8a7..7a2b3300 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -93,6 +93,7 @@ class TralisLayer extends mixin(TrackerLayer) { this.renderTrajectories( [width / this.pixelRatio, height / this.pixelRatio], getResolution(this.map), + this.map.getBearing(), ); } diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index bd710c84..f1165a29 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -49,12 +49,7 @@ class TrackerLayer extends mixin(Layer) { if (!this.tracker || !this.tracker.canvas) { return null; } - const { - zoom, - center, - resolution, - rotation, - } = frameState.viewState; + const { zoom, center, rotation } = frameState.viewState; if ( zoom !== this.renderState.zoom || @@ -74,7 +69,7 @@ class TrackerLayer extends mixin(Layer) { return this.tracker.canvas; } - this.renderTrajectories(frameState.size, resolution, true); + this.renderTrajectories(true); } else if ( this.renderState.center[0] !== center[0] || this.renderState.center[1] !== center[1] @@ -136,31 +131,12 @@ class TrackerLayer extends mixin(Layer) { }); } - /** - * Set the current time, it triggers a rendering of the trajectories. - * @param {dateString | value} time - */ - setCurrTime(time) { - const view = this.map.getView(); - super.setCurrTime( - time, - this.map.getSize(), - view.getResolution(), - !this.map.getView().getAnimating() && - !this.map.getView().getInteracting(), - ); - } - /** * Trackerlayer is started. * @private */ start() { - super.start( - this.map.getSize(), - this.currentZoom || this.map.getView().getZoom(), - this.map.getView().getResolution(), - ); + super.start(); this.olEventsKeys = [ this.map.on('moveend', () => { @@ -206,6 +182,28 @@ class TrackerLayer extends mixin(Layer) { this.olEventsKeys = []; } + /** + * Render the trajectories using current map's size, resolution and rotation. + * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions. + * @overrides + */ + renderTrajectories(noInterpolate) { + const view = this.map.getView(); + super.renderTrajectories( + this.map.getSize(), + view.getResolution(), + view.getRotation(), + noInterpolate, + ); + } + + /** + * Return the delay in ms before the next rendering. + */ + getRefreshTimeInMs() { + return super.getRefreshTimeInMs(this.map.getView().getZoom()); + } + /** * Returns the vehicle which are at the given coordinates. * Returns null when no vehicle is located at the given coordinates. From 9dd0c129ad03fd6a6462a43f162428796c12ca71 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 4 Oct 2021 13:30:55 +0200 Subject: [PATCH 027/109] v1.3.13-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29dd5e40..08814129 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.5", + "version": "1.3.13-beta.6", "main": "index.js", "module": "module.js", "dependencies": { From b18e2569fcf0d30c78da391eda7199521e1761a3 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 4 Oct 2021 13:32:47 +0200 Subject: [PATCH 028/109] Clean TrackerLayer --- src/common/mixins/TrackerLayerMixin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index cb6f4000..9cbe15fa 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -439,6 +439,7 @@ const TrackerLayerMixin = (Base) => this.lastRenderTime = renderTime; this.lastRenderResolution = resolution; + this.lastRenderRotation = rotation; this.tracker.renderTrajectories( renderTime, From d517ecf00a453f277ec9c52d98cf0275cbbc12c3 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 4 Oct 2021 13:33:13 +0200 Subject: [PATCH 029/109] v1.3.13-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08814129..be453b30 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.3.13-beta.6", + "version": "1.3.13-beta.7", "main": "index.js", "module": "module.js", "dependencies": { From d30077af5bf5ee1b18062f7c6b41eb6ceb0cd1e8 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 6 Oct 2021 12:21:21 +0200 Subject: [PATCH 030/109] Use trnasorm --- src/common/mixins/TrackerLayerMixin.js | 6 +- src/doc/App.js | 2 - src/doc/components/Example.js | 2 +- src/ol/layers/TrackerLayer.js | 183 +++++++++++++++++++------ 4 files changed, 145 insertions(+), 48 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 9cbe15fa..1144dde4 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -422,7 +422,7 @@ const TrackerLayerMixin = (Base) => */ renderTrajectoriesInternal(size, resolution, rotation, noInterpolate) { if (!this.tracker) { - return; + return false; } const renderTime = this.live ? Date.now() : this.time; @@ -434,7 +434,7 @@ const TrackerLayerMixin = (Base) => rotation === this.lastRenderRotation && renderTime - this.lastRenderTime < this.updateTimeDelay ) { - return; + return false; } this.lastRenderTime = renderTime; @@ -447,6 +447,7 @@ const TrackerLayerMixin = (Base) => resolution, noInterpolate, ); + return true; } /** @@ -458,6 +459,7 @@ const TrackerLayerMixin = (Base) => if (this.requestId) { cancelAnimationFrame(this.requestId); } + console.log('render');:q if (this.useRequestAnimationFrame) { this.requestId = requestAnimationFrame(() => { diff --git a/src/doc/App.js b/src/doc/App.js index 08d7bb5d..1b859287 100644 --- a/src/doc/App.js +++ b/src/doc/App.js @@ -16,8 +16,6 @@ const useStyles = makeStyles({ flexGrow: 1, overflowY: 'auto', paddingBottom: 115, - minHeight: 'calc(100vh - 528px)', - maxWidth: 'calc(85vw + 48px)', margin: 'auto', marginTop: 30, }, diff --git a/src/doc/components/Example.js b/src/doc/components/Example.js index 48ffc256..a7145e7a 100644 --- a/src/doc/components/Example.js +++ b/src/doc/components/Example.js @@ -89,7 +89,7 @@ const Example = () => { }, [example, htmlFileName, jsFileName, readmeFileName]); return ( -
+
diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index f1165a29..c51cf5fc 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -1,6 +1,8 @@ import { Layer as OLLayer, Group } from 'ol/layer'; import { unByKey } from 'ol/Observable'; import Source from 'ol/source/Source'; +import { composeCssTransform } from 'ol/transform'; + import mixin from '../../common/mixins/TrackerLayerMixin'; import Layer from './Layer'; @@ -26,15 +28,46 @@ class TrackerLayer extends mixin(Layer) { }); /** - * Function to define when allowing the render of trajectories depending on the zoom level. Default the fundtion return true. + * Boolean that defines if the layer is allow to renderTrajectories when this.map.getView().getInterating() id true. * It's useful to avoid rendering the map when the map is animating or interacting. * @type {function} */ - this.renderWhenZooming = options.renderWhenZooming || (() => true); + this.renderWhenInteracting = options.renderWhenInteracting || (() => true); /** - * Function to define when allowing the render of trajectories depending on the rotation. Default the fundtion return true. - * It's useful to avoid rendering the map when the map is animating or interacting. + * Function to define if we want to allow the render of trajectories when the map is animating. By default the function return true. + * If it returns false, the canva swill be scaled + * @type {function} + */ + this.renderWhenAnimating = options.renderWhenAnimating || (() => true); + + /** + * Function to define if we want to allow the render of trajectories when the map is zooming. By default the function return true. + * If it returns false, the canvas will be scaled using css until the next render. + * @type {function} + */ + this.renderWhenZooming = + options.renderWhenZooming || + ((viewState, renderedState) => { + if (viewState.resolution <= renderedState.resolution) { + this.tracker.clear(); + console.log('tufalsere'); + return false; + } + console.log('ture'); + return !this.map.getView().getInteracting(); + }); + + /** + * Function to define if we want to allow the render of trajectories when the map is panning. By default the function return true. + * If it returns false, the canvas will be moved using css until the next render. + * @type {function} + */ + this.renderWhenPanning = options.renderWhenPanning || (() => true); + + /** + * Function to define if we want to allow the render of trajectories when the map is rotating. By default the function return true. + * If it returns false, the canvas will be rotated using css until the next render. * @type {function} */ this.renderWhenRotating = options.renderWhenRotating || (() => true); @@ -49,51 +82,82 @@ class TrackerLayer extends mixin(Layer) { if (!this.tracker || !this.tracker.canvas) { return null; } - const { zoom, center, rotation } = frameState.viewState; - if ( - zoom !== this.renderState.zoom || - rotation !== this.renderState.rotation - ) { - this.renderState.zoom = zoom; - this.renderState.center = center; - this.renderState.rotation = rotation; + if (!this.container) { + this.container = document.createElement('div'); + this.container.style.position = 'absolute'; + this.container.style.width = '100%'; + this.container.style.height = '100%'; + this.transformContainer = document.createElement('div'); + this.transformContainer.style.position = 'absolute'; + this.transformContainer.style.width = '100%'; + this.transformContainer.style.height = '100%'; + this.container.appendChild(this.transformContainer); + this.tracker.canvas.style.position = 'absolute'; + this.tracker.canvas.style.left = '0'; + this.tracker.canvas.style.transformOrigin = 'top left'; + this.transformContainer.appendChild(this.tracker.canvas); + } + + if (this.renderedViewState) { + // Update container tranform + const { center, resolution, rotation } = frameState.viewState; + const { + center: renderedCenter, + resolution: renderedResolution, + rotation: renderedRotation, + } = this.renderedViewState; if ( - (zoom !== this.renderState.zoom && - !this.renderWhenZooming(zoom)) || - (rotation !== this.renderState.rotation && - !this.renderWhenRotating(rotation)) + !this.map.getView().getInteracting() && + !this.map.getView().getAnimating() ) { - this.tracker.clear(); - return this.tracker.canvas; + if ( + resolution !== renderedResolution && + this.renderWhenZooming + ) { + const allowRender = this.renderWhenZooming( + frameState.viewState, + this.renderedViewState, + ); + if (allowRender) { + console.log('ici'); + this.renderTrajectories(true); + } + } else if ( + (center[0] !== renderedCenter[0] || + center[1] !== renderedCenter[1]) && + this.renderWhenPanning( + frameState.viewState, + this.renderedViewState, + ) + ) { + console.log('ici2'); + this.renderTrajectories(true); + } else if ( + rotation !== renderedRotation && + this.renderWhenRotating( + frameState.viewState, + this.renderedViewState, + ) + ) { + console.log('ici3'); + this.renderTrajectories(true); + } + } else { + console.log('ici4'); + this.transformContainer.style.transform = composeCssTransform( + (renderedCenter[0] - center[0]) / resolution, + (center[1] - renderedCenter[1]) / resolution, + renderedResolution / resolution, + renderedResolution / resolution, + rotation - renderedRotation, + 0, + 0, + ); } - - this.renderTrajectories(true); - } else if ( - this.renderState.center[0] !== center[0] || - this.renderState.center[1] !== center[1] - ) { - const px = this.map.getPixelFromCoordinate(center); - const oldPx = this.map.getPixelFromCoordinate( - this.renderState.center, - ); - - // We move the canvas to avoid re render the trajectories - const oldLeft = parseFloat(this.tracker.canvas.style.left); - const oldTop = parseFloat(this.tracker.canvas.style.top); - this.tracker.canvas.style.left = `${ - oldLeft - (px[0] - oldPx[0]) - }px`; - this.tracker.canvas.style.top = `${ - oldTop - (px[1] - oldPx[1]) - }px`; - - this.renderState.center = center; - this.renderState.rotation = rotation; } - - return this.tracker.canvas; + return this.container; }, }), ], @@ -189,6 +253,13 @@ class TrackerLayer extends mixin(Layer) { */ renderTrajectories(noInterpolate) { const view = this.map.getView(); + + if ( + this.map.getView().getAnimating() || + this.map.getView().getInteracting() + ) { + return; + } super.renderTrajectories( this.map.getSize(), view.getResolution(), @@ -197,6 +268,32 @@ class TrackerLayer extends mixin(Layer) { ); } + /** + * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. + * @private + */ + renderTrajectoriesInternal(size, resolution, rotation, noInterpolate) { + const isRendered = super.renderTrajectoriesInternal( + size, + resolution, + rotation, + noInterpolate, + ); + + // We update the current render state. + if (isRendered) { + if (this.transformContainer) { + this.transformContainer.style.transform = null; + } + this.renderedViewState = { + size, + center: this.map.getView().getCenter(), + resolution, + rotation, + }; + } + } + /** * Return the delay in ms before the next rendering. */ From 195f0b05749a0d8c37f853b37513af15aaa2a24b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 13 Oct 2021 12:07:32 +0200 Subject: [PATCH 031/109] Use transform --- __mocks__/mapbox-gl.js | 4 + src/common/Tracker.js | 40 ++++--- src/common/mixins/TrackerLayerMixin.js | 32 ++++-- src/common/mixins/TrajservLayerMixin.js | 1 + src/mapbox/layers/TrackerLayer.js | 109 ++++++++++++++++--- src/mapbox/layers/TrajservLayer.js | 72 ------------- src/mapbox/layers/TralisLayer.js | 48 +-------- src/mapbox/utils.js | 3 +- src/ol/layers/TrackerLayer.js | 138 ++++++------------------ src/ol/layers/TrajservLayer.js | 12 ++- 10 files changed, 195 insertions(+), 264 deletions(-) diff --git a/__mocks__/mapbox-gl.js b/__mocks__/mapbox-gl.js index 4644903a..474a567e 100644 --- a/__mocks__/mapbox-gl.js +++ b/__mocks__/mapbox-gl.js @@ -30,6 +30,10 @@ class Map { }; } + getCenter() { + return { lng: 0, lat: 0 }; + } + getZoom() {} getBearing() {} diff --git a/src/common/Tracker.js b/src/common/Tracker.js index df6fca6a..8c8a4e73 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -1,5 +1,7 @@ import { unByKey } from 'ol/Observable'; import GeomType from 'ol/geom/GeometryType'; +import { compose, apply, create } from 'ol/transform'; +import { toLonLat } from 'ol/proj'; /** * Tracker. This class stores and allows to draw trajectories on a canvas. @@ -40,12 +42,6 @@ export default class Tracker { */ this.interpolate = !!opts.interpolate; - /** - * Function to Convert coordinate to canvas pixel. - * @type {function} - */ - this.getPixelFromCoordinate = opts.getPixelFromCoordinate; - /** * Id of the trajectory which is hovered. * @type {string} @@ -157,7 +153,10 @@ export default class Tracker { renderTrajectories( currTime = Date.now(), size = [], + center, + extent, resolution, + rotation = 0, noInterpolate = false, ) { this.clear(); @@ -174,11 +173,23 @@ export default class Tracker { ]; } + const coordinateToPixelTransform = compose( + create(), + size[0] / 2, + size[1] / 2, + 1 / resolution, + -1 / resolution, + -rotation, + -center[0], + -center[1], + ); + this.canvas.style.left = '0px'; this.canvas.style.top = '0px'; this.canvas.style.transform = ``; this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; this.canvas.style.height = `${this.canvas.height / this.pixelRatio}px`; + /** * Current resolution. * @type {number} @@ -207,7 +218,7 @@ export default class Tracker { } let coord = null; - let rotation; + let rotationIcon; if (traj.coordinate && (noInterpolate || !this.interpolate)) { coord = traj.coordinate; @@ -222,7 +233,7 @@ export default class Tracker { // Search th time interval. for (let j = 0; j < timeIntervals.length - 1; j += 1) { // Rotation only available in tralis layer. - [start, startFrac, rotation] = timeIntervals[j]; + [start, startFrac, rotationIcon] = timeIntervals[j]; [end, endFrac] = timeIntervals[j + 1]; if (start <= now && now <= end) { @@ -249,16 +260,16 @@ export default class Tracker { coord = geometry.getCoordinateAt(geomFrac); // We set the rotation and the timeFraction of the trajectory (used by tralis). - this.trajectories[i].rotation = rotation; + this.trajectories[i].rotation = rotationIcon; this.trajectories[i].endFraction = timeFrac; // It happens that the now date was some ms before the first timeIntervals we have. } else if (now < timeIntervals[0][0]) { - [[, , rotation]] = timeIntervals; + [[, , rotationIcon]] = timeIntervals; timeFrac = 0; coord = geometry.getFirstCoordinate(); } else if (now > timeIntervals[timeIntervals.length - 1][0]) { - [, , rotation] = timeIntervals[timeIntervals.length - 1]; + [, , rotationIcon] = timeIntervals[timeIntervals.length - 1]; timeFrac = 1; coord = geometry.getLastCoordinate(); } @@ -271,15 +282,16 @@ export default class Tracker { } // We set the rotation and the timeFraction of the trajectory (used by tralis). // if rotation === null that seems there is no rotation available. - this.trajectories[i].rotation = rotation; + this.trajectories[i].rotation = rotationIcon; this.trajectories[i].endFraction = timeFrac || 0; } if (coord) { // We set the rotation of the trajectory (used by tralis). this.trajectories[i].coordinate = coord; - let px = this.getPixelFromCoordinate(coord); - + // console.log([...toLonLat(coord)]); + let px = apply(coordinateToPixelTransform, [...toLonLat(coord)]); + // console.log(px); if (!px) { // eslint-disable-next-line no-continue continue; diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index c274ab31..cb4b7df3 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -23,7 +23,6 @@ export class TrackerLayerInterface { * @param {Object} options * @param {number} [options.width] Canvas's width. * @param {number} [options.height] Canvas's height. - * @param {function} [options.getPixelFromCoordinate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). */ // eslint-disable-next-line no-unused-vars init(map, options) {} @@ -320,7 +319,6 @@ const TrackerLayerMixin = (Base) => * @param {bool} [options.interpolate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). * @param {string} [options.hoverVehicleId] Id of the trajectory which is hovered. * @param {string} [options.selectedVehicleId] Id of the trajectory which is selected. - * @param {function} [options.getPixelFromCoordinate] Convert an EPSG:3857 coordinate to a canvas pixel (origin top-left). * @param {function} [options.filter] Function use to filter the features displayed. * @param {function} [options.sort] Function use to sort the features displayed. * @param {function} [options.style] Function use to style the features displayed. @@ -415,9 +413,16 @@ const TrackerLayerMixin = (Base) => * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. * @private */ - renderTrajectoriesInternal(size, resolution, rotation, noInterpolate) { + renderTrajectoriesInternal( + size, + center, + extent, + resolution, + rotation, + noInterpolate, + ) { if (!this.tracker) { - return; + return false; } const renderTime = this.live ? Date.now() : this.time; @@ -429,7 +434,7 @@ const TrackerLayerMixin = (Base) => rotation === this.lastRenderRotation && renderTime - this.lastRenderTime < this.updateTimeDelay ) { - return; + return false; } this.lastRenderTime = renderTime; @@ -439,9 +444,13 @@ const TrackerLayerMixin = (Base) => this.tracker.renderTrajectories( renderTime, size, + center, + extent, resolution, + rotation, noInterpolate, ); + return true; } /** @@ -449,7 +458,14 @@ const TrackerLayerMixin = (Base) => * This function must be overrided by children to provide the correct parameters. * @private */ - renderTrajectories(size, resolution, rotation, noInterpolate) { + renderTrajectories( + size, + center, + extent, + resolution, + rotation, + noInterpolate, + ) { if (this.requestId) { cancelAnimationFrame(this.requestId); } @@ -458,6 +474,8 @@ const TrackerLayerMixin = (Base) => this.requestId = requestAnimationFrame(() => { this.renderTrajectoriesInternal( size, + center, + extent, resolution, rotation, noInterpolate, @@ -466,6 +484,8 @@ const TrackerLayerMixin = (Base) => } else { this.renderTrajectoriesInternal( size, + center, + extent, resolution, rotation, noInterpolate, diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index 522fcdb5..bd6a25f4 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -395,6 +395,7 @@ const TrajservLayerMixin = (TrackerLayer) => updateTrajectories() { this.abortFetchTrajectories(); this.abortController = new AbortController(); + this.api .fetchTrajectories( this.getParams({ diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index 2e6d5b29..b93b3767 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -1,7 +1,8 @@ -import { toLonLat, fromLonLat } from 'ol/proj'; +import { fromLonLat } from 'ol/proj'; +import { unByKey } from 'ol/Observable'; import Layer from '../../common/layers/Layer'; import mixin from '../../common/mixins/TrackerLayerMixin'; -import { getResolution } from '../utils'; +import { getSourceCoordinates, getResolution } from '../utils'; /** * Responsible for loading tracker data. @@ -16,9 +17,12 @@ class TrackerLayer extends mixin(Layer) { }); /** @ignores */ - this.onMapZoomEnd = this.onMapZoomEnd.bind(this); + this.onMove = this.onMove.bind(this); + this.onZoomEnd = this.onZoomEnd.bind(this); + this.onVisibilityChange = this.onVisibilityChange.bind(this); + /** @ignores */ - this.onMapMouseMove = this.onMapMouseMove.bind(this); + this.onMouseMove = this.onMouseMove.bind(this); } /** @@ -27,7 +31,7 @@ class TrackerLayer extends mixin(Layer) { * @param {mapboxgl.Map} map A [mapbox Map](https://docs.mapbox.com/mapbox-gl-js/api/map/). * @override */ - init(map) { + init(map, beforeId) { if (!map) { return; } @@ -37,12 +41,49 @@ class TrackerLayer extends mixin(Layer) { super.init(map, { width: canvas.width / this.pixelRatio, height: canvas.height / this.pixelRatio, - getPixelFromCoordinate: (coord) => { - const [lng, lat] = toLonLat(coord); - const { x, y } = this.map.project({ lng, lat }); - return [x, y]; - }, }); + + const source = { + type: 'canvas', + canvas: this.tracker.canvas, + coordinates: getSourceCoordinates(map, this.pixelRatio), + // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance. + animate: true, + attribution: this.copyrights, + }; + + this.beforeId = beforeId; + this.layer = { + id: this.key, + type: 'raster', + source: this.key, + layout: { + visibility: this.visible ? 'visible' : 'none', + }, + paint: { + 'raster-opacity': 1, + 'raster-fade-duration': 0, + 'raster-resampling': 'nearest', // important otherwise it looks blurry + }, + }; + map.addSource(this.key, source); + map.addLayer(this.layer, this.beforeId); + + this.listeners = [this.on('change:visible', this.onVisibilityChange)]; + } + + /** + * Remove listeners from the Mapbox Map. + */ + terminate() { + if (this.map) { + this.listeners.forEach((listener) => { + unByKey(listener); + }); + this.map.removeLayer(this.key); + this.map.removeSource(this.key); + } + super.terminate(); } /** @@ -55,10 +96,11 @@ class TrackerLayer extends mixin(Layer) { start() { super.start(); - this.map.on('zoomend', this.onMapZoomEnd); + this.map.on('move', this.onMove); + this.map.on('zoomend', this.onZoomEnd); if (this.isHoverActive) { - this.map.on('mousemove', this.onMapMouseMove); + this.map.on('mousemove', this.onMouseMove); } } @@ -70,8 +112,9 @@ class TrackerLayer extends mixin(Layer) { stop() { super.stop(); if (this.map) { - this.map.off('zoomend', this.onMapZoomEnd); - this.map.off('mousemove', this.onMapMouseMove); + this.map.off('move', this.onMove); + this.map.off('zoomend', this.onZoomEnd); + this.map.off('mousemove', this.onMouseMove); } } @@ -82,10 +125,14 @@ class TrackerLayer extends mixin(Layer) { */ renderTrajectories(noInterpolate) { const canvas = this.map.getCanvas(); + const center = this.map.getCenter(); + const extent = getSourceCoordinates(this.map, this.pixelRatio); super.renderTrajectories( [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], + [center.lng, center.lat], + [extent[0][0], extent[1][1], extent[1][0], extent[0][1]], getResolution(this.map), - this.map.getBearing(), + -(this.map.getBearing() * Math.PI) / 180, noInterpolate, ); } @@ -110,12 +157,40 @@ class TrackerLayer extends mixin(Layer) { return super.getVehiclesAtCoordinate(coordinate, resolution, nb); } + onVisibilityChange() { + if (this.visible && !this.map.getLayer(this.key)) { + this.map.addLayer(this.layer, this.beforeId); + } else if (this.map.getLayer(this.key)) { + this.map.removeLayer(this.key); + } + // We can't use setLayoutProperty it triggers an error probably a bug in mapbox + // this.map.setLayoutProperty( + // this.key, + // 'visibilty', + // this.visible ? 'visible' : 'none', + // ); + } + + /** + * Callback on 'move' event. + * + * @private + */ + onMove() { + const extent = getSourceCoordinates(this.map, this.pixelRatio); + const source = this.map.getSource(this.key); + if (source) { + source.setCoordinates(extent); + } + this.renderTrajectories(); + } + /** * On zoomend we adjust the time interval of the update of vehicles positions. * * @private */ - onMapZoomEnd() { + onZoomEnd() { this.startUpdateTime(this.map.getZoom()); } @@ -125,7 +200,7 @@ class TrackerLayer extends mixin(Layer) { * @param {mapboxgl.MapMouseEvent} evt Map's mousemove event. * @private */ - onMapMouseMove(evt) { + onMouseMove(evt) { if ( this.map.isMoving() || this.map.isRotating() || diff --git a/src/mapbox/layers/TrajservLayer.js b/src/mapbox/layers/TrajservLayer.js index 1ca39071..7fd7c47c 100644 --- a/src/mapbox/layers/TrajservLayer.js +++ b/src/mapbox/layers/TrajservLayer.js @@ -4,7 +4,6 @@ import { unByKey } from 'ol/Observable'; import TrackerLayer from './TrackerLayer'; import mixin from '../../common/mixins/TrajservLayerMixin'; import { getUTCTimeString } from '../../common/timeUtils'; -import { getSourceCoordinates, getResolution } from '../utils'; /** * Responsible for loading and display data from a Trajserv service. @@ -32,48 +31,6 @@ class TrajservLayer extends mixin(TrackerLayer) { this.onVisibilityChange = this.onVisibilityChange.bind(this); } - /** - * Add the mapbox layer and source to the map. - * - * @param {mapboxgl.Map} map A Mapbox map. - * @param {String} beforeId See [mapboxgl.Map#addLayer](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer) documentation. - */ - init(map, beforeId) { - if (!map) { - return; - } - - super.init(map); - - const source = { - type: 'canvas', - canvas: this.tracker.canvas, - coordinates: getSourceCoordinates(map, this.pixelRatio), - // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance. - animate: true, - attribution: this.copyrights, - }; - - this.beforeId = beforeId; - this.layer = { - id: this.key, - type: 'raster', - source: this.key, - layout: { - visibility: this.visible ? 'visible' : 'none', - }, - paint: { - 'raster-opacity': 1, - 'raster-fade-duration': 0, - 'raster-resampling': 'nearest', // important otherwise it looks blurry - }, - }; - map.addSource(this.key, source); - map.addLayer(this.layer, this.beforeId); - - this.listeners = [this.on('change:visible', this.onVisibilityChange)]; - } - /** * Remove the mapbox layer and the mapbox source. * @@ -109,35 +66,6 @@ class TrajservLayer extends mixin(TrackerLayer) { super.stop(); } - onVisibilityChange() { - if (this.visible && !this.map.getLayer(this.key)) { - this.map.addLayer(this.layer, this.beforeId); - } else if (this.map.getLayer(this.key)) { - this.map.removeLayer(this.key); - } - // We can't use setLayoutProperty it triggers an error probably a bug in mapbox - // this.map.setLayoutProperty( - // this.key, - // 'visibilty', - // this.visible ? 'visible' : 'none', - // ); - } - - /** - * Callback on 'move' event. - * @private - */ - onMove() { - this.map - .getSource(this.key) - .setCoordinates(getSourceCoordinates(this.map, this.pixelRatio)); - this.renderTrajectories( - undefined, - getResolution(this.map), - this.map.getBearing(), - ); - } - /** * Callback on 'moveend' event. * @private diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 7a2b3300..559faa02 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -1,5 +1,4 @@ import TrackerLayer from './TrackerLayer'; -import { getSourceCoordinates, getResolution } from '../utils'; import mixin from '../../common/mixins/TralisLayerMixin'; /** @@ -23,8 +22,6 @@ class TralisLayer extends mixin(TrackerLayer) { constructor(options = {}) { super({ ...options }); - /** @ignore */ - this.onMove = this.onMove.bind(this); /** @ignore */ this.onMoveEnd = this.onMoveEnd.bind(this); } @@ -36,35 +33,12 @@ class TralisLayer extends mixin(TrackerLayer) { * @param {string} beforeId See [mapboxgl.Map#addLayer](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer) beforeId documentation. */ init(map, beforeId) { - super.init(map); + super.init(map, beforeId); if (!this.map) { return; } - - this.map.on('zoomend', this.onZoomEnd); - this.map.on('move', this.onMove); this.map.on('moveend', this.onMoveEnd); - - this.map.addSource('canvas-source', { - type: 'canvas', - canvas: this.tracker.canvas, - coordinates: getSourceCoordinates(this.map, this.pixelRatio), - // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance. - animate: true, - }); - this.map.addLayer( - { - id: 'canvas-layer', - type: 'raster', - source: 'canvas-source', - paint: { - 'raster-opacity': 1, - 'raster-fade-duration': 0, - }, - }, - beforeId, - ); } /** @@ -72,31 +46,11 @@ class TralisLayer extends mixin(TrackerLayer) { */ terminate() { if (this.map) { - this.map.off('zoomend', this.onZoomEnd); - this.map.off('move', this.onMove); this.map.off('moveend', this.onMoveEnd); } super.terminate(); } - /** - * Callback on 'move' event. - * - * @private - */ - onMove() { - this.map - .getSource('canvas-source') - .setCoordinates(getSourceCoordinates(this.map, this.pixelRatio)); - - const { width, height } = this.map.getCanvas(); - this.renderTrajectories( - [width / this.pixelRatio, height / this.pixelRatio], - getResolution(this.map), - this.map.getBearing(), - ); - } - /** * Callback on 'moveend' event. * diff --git a/src/mapbox/utils.js b/src/mapbox/utils.js index 53c80426..cef06421 100644 --- a/src/mapbox/utils.js +++ b/src/mapbox/utils.js @@ -1,4 +1,3 @@ -import { fromLonLat } from 'ol/proj'; import { getWidth, getHeight } from 'ol/extent'; /** @@ -8,7 +7,7 @@ import { getWidth, getHeight } from 'ol/extent'; */ export const getResolution = (map) => { const bounds = map.getBounds().toArray(); - const extent = [...fromLonLat(bounds[0]), ...fromLonLat(bounds[1])]; + const extent = [...bounds[0], ...bounds[1]]; const { width, height } = map.getCanvas(); const xResolution = getWidth(extent) / width; const yResolution = getHeight(extent) / height; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 1c9c3809..d27ca750 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -28,56 +28,11 @@ class TrackerLayer extends mixin(Layer) { }); /** - * Boolean that defines if the layer is allow to renderTrajectories when this.map.getView().getInterating() id true. + * Boolean that defines if the layer is allow to renderTrajectories when the map is zooming, rotating or poanning true. * It's useful to avoid rendering the map when the map is animating or interacting. * @type {function} */ - this.renderWhenInteracting = options.renderWhenInteracting || (() => true); - - /** - * Function to define if we want to allow the render of trajectories when the map is animating. By default the function return true. - * If it returns false, the canva swill be scaled - * @type {function} - */ - this.renderWhenAnimating = options.renderWhenAnimating || (() => true); - - /** - * Function to define if we want to allow the render of trajectories when the map is zooming. By default the function return true. - * If it returns false, the canvas will be scaled using css until the next render. - * @type {function} - */ - this.renderWhenZooming = - options.renderWhenZooming || - ((viewState, renderedState) => { - if (viewState.resolution <= renderedState.resolution) { - this.tracker.clear(); - console.log('tufalsere'); - return false; - } - console.log('ture'); - return !this.map.getView().getInteracting(); - }); - - /** - * Function to define when allowing the render of trajectories depending on the zoom level. Default the fundtion return true. - * It's useful to avoid rendering the map when the map is animating or interacting. - * @type {function} - */ - this.renderWhenPanning = options.renderWhenPanning || (() => true); - - /** - * Function to define if we want to allow the render of trajectories when the map is rotating. By default the function return true. - * If it returns false, the canvas will be rotated using css until the next render. - * @type {function} - */ - this.renderWhenRotating = options.renderWhenRotating || (() => true); - - /** - * Function to define when allowing the render of trajectories depending on the rotation. Default the fundtion return true. - * It's useful to avoid rendering the map when the map is animating or interacting. - * @type {function} - */ - this.renderWhenRotating = options.renderWhenRotating || (() => true); + this.renderWhenInteracting = options.renderWhenInteracting || (() => false); this.olLayer = options.olLayer || @@ -89,7 +44,6 @@ class TrackerLayer extends mixin(Layer) { if (!this.tracker || !this.tracker.canvas) { return null; } - if (!this.container) { this.container = document.createElement('div'); this.container.style.position = 'absolute'; @@ -101,13 +55,13 @@ class TrackerLayer extends mixin(Layer) { this.transformContainer.style.height = '100%'; this.container.appendChild(this.transformContainer); this.tracker.canvas.style.position = 'absolute'; + this.tracker.canvas.style.top = '0'; this.tracker.canvas.style.left = '0'; this.tracker.canvas.style.transformOrigin = 'top left'; this.transformContainer.appendChild(this.tracker.canvas); } if (this.renderedViewState) { - // Update container tranform const { center, resolution, rotation } = frameState.viewState; const { center: renderedCenter, @@ -116,43 +70,17 @@ class TrackerLayer extends mixin(Layer) { } = this.renderedViewState; if ( - !this.map.getView().getInteracting() && - !this.map.getView().getAnimating() + this.renderWhenInteracting && + this.renderWhenInteracting( + frameState.viewState, + this.renderedViewState, + ) ) { - if ( - resolution !== renderedResolution && - this.renderWhenZooming - ) { - const allowRender = this.renderWhenZooming( - frameState.viewState, - this.renderedViewState, - ); - if (allowRender) { - console.log('ici'); - this.renderTrajectories(true); - } - } else if ( - (center[0] !== renderedCenter[0] || - center[1] !== renderedCenter[1]) && - this.renderWhenPanning( - frameState.viewState, - this.renderedViewState, - ) - ) { - console.log('ici2'); - this.renderTrajectories(true); - } else if ( - rotation !== renderedRotation && - this.renderWhenRotating( - frameState.viewState, - this.renderedViewState, - ) - ) { - console.log('ici3'); - this.renderTrajectories(true); - } + this.renderTrajectories(true); + } else if (renderedResolution / resolution >= 3) { + // Avoid having really big points when zooming fast. + this.tracker.clear(); } else { - console.log('ici4'); this.transformContainer.style.transform = composeCssTransform( (renderedCenter[0] - center[0]) / resolution, (center[1] - renderedCenter[1]) / resolution, @@ -187,21 +115,6 @@ class TrackerLayer extends mixin(Layer) { this.olEventsKeys = []; // Be careful to not override this value in child classe. } - /** - * Initialize the layer and listen to feature clicks. - * @param {ol/Map~Map} map A OpenLayers map. - * @private - */ - init(map) { - if (!map) { - return; - } - - super.init(map, { - getPixelFromCoordinate: map.getPixelFromCoordinate.bind(map), - }); - } - /** * Trackerlayer is started. * @private @@ -262,6 +175,8 @@ class TrackerLayer extends mixin(Layer) { const view = this.map.getView(); super.renderTrajectories( this.map.getSize(), + this.map.getView().getCenter(), + view.calculateExtent(), view.getResolution(), view.getRotation(), noInterpolate, @@ -272,9 +187,20 @@ class TrackerLayer extends mixin(Layer) { * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. * @private */ - renderTrajectoriesInternal(size, resolution, rotation, noInterpolate) { - const isRendered = super.renderTrajectoriesInternal( + renderTrajectoriesInternal( + size, + center, + extent, + resolution, + rotation, + noInterpolate, + ) { + let isRendered = false; + + isRendered = super.renderTrajectoriesInternal( size, + center, + extent, resolution, rotation, noInterpolate, @@ -282,15 +208,17 @@ class TrackerLayer extends mixin(Layer) { // We update the current render state. if (isRendered) { - if (this.transformContainer) { - this.transformContainer.style.transform = null; - } this.renderedViewState = { size, - center: this.map.getView().getCenter(), + center, + extent, resolution, rotation, }; + + if (this.transformContainer) { + this.transformContainer.style.transform = ''; + } } } diff --git a/src/ol/layers/TrajservLayer.js b/src/ol/layers/TrajservLayer.js index 81872fb5..3fb4f772 100644 --- a/src/ol/layers/TrajservLayer.js +++ b/src/ol/layers/TrajservLayer.js @@ -84,6 +84,10 @@ class TrajservLayer extends mixin(TrackerLayer) { this.olEventsKeys = [ ...this.olEventsKeys, this.map.on('singleclick', this.onMapClick.bind(this)), + + this.map.on('movestart', () => { + this.abortFetchTrajectories(); + }), this.map.on('moveend', this.onMoveEnd.bind(this)), ]; } @@ -93,7 +97,13 @@ class TrajservLayer extends mixin(TrackerLayer) { * @private */ onMoveEnd() { - this.updateTrajectories(); + this.abortFetchTrajectories(); + if ( + !this.map.getView().getAnimating() && + !this.map.getView().getInteracting() + ) { + this.updateTrajectories(); + } if (this.selectedVehicleId && this.journeyId) { this.highlightTrajectory(); } From b60addd42d09bfdca3999ce3805b6f5beaea5ad1 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 18 Oct 2021 13:42:33 +0200 Subject: [PATCH 032/109] USe transform for mb --- __mocks__/mapbox-gl.js | 11 +- package.json | 3 + src/common/Tracker.js | 3 +- src/mapbox/layers/TrackerLayer.js | 88 ++++- src/mapbox/utils.js | 9 +- yarn.lock | 630 ++++++++++++++++++++++++++++++ 6 files changed, 723 insertions(+), 21 deletions(-) diff --git a/__mocks__/mapbox-gl.js b/__mocks__/mapbox-gl.js index 474a567e..29541739 100644 --- a/__mocks__/mapbox-gl.js +++ b/__mocks__/mapbox-gl.js @@ -36,7 +36,9 @@ class Map { getZoom() {} - getBearing() {} + getBearing() { + return 0; + } once() {} @@ -49,12 +51,7 @@ class Map { remove() {} unproject() { - return [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - ]; + return { lng: 0, lat: 0 }; } } module.exports = { diff --git a/package.json b/package.json index 737435f1..1a5dd557 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "@material-ui/core": "^4.9.14", "@material-ui/icons": "^4.9.1", "@material-ui/styles": "^4.9.14", + "@turf/helpers": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", "codesandbox": "^2.1.16", "es6-mixin": "^0.3.0", "path": "^0.12.7", @@ -25,6 +27,7 @@ "react-scrollchor": "^6.0.0", "react-syntax-highlighter": "^15.4.1", "taffydb": "^2.7.3", + "turf": "^3.0.14", "typeface-lato": "^0.0.75", "uuid": "^8.1.0" }, diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 8c8a4e73..3ee5174c 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -1,7 +1,6 @@ import { unByKey } from 'ol/Observable'; import GeomType from 'ol/geom/GeometryType'; import { compose, apply, create } from 'ol/transform'; -import { toLonLat } from 'ol/proj'; /** * Tracker. This class stores and allows to draw trajectories on a canvas. @@ -290,7 +289,7 @@ export default class Tracker { // We set the rotation of the trajectory (used by tralis). this.trajectories[i].coordinate = coord; // console.log([...toLonLat(coord)]); - let px = apply(coordinateToPixelTransform, [...toLonLat(coord)]); + let px = apply(coordinateToPixelTransform, [...coord]); // [...toLonLat(coord)]); // console.log(px); if (!px) { // eslint-disable-next-line no-continue diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index b93b3767..f18b3396 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -1,9 +1,11 @@ import { fromLonLat } from 'ol/proj'; import { unByKey } from 'ol/Observable'; +import { getWidth, getHeight } from 'ol/extent'; +import transformRotate from '@turf/transform-rotate'; +import { point } from '@turf/helpers'; import Layer from '../../common/layers/Layer'; import mixin from '../../common/mixins/TrackerLayerMixin'; -import { getSourceCoordinates, getResolution } from '../utils'; - +import { getSourceCoordinates, getMercatorResolution } from '../utils'; /** * Responsible for loading tracker data. * @@ -124,14 +126,82 @@ class TrackerLayer extends mixin(Layer) { * @overrides */ renderTrajectories(noInterpolate) { - const canvas = this.map.getCanvas(); + const { width, height } = this.map.getCanvas(); const center = this.map.getCenter(); - const extent = getSourceCoordinates(this.map, this.pixelRatio); + // const extent = getSourceCoordinates(this.map, this.pixelRatio); + // const extent2 = this.map.getBounds(); + // const bounds = [ + // ...fromLonLat([extent[0][0], extent[2][1]]), + // ...fromLonLat([extent[1][0], extent[1][1]]), + // ]; + // const bounds2 = extent2.toArray(); + // const bounds3 = [...fromLonLat(bounds2[0]), ...fromLonLat(bounds2[1])]; + // console.log(extent); + // const a = bounds[0]; + // const b = bounds[1]; + // const transform = compose( + // create(), + // center.lng, + // center.lat, + // 1, + // 1, + // (this.map.getBearing() * Math.PI) / 180, + // -center.lng, + // -center.lat, + // ); + // composeCssTransform( + // (renderedCenter[0] - center[0]) / resolution, + // (center[1] - renderedCenter[1]) / resolution, + // renderedResolution / resolution, + // renderedResolution / resolution, + // rotation - renderedRotation, + // 0, + // 0, + + // turf + const leftBottom = this.map.unproject({ + x: 0, + y: height / this.pixelRatio, + }); // southWest + const rightTop = this.map.unproject({ x: width / this.pixelRatio, y: 0 }); // north east + + const coord0 = transformRotate( + point([leftBottom.lng, leftBottom.lat]), + -this.map.getBearing(), + { + pivot: [center.lng, center.lat], + }, + ).geometry.coordinates; + const coord1 = transformRotate( + point([rightTop.lng, rightTop.lat]), + -this.map.getBearing(), + { + pivot: [center.lng, center.lat], + }, + ).geometry.coordinates; + + // ol + // const coord0 = apply(transform, [leftBottom.lng, leftBottom.lat]); + // const coord1 = apply(transform, [rightTop.lng, rightTop.lat]); // [...toLonLat(coord)]); + // console.log( + // [extent[0][0], extent[1][1]], + // coord0, + // [extent[1][0], extent[0][1]], + // coord1, + // ); + const bounds = [...fromLonLat(coord0), ...fromLonLat(coord1)]; + const xResolution = getWidth(bounds) / (width / this.pixelRatio); + const yResolution = getHeight(bounds) / (height / this.pixelRatio); + const res = Math.max(xResolution, yResolution); + // console.log(coord0, coord02); + // console.log(bounds2[0], px); + + // Coordinate of trajectories are in mercator so we have to pass the propert resolution and center in mercator. super.renderTrajectories( - [canvas.width / this.pixelRatio, canvas.height / this.pixelRatio], - [center.lng, center.lat], - [extent[0][0], extent[1][1], extent[1][0], extent[0][1]], - getResolution(this.map), + [width / this.pixelRatio, height / this.pixelRatio], + fromLonLat([center.lng, center.lat]), + bounds, + res, -(this.map.getBearing() * Math.PI) / 180, noInterpolate, ); @@ -153,7 +223,7 @@ class TrackerLayer extends mixin(Layer) { * @override */ getVehiclesAtCoordinate(coordinate, nb) { - const resolution = getResolution(this.map); + const resolution = getMercatorResolution(this.map); return super.getVehiclesAtCoordinate(coordinate, resolution, nb); } diff --git a/src/mapbox/utils.js b/src/mapbox/utils.js index cef06421..7b5c97ef 100644 --- a/src/mapbox/utils.js +++ b/src/mapbox/utils.js @@ -1,13 +1,16 @@ import { getWidth, getHeight } from 'ol/extent'; +import { fromLonLat } from 'ol/proj'; /** * Get the current resolution of a Mapbox map. * @param {mapboxgl.Map} map A map object. * @private */ -export const getResolution = (map) => { +export const getMercatorResolution = (map) => { const bounds = map.getBounds().toArray(); - const extent = [...bounds[0], ...bounds[1]]; + const a = fromLonLat(bounds[0]); + const b = fromLonLat(bounds[1]); + const extent = [...a, ...b]; const { width, height } = map.getCanvas(); const xResolution = getWidth(extent) / width; const yResolution = getHeight(extent) / height; @@ -38,6 +41,6 @@ export const getSourceCoordinates = (map, pixelRatio) => { }; export default { - getResolution, + getMercatorResolution, getSourceCoordinates, }; diff --git a/yarn.lock b/yarn.lock index c67ec0ab..ad58e68d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1596,6 +1596,78 @@ remark "^12.0.0" unist-util-find-all-after "^3.0.1" +"@turf/centroid@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/centroid/-/centroid-6.5.0.tgz#ecaa365412e5a4d595bb448e7dcdacfb49eb0009" + integrity sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + +"@turf/clone@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/clone/-/clone-6.5.0.tgz#895860573881ae10a02dfff95f274388b1cda51a" + integrity sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw== + dependencies: + "@turf/helpers" "^6.5.0" + +"@turf/helpers@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-6.5.0.tgz#f79af094bd6b8ce7ed2bd3e089a8493ee6cae82e" + integrity sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw== + +"@turf/invariant@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-6.5.0.tgz#970afc988023e39c7ccab2341bd06979ddc7463f" + integrity sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg== + dependencies: + "@turf/helpers" "^6.5.0" + +"@turf/meta@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-6.5.0.tgz#b725c3653c9f432133eaa04d3421f7e51e0418ca" + integrity sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA== + dependencies: + "@turf/helpers" "^6.5.0" + +"@turf/rhumb-bearing@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz#8c41ad62b44fb4e57c14fe790488056684eee7b9" + integrity sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/invariant" "^6.5.0" + +"@turf/rhumb-destination@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz#12da8c85e674b182e8b0ec8ea9c5fe2186716dae" + integrity sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/invariant" "^6.5.0" + +"@turf/rhumb-distance@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz#ed068004b1469512b857070fbf5cb7b7eabbe592" + integrity sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/invariant" "^6.5.0" + +"@turf/transform-rotate@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz#e50e96a8779af91d58149eedb00ffd7f6395c804" + integrity sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag== + dependencies: + "@turf/centroid" "^6.5.0" + "@turf/clone" "^6.5.0" + "@turf/helpers" "^6.5.0" + "@turf/invariant" "^6.5.0" + "@turf/meta" "^6.5.0" + "@turf/rhumb-bearing" "^6.5.0" + "@turf/rhumb-destination" "^6.5.0" + "@turf/rhumb-distance" "^6.5.0" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -2057,6 +2129,13 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +affine-hull@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/affine-hull/-/affine-hull-1.0.0.tgz#763ff1d38d063ceb7e272f17ee4d7bbcaf905c5d" + integrity sha1-dj/x040GPOt+Jy8X7k17vK+QXF0= + dependencies: + robust-orientation "^1.1.3" + agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -2744,6 +2823,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bit-twiddle@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz#0c6c1fabe2b23d17173d9a61b7b7093eb9e1769e" + integrity sha1-DGwfq+KyPRcXPZpht7cJPrnhdp4= + bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -3801,6 +3885,15 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +convex-hull@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/convex-hull/-/convex-hull-1.0.3.tgz#20a3aa6ce87f4adea2ff7d17971c9fc1c67e1fff" + integrity sha1-IKOqbOh/St6i/30XlxyfwcZ+H/8= + dependencies: + affine-hull "^1.0.0" + incremental-convex-hull "^1.0.1" + monotone-convex-hull-2d "^1.0.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -4610,6 +4703,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +earcut@^2.0.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.3.tgz#d44ced2ff5a18859568e327dd9c7d46b16f55cf4" + integrity sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug== + earcut@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.2.tgz#41b0bc35f63e0fe80da7cddff28511e7e2e80d11" @@ -6105,6 +6203,23 @@ gensync@^1.0.0-beta.1: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== +geojson-area@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/geojson-area/-/geojson-area-0.2.1.tgz#2537b0982db86309f21d2c428a4257c7a6282cc6" + integrity sha1-JTewmC24YwnyHSxCikJXx6YoLMY= + dependencies: + wgs84 "0.0.0" + +geojson-normalize@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/geojson-normalize/-/geojson-normalize-0.0.0.tgz#2dbc3678cd1b31b8179e876bda70cd120dde35c0" + integrity sha1-Lbw2eM0bMbgXnodr2nDNEg3eNcA= + +geojson-random@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/geojson-random/-/geojson-random-0.2.2.tgz#ab4838f126adc5e16f8f94e655def820f9119dbc" + integrity sha1-q0g48SatxeFvj5TmVd74IPkRnbw= + geojson-vt@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" @@ -6975,6 +7090,14 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== +incremental-convex-hull@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/incremental-convex-hull/-/incremental-convex-hull-1.0.1.tgz#51428c14cb9d9a6144bfe69b2851fb377334be1e" + integrity sha1-UUKMFMudmmFEv+abKFH7N3M0vh4= + dependencies: + robust-orientation "^1.1.2" + simplicial-complex "^1.0.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -8271,6 +8394,11 @@ jss@^10.0.3, jss@^10.3.0: is-in-browser "^1.1.3" tiny-warning "^1.0.2" +jsts@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/jsts/-/jsts-1.1.2.tgz#d205d2cc8393081d9e484ae36282110695edc230" + integrity sha1-0gXSzIOTCB2eSErjYoIRBpXtwjA= + jsx-ast-utils@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" @@ -9292,6 +9420,13 @@ moment@^2.27.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== +monotone-convex-hull-2d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/monotone-convex-hull-2d/-/monotone-convex-hull-2d-1.0.1.tgz#47f5daeadf3c4afd37764baa1aa8787a40eee08c" + integrity sha1-R/Xa6t88Sv03dkuqGqh4ekDu4Iw= + dependencies: + robust-orientation "^1.1.3" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -11650,6 +11785,34 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +robust-orientation@^1.1.2, robust-orientation@^1.1.3: + version "1.2.1" + resolved "https://registry.yarnpkg.com/robust-orientation/-/robust-orientation-1.2.1.tgz#f6c2b00a5df5f1cb9597be63a45190f273899361" + integrity sha512-FuTptgKwY6iNuU15nrIJDLjXzCChWB+T4AvksRtwPS/WZ3HuP1CElCm1t+OBfgQKfWbtZIawip+61k7+buRKAg== + dependencies: + robust-scale "^1.0.2" + robust-subtract "^1.0.0" + robust-sum "^1.0.0" + two-product "^1.0.2" + +robust-scale@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/robust-scale/-/robust-scale-1.0.2.tgz#775132ed09542d028e58b2cc79c06290bcf78c32" + integrity sha1-d1Ey7QlULQKOWLLMecBikLz3jDI= + dependencies: + two-product "^1.0.2" + two-sum "^1.0.0" + +robust-subtract@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/robust-subtract/-/robust-subtract-1.0.0.tgz#e0b164e1ed8ba4e3a5dda45a12038348dbed3e9a" + integrity sha1-4LFk4e2LpOOl3aRaEgODSNvtPpo= + +robust-sum@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/robust-sum/-/robust-sum-1.0.0.tgz#16646e525292b4d25d82757a286955e0bbfa53d9" + integrity sha1-FmRuUlKStNJdgnV6KGlV4Lv6U9k= + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -11995,6 +12158,19 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +simplicial-complex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simplicial-complex/-/simplicial-complex-1.0.0.tgz#6c33a4ed69fcd4d91b7bcadd3b30b63683eae241" + integrity sha1-bDOk7Wn81Nkbe8rdOzC2NoPq4kE= + dependencies: + bit-twiddle "^1.0.0" + union-find "^1.0.0" + +simplify-js@^1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/simplify-js/-/simplify-js-1.2.4.tgz#7aab22d6df547ffd40ef0761ccd82b75287d45c7" + integrity sha512-vITfSlwt7h/oyrU42R83mtzFpwYk3+mkH9bOHqq/Qw6n8rtR7aE3NZQ5fbcyCUVVmuMJR6ynsAhOfK2qoah8Jg== + sisteransi@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -13130,11 +13306,455 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +turf-along@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-along/-/turf-along-3.0.12.tgz#e622bde7a4bd138c09647d4b14aa0ea700485de6" + integrity sha1-5iK956S9E4wJZH1LFKoOpwBIXeY= + dependencies: + turf-bearing "^3.0.12" + turf-destination "^3.0.12" + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-area@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-area/-/turf-area-3.0.12.tgz#9b7e469ef9fb558fd147bb0c214823263bdbf13c" + integrity sha1-m35Gnvn7VY/RR7sMIUgjJjvb8Tw= + dependencies: + geojson-area "^0.2.1" + +turf-bbox-polygon@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-bbox-polygon/-/turf-bbox-polygon-3.0.12.tgz#330dc0bb38322d61545df966ce6c80f685acf4f2" + integrity sha1-Mw3AuzgyLWFUXflmzmyA9oWs9PI= + dependencies: + turf-helpers "^3.0.12" + +turf-bbox@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-bbox/-/turf-bbox-3.0.12.tgz#3fa06117c8443860ec80ac60fd5d2f1320bfb1be" + integrity sha1-P6BhF8hEOGDsgKxg/V0vEyC/sb4= + dependencies: + turf-meta "^3.0.12" + +turf-bearing@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-bearing/-/turf-bearing-3.0.12.tgz#65f609dd850e7364c7771aa6ded87b0e1917fd20" + integrity sha1-ZfYJ3YUOc2THdxqm3th7DhkX/SA= + dependencies: + turf-invariant "^3.0.12" + +turf-bezier@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-bezier/-/turf-bezier-3.0.12.tgz#102efdd4a63b265ee9c8c1727631920b36f4dd02" + integrity sha1-EC791KY7Jl7pyMFydjGSCzb03QI= + dependencies: + turf-helpers "^3.0.12" + +turf-buffer@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-buffer/-/turf-buffer-3.0.12.tgz#20840fe7c6aa67b24be1cab7ffcc5a82fd6bd971" + integrity sha1-IIQP58aqZ7JL4cq3/8xagv1r2XE= + dependencies: + geojson-normalize "0.0.0" + jsts "1.1.2" + turf-combine "^3.0.12" + turf-helpers "^3.0.12" + +turf-center@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-center/-/turf-center-3.0.12.tgz#45dd6c1729bb867291e3e002e9c7506f8c440196" + integrity sha1-Rd1sFym7hnKR4+AC6cdQb4xEAZY= + dependencies: + turf-bbox "^3.0.12" + turf-helpers "^3.0.12" + +turf-centroid@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-centroid/-/turf-centroid-3.0.12.tgz#eaee0d698204b57fc33994bb1bc867b8da293f8f" + integrity sha1-6u4NaYIEtX/DOZS7G8hnuNopP48= + dependencies: + turf-helpers "^3.0.12" + turf-meta "^3.0.12" + +turf-circle@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-circle/-/turf-circle-3.0.12.tgz#140b21cb4950f2d3cbc70d2df012936867f58930" + integrity sha1-FAshy0lQ8tPLxw0t8BKTaGf1iTA= + dependencies: + turf-destination "^3.0.12" + turf-helpers "^3.0.12" + +turf-collect@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-collect/-/turf-collect-3.0.12.tgz#6e986d1a707da319cc83e7238d0bcdf19aa3c7f2" + integrity sha1-bphtGnB9oxnMg+cjjQvN8Zqjx/I= + dependencies: + turf-inside "^3.0.12" + +turf-combine@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-combine/-/turf-combine-3.0.12.tgz#1670746f0fdce0d1ea8aa6a29ffe5438d446cf73" + integrity sha1-FnB0bw/c4NHqiqain/5UONRGz3M= + dependencies: + turf-meta "^3.0.12" + +turf-concave@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-concave/-/turf-concave-3.0.12.tgz#fcab6056965b0a8319f6cd802601095f2fd3a8eb" + integrity sha1-/KtgVpZbCoMZ9s2AJgEJXy/TqOs= + dependencies: + turf-distance "^3.0.12" + turf-meta "^3.0.12" + turf-tin "^3.0.12" + turf-union "^3.0.12" + +turf-convex@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-convex/-/turf-convex-3.0.12.tgz#a88ddc3e22d1cb658796a9c85d3ada3bd3eca357" + integrity sha1-qI3cPiLRy2WHlqnIXTraO9Pso1c= + dependencies: + convex-hull "^1.0.3" + turf-helpers "^3.0.12" + turf-meta "^3.0.12" + +turf-destination@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-destination/-/turf-destination-3.0.12.tgz#7dd6fbf97e86f831a26c83ef2d5a2f8d1d8a6de2" + integrity sha1-fdb7+X6G+DGibIPvLVovjR2KbeI= + dependencies: + turf-helpers "^3.0.12" + turf-invariant "^3.0.12" + +turf-difference@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-difference/-/turf-difference-3.0.12.tgz#9c3d0d7630421005b8b25b7f068ed9efb4bc6ea7" + integrity sha1-nD0NdjBCEAW4slt/Bo7Z77S8bqc= + dependencies: + jsts "1.1.2" + turf-helpers "^3.0.12" + +turf-distance@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-distance/-/turf-distance-3.0.12.tgz#fb97b8705facd993b145e014b41862610eeca449" + integrity sha1-+5e4cF+s2ZOxReAUtBhiYQ7spEk= + dependencies: + turf-helpers "^3.0.12" + turf-invariant "^3.0.12" + +turf-envelope@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-envelope/-/turf-envelope-3.0.12.tgz#96921d278cc8c664692e320e2543b914080d786b" + integrity sha1-lpIdJ4zIxmRpLjIOJUO5FAgNeGs= + dependencies: + turf-bbox "^3.0.12" + turf-bbox-polygon "^3.0.12" + +turf-explode@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-explode/-/turf-explode-3.0.12.tgz#c5ae28c284cd006c56511ec7d408c48a5414ecfe" + integrity sha1-xa4owoTNAGxWUR7H1AjEilQU7P4= + dependencies: + turf-helpers "^3.0.12" + turf-meta "^3.0.12" + +turf-flip@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-flip/-/turf-flip-3.0.12.tgz#deb868177b9ff3bb310c5d41aaac61a9156a3cbb" + integrity sha1-3rhoF3uf87sxDF1BqqxhqRVqPLs= + dependencies: + turf-meta "^3.0.12" + +turf-grid@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/turf-grid/-/turf-grid-1.0.1.tgz#b904abc564b939b627a66ac15eb16e053829b80f" + integrity sha1-uQSrxWS5ObYnpmrBXrFuBTgpuA8= + dependencies: + turf-point "^2.0.0" + +turf-helpers@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-helpers/-/turf-helpers-3.0.12.tgz#dd4272e74b3ad7c96eecb7ae5c57fe8eca544b7b" + integrity sha1-3UJy50s618lu7LeuXFf+jspUS3s= + +turf-hex-grid@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-hex-grid/-/turf-hex-grid-3.0.12.tgz#0698ef669020bb31d8e9cc2056d0abfcafc84e8f" + integrity sha1-BpjvZpAguzHY6cwgVtCr/K/ITo8= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-inside@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-inside/-/turf-inside-3.0.12.tgz#9ba40fa6eed63bec7e7d88aa6427622c4df07066" + integrity sha1-m6QPpu7WO+x+fYiqZCdiLE3wcGY= + dependencies: + turf-invariant "^3.0.12" + +turf-intersect@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-intersect/-/turf-intersect-3.0.12.tgz#c0d7fb305843a19275670057a39d268b17830d83" + integrity sha1-wNf7MFhDoZJ1ZwBXo50mixeDDYM= + dependencies: + jsts "1.1.2" + +turf-invariant@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-invariant/-/turf-invariant-3.0.12.tgz#3b95253953991ebd962dd35d4f6704c287de8ebe" + integrity sha1-O5UlOVOZHr2WLdNdT2cEwofejr4= + +turf-isolines@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-isolines/-/turf-isolines-3.0.12.tgz#00b233dfe2eebd4ecb47a94fc923c6ecec89c7ab" + integrity sha1-ALIz3+LuvU7LR6lPySPG7OyJx6s= + dependencies: + turf-bbox "^3.0.12" + turf-grid "1.0.1" + turf-helpers "^3.0.12" + turf-inside "^3.0.12" + turf-planepoint "^3.0.12" + turf-square "^3.0.12" + turf-tin "^3.0.12" + +turf-kinks@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-kinks/-/turf-kinks-3.0.12.tgz#e9c9a8dba5724d98f2350fc5bdeba069ec333755" + integrity sha1-6cmo26VyTZjyNQ/FveugaewzN1U= + dependencies: + turf-helpers "^3.0.12" + +turf-line-distance@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-line-distance/-/turf-line-distance-3.0.12.tgz#7108f5b26907f7b8c2dd1b3997866dd3a60e8f5f" + integrity sha1-cQj1smkH97jC3Rs5l4Zt06YOj18= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-line-slice@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-line-slice/-/turf-line-slice-3.0.12.tgz#f5f1accc92adae69ea1ac0b29f07529a28dde916" + integrity sha1-9fGszJKtrmnqGsCynwdSmijd6RY= + dependencies: + turf-bearing "^3.0.12" + turf-destination "^3.0.12" + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + turf-point-on-line "^3.0.12" + +turf-meta@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-meta/-/turf-meta-3.0.12.tgz#0aa9a1caf82b2a5a08d54e0830b5b5a3fa0e8a38" + integrity sha1-CqmhyvgrKloI1U4IMLW1o/oOijg= + +turf-midpoint@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-midpoint/-/turf-midpoint-3.0.12.tgz#b12765ae89acdee8556fd5e26c9c5fa041a02cbe" + integrity sha1-sSdlroms3uhVb9XibJxfoEGgLL4= + dependencies: + turf-bearing "^3.0.12" + turf-destination "^3.0.12" + turf-distance "^3.0.12" + turf-invariant "^3.0.12" + +turf-nearest@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-nearest/-/turf-nearest-3.0.12.tgz#700207f4443f05096f86cd246f929f170dfaf46d" + integrity sha1-cAIH9EQ/BQlvhs0kb5KfFw369G0= + dependencies: + turf-distance "^3.0.12" + +turf-planepoint@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-planepoint/-/turf-planepoint-3.0.12.tgz#2c37ae0f17fcb30db6e38f0d59ee6c0dd6caa9af" + integrity sha1-LDeuDxf8sw22448NWe5sDdbKqa8= + +turf-point-grid@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-point-grid/-/turf-point-grid-3.0.12.tgz#d604978be10bc9e53306ae02cef7098431db4971" + integrity sha1-1gSXi+ELyeUzBq4CzvcJhDHbSXE= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-point-on-line@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-point-on-line/-/turf-point-on-line-3.0.12.tgz#1d8663354e70372db1863e6253e9040c47127b0f" + integrity sha1-HYZjNU5wNy2xhj5iU+kEDEcSew8= + dependencies: + turf-bearing "^3.0.12" + turf-destination "^3.0.12" + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-point-on-surface@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-point-on-surface/-/turf-point-on-surface-3.0.12.tgz#9be505b6b0ba78e98565001de3b3a4267115240a" + integrity sha1-m+UFtrC6eOmFZQAd47OkJnEVJAo= + dependencies: + turf-center "^3.0.12" + turf-distance "^3.0.12" + turf-explode "^3.0.12" + turf-helpers "^3.0.12" + turf-inside "^3.0.12" + +turf-point@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/turf-point/-/turf-point-2.0.1.tgz#a2dcc30a2d20f44cf5c6271df7bae2c0e2146069" + integrity sha1-otzDCi0g9Ez1xicd97riwOIUYGk= + dependencies: + minimist "^1.1.0" + +turf-random@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-random/-/turf-random-3.0.12.tgz#34dbb141c3f1eaeae1424fd6c5eaba1f6fb9b1e8" + integrity sha1-NNuxQcPx6urhQk/Wxeq6H2+5seg= + dependencies: + geojson-random "^0.2.2" + +turf-sample@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-sample/-/turf-sample-3.0.12.tgz#7949f8620612047e1314c1ced87e99c142463cd2" + integrity sha1-eUn4YgYSBH4TFMHO2H6ZwUJGPNI= + dependencies: + turf-helpers "^3.0.12" + +turf-simplify@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-simplify/-/turf-simplify-3.0.12.tgz#85e443c8b46aa2b7526389444c7381daa2ad19e7" + integrity sha1-heRDyLRqordSY4lETHOB2qKtGec= + dependencies: + simplify-js "^1.2.1" + +turf-square-grid@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-square-grid/-/turf-square-grid-3.0.12.tgz#3c1d80ac14556c6813b478bda012512ed4b93ec8" + integrity sha1-PB2ArBRVbGgTtHi9oBJRLtS5Psg= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-square@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-square/-/turf-square-3.0.12.tgz#1a38b1e0fb05ffe0fcaa43188e2f37942a515b64" + integrity sha1-Gjix4PsF/+D8qkMYji83lCpRW2Q= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-tag@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-tag/-/turf-tag-3.0.12.tgz#2284fff0e8a1e92a27d4ac7fd7471b3c48ddd1a8" + integrity sha1-IoT/8Oih6Son1Kx/10cbPEjd0ag= + dependencies: + turf-inside "^3.0.12" + +turf-tesselate@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-tesselate/-/turf-tesselate-3.0.12.tgz#41474b7b5b3820bcf273fb71e1894d8c3cd40d35" + integrity sha1-QUdLe1s4ILzyc/tx4YlNjDzUDTU= + dependencies: + earcut "^2.0.0" + turf-helpers "^3.0.12" + +turf-tin@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-tin/-/turf-tin-3.0.12.tgz#b6534644763ace1c9df241c958d2384855257385" + integrity sha1-tlNGRHY6zhyd8kHJWNI4SFUlc4U= + dependencies: + turf-helpers "^3.0.12" + +turf-triangle-grid@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-triangle-grid/-/turf-triangle-grid-3.0.12.tgz#80647e57dafe09346879a29a18a0e6294acf1159" + integrity sha1-gGR+V9r+CTRoeaKaGKDmKUrPEVk= + dependencies: + turf-distance "^3.0.12" + turf-helpers "^3.0.12" + +turf-union@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-union/-/turf-union-3.0.12.tgz#dfed0e5540b8c2855e4994c14621e3a60c829c8e" + integrity sha1-3+0OVUC4woVeSZTBRiHjpgyCnI4= + dependencies: + jsts "1.1.2" + +turf-within@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/turf-within/-/turf-within-3.0.12.tgz#f77eeaf377238561b7fb1338e76e9d1298741f94" + integrity sha1-937q83cjhWG3+xM4526dEph0H5Q= + dependencies: + turf-helpers "^3.0.12" + turf-inside "^3.0.12" + +turf@^3.0.14: + version "3.0.14" + resolved "https://registry.yarnpkg.com/turf/-/turf-3.0.14.tgz#eb2f4a80a2d583b8c6486bc7b5c7190466866c27" + integrity sha1-6y9KgKLVg7jGSGvHtccZBGaGbCc= + dependencies: + turf-along "^3.0.12" + turf-area "^3.0.12" + turf-bbox "^3.0.12" + turf-bbox-polygon "^3.0.12" + turf-bearing "^3.0.12" + turf-bezier "^3.0.12" + turf-buffer "^3.0.12" + turf-center "^3.0.12" + turf-centroid "^3.0.12" + turf-circle "^3.0.12" + turf-collect "^3.0.12" + turf-combine "^3.0.12" + turf-concave "^3.0.12" + turf-convex "^3.0.12" + turf-destination "^3.0.12" + turf-difference "^3.0.12" + turf-distance "^3.0.12" + turf-envelope "^3.0.12" + turf-explode "^3.0.12" + turf-flip "^3.0.12" + turf-helpers "^3.0.12" + turf-hex-grid "^3.0.12" + turf-inside "^3.0.12" + turf-intersect "^3.0.12" + turf-isolines "^3.0.12" + turf-kinks "^3.0.12" + turf-line-distance "^3.0.12" + turf-line-slice "^3.0.12" + turf-meta "^3.0.12" + turf-midpoint "^3.0.12" + turf-nearest "^3.0.12" + turf-planepoint "^3.0.12" + turf-point-grid "^3.0.12" + turf-point-on-line "^3.0.12" + turf-point-on-surface "^3.0.12" + turf-random "^3.0.12" + turf-sample "^3.0.12" + turf-simplify "^3.0.12" + turf-square "^3.0.12" + turf-square-grid "^3.0.12" + turf-tag "^3.0.12" + turf-tesselate "^3.0.12" + turf-tin "^3.0.12" + turf-triangle-grid "^3.0.12" + turf-union "^3.0.12" + turf-within "^3.0.12" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +two-product@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/two-product/-/two-product-1.0.2.tgz#67d95d4b257a921e2cb4bd7af9511f9088522eaa" + integrity sha1-Z9ldSyV6kh4stL16+VEfkIhSLqo= + +two-sum@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/two-sum/-/two-sum-1.0.0.tgz#31d3f32239e4f731eca9df9155e2b297f008ab64" + integrity sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q= + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -13272,6 +13892,11 @@ unified@^9.0.0: trough "^1.0.0" vfile "^4.0.0" +union-find@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/union-find/-/union-find-1.0.2.tgz#292bac415e6ad3a89535d237010db4a536284e58" + integrity sha1-KSusQV5q06iVNdI3AQ20pTYoTlg= + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -13891,6 +14516,11 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +wgs84@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/wgs84/-/wgs84-0.0.0.tgz#34fdc555917b6e57cf2a282ed043710c049cdc76" + integrity sha1-NP3FVZF7blfPKigu0ENxDASc3HY= + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" From 5fee4ee08aa38bd24af33c856a43b1520e9e1688 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 18 Oct 2021 13:52:57 +0200 Subject: [PATCH 033/109] v1.4.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1a5dd557..08732906 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.4.0", + "version": "1.4.0-beta.1", "main": "index.js", "module": "module.js", "dependencies": { From c27e6d392e84c0173e3e0c27735c32ddf9d19a98 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Oct 2021 11:19:26 +0200 Subject: [PATCH 034/109] Add worker support --- .neutrinorc.js | 24 ++++++++++++++++++++++++ package.json | 3 ++- yarn.lock | 18 +++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.neutrinorc.js b/.neutrinorc.js index 367adfe0..b080b114 100644 --- a/.neutrinorc.js +++ b/.neutrinorc.js @@ -22,6 +22,18 @@ if (process.env.REACT_APP_LIB_MODE) { }, }, use: [ + (neutrino) => { + neutrino.config.output + .globalObject('this') // will prevent `window` + .end() + .module.rule('worker') + .test(neutrino.regexFromExtensions(['worker.js'])) + .use('worker') + .loader(require.resolve('worker-loader')) + .options({ + // See: https://github.com/webpack-contrib/worker-loader#options + }); + }, library({ name: 'mobility-toolbox-js', targets: { @@ -159,6 +171,18 @@ if (process.env.REACT_APP_LIB_MODE) { }, ], }), + (neutrino) => { + neutrino.config.output + .globalObject('this') // will prevent `window` + .end() + .module.rule('worker') + .test(neutrino.regexFromExtensions(['worker.js'])) + .use('worker') + .loader(require.resolve('worker-loader')) + .options({ + // See: https://github.com/webpack-contrib/worker-loader#options + }); + }, ], }; } diff --git a/package.json b/package.json index 08732906..ec3afa9a 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,8 @@ "stylelint-scss": "3.17.2", "webpack": "4.43.0", "webpack-cli": "3.3.11", - "webpack-dev-server": "3.11.0" + "webpack-dev-server": "3.11.0", + "worker-loader": "2.0.0" }, "scripts": { "apidoc": "esdoc", diff --git a/yarn.lock b/yarn.lock index ad58e68d..d3e03912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8679,7 +8679,7 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.0, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -11925,6 +11925,14 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" +schema-utils@^0.4.0: + version "0.4.7" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" + integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -14609,6 +14617,14 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +worker-loader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac" + integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw== + dependencies: + loader-utils "^1.0.0" + schema-utils "^0.4.0" + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" From ee9ca9d8357452c9068d88adefbba0135156f8b6 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Oct 2021 15:35:05 +0200 Subject: [PATCH 035/109] Use OffscreenCanvas to render trajectories --- package.json | 1 + src/api/trajserv/TrajservAPIUtils.js | 4 +- src/common/mixins/TrajservLayerMixin.js | 9 +- src/common/tracker.worker.js | 449 ++++++++++++++++++++++++ src/ol/layers/TrackerLayer.js | 209 ++++++++--- yarn.lock | 2 +- 6 files changed, 624 insertions(+), 50 deletions(-) create mode 100644 src/common/tracker.worker.js diff --git a/package.json b/package.json index ec3afa9a..96086a8a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@turf/transform-rotate": "^6.5.0", "codesandbox": "^2.1.16", "es6-mixin": "^0.3.0", + "json-stringify-safe": "^5.0.1", "path": "^0.12.7", "prop-types": "^15.7.2", "query-string": "^6.13.0", diff --git a/src/api/trajserv/TrajservAPIUtils.js b/src/api/trajserv/TrajservAPIUtils.js index d02b2f01..cdd20849 100644 --- a/src/api/trajserv/TrajservAPIUtils.js +++ b/src/api/trajserv/TrajservAPIUtils.js @@ -1,5 +1,3 @@ -import { LineString } from 'ol/geom'; - /** * Translate the response date object into a readable object. * @returns {array} @@ -132,7 +130,7 @@ export const translateTrajCollResponse = (features = []) => { const trajectories = []; for (let i = 0; i < features.length; i += 1) { const traj = features[i]; - const geometry = new LineString(traj.geometry.coordinates); + const { geometry } = traj; const { ID: id, ProductIdentifier: type, diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index bd6a25f4..a9418b57 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -414,7 +414,14 @@ const TrajservLayerMixin = (TrackerLayer) => // Don't set trajectories when the user has aborted the request. if (trajectories) { this.tracker.setTrajectories(trajectories); - this.renderTrajectories(); + if (this.worker) { + this.worker.postMessage({ + action: 'sendData', + trajectories, + }); + } else { + this.renderTrajectories(); + } } }); } diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js new file mode 100644 index 00000000..b172f0de --- /dev/null +++ b/src/common/tracker.worker.js @@ -0,0 +1,449 @@ +import { Point, LineString } from 'ol/geom'; +import GeomType from 'ol/geom/GeometryType'; +import stringify from 'json-stringify-safe'; +import { compose, apply, create } from 'ol/transform'; +import { + getRadius, + getDelayColor, + getDelayText, + getBgColor, + getTextColor, + getTextSize, +} from './trackerConfig'; + +const debug = true; + +let trajectories = []; +const styleCache = {}; +// const defaultStyle = (traj) => { +// // const { id: text } = traj; +// const text = 'la'; +// if (styleCache[text]) { +// return styleCache[text]; +// } +// const canvas = new OffscreenCanvas(15, 15); +// const ctx = canvas.getContext('2d'); +// ctx.arc(8, 8, 5, 0, 2 * Math.PI, false); +// ctx.fillStyle = '#8ED6FF'; +// ctx.fill(); +// ctx.lineWidth = 3; +// ctx.strokeStyle = 'black'; +// ctx.stroke(); +// // ctx.font = 'bold 12px arial'; +// // ctx.strokeStyle = 'white'; +// // ctx.lineWidth = 3; +// // ctx.strokeText(text, 20, 10); +// // ctx.fillStyle = 'black'; +// // ctx.fillText(text, 20, 10); +// styleCache[text] = canvas; +// return styleCache[text]; +// }; + +const style = ( + trajectory, + zoom, + pixelRatio, + hoverVehicleId, + selectedVehicleId, + delayDisplay, + delayOutlineColor, + useDelayStyle, + operatorProvidesRealtime, +) => { + const { type, name, id, color, textColor, delay, cancelled } = trajectory; + const z = Math.min(Math.floor(zoom || 1), 16); + const hover = hoverVehicleId === id; + const selected = selectedVehicleId === id; + const key = `${z}${type}${name}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + + if (!styleCache[key]) { + let radius = getRadius(type, z) * pixelRatio; + const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio; + + if (radius === 0) { + styleCache[key] = null; + return null; + } + + if (hover || selected) { + radius = isDisplayStrokeAndDelay + ? radius + 5 * pixelRatio + : 14 * pixelRatio; + } + const margin = 1 * pixelRatio; + const radiusDelay = radius + 2; + const markerSize = radius * 2; + + const size = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + const canvas = new OffscreenCanvas(size, size); + const ctx = canvas.getContext('2d'); + const origin = canvas.width / 2; + + if (isDisplayStrokeAndDelay && delay !== null) { + // Draw circle delay background + ctx.save(); + ctx.beginPath(); + ctx.arc(origin, origin, radiusDelay, 0, 2 * Math.PI, false); + ctx.fillStyle = getDelayColor(delay, cancelled); + ctx.filter = 'blur(1px)'; + ctx.fill(); + ctx.restore(); + } + + // Show delay if feature is hovered or if delay is above 5mins. + if ( + isDisplayStrokeAndDelay && + (hover || delay >= delayDisplay || cancelled) + ) { + // Draw delay text + ctx.save(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.font = `bold ${Math.max( + cancelled ? 19 : 14, + Math.min(cancelled ? 19 : 17, radius * 1.2), + )}px arial, sans-serif`; + ctx.fillStyle = getDelayColor(delay, cancelled, true); + + ctx.strokeStyle = delayOutlineColor; + ctx.lineWidth = 1.5 * pixelRatio; + const delayText = getDelayText(delay, cancelled); + ctx.strokeText(delayText, origin + radiusDelay + margin, origin); + ctx.fillText(delayText, origin + radiusDelay + margin, origin); + ctx.restore(); + } + + // Draw colored circle with black border + let circleFillColor; + if (useDelayStyle) { + circleFillColor = getDelayColor(delay, cancelled); + } else { + circleFillColor = color || getBgColor(type); + } + + ctx.save(); + if (isDisplayStrokeAndDelay || hover || selected) { + ctx.lineWidth = 1 * pixelRatio; + ctx.strokeStyle = '#000000'; + } + ctx.fillStyle = circleFillColor; + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fill(); + + // Dashed outline if a provider provides realtime but we don't use it. + if ( + isDisplayStrokeAndDelay && + useDelayStyle && + delay === null && + operatorProvidesRealtime === 'yes' + ) { + ctx.setLineDash([5, 3]); + } + + if (isDisplayStrokeAndDelay || hover || selected) { + ctx.stroke(); + } + + ctx.restore(); + + // Draw text in the circle + if (radius > 10) { + const fontSize = Math.max(radius, 10); + const textSize = getTextSize(ctx, markerSize, name, fontSize); + + // Draw a stroke to the text only if a provider provides realtime but we don't use it. + if ( + useDelayStyle && + delay === null && + operatorProvidesRealtime === 'yes' + ) { + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.font = `bold ${textSize + 2}px Arial`; + ctx.strokeStyle = circleFillColor; + ctx.strokeText(name, origin, origin); + ctx.restore(); + } + + // Draw a text + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.fillStyle = !useDelayStyle + ? textColor || getTextColor(type) + : '#000000'; + ctx.font = `bold ${textSize}px Arial`; + ctx.strokeStyle = circleFillColor; + ctx.strokeText(name, origin, origin); + ctx.fillText(name, origin, origin); + ctx.restore(); + } + + styleCache[key] = canvas; + } + + return styleCache[key]; +}; + +// eslint-disable-next-line no-restricted-globals +self.onmessage = function (evt) { + // debugger; + if (evt.data.action === 'sendData') { + // eslint-disable-next-line no-console + if (debug) console.log('sendData', evt.data); + trajectories = evt.data.trajectories; + return; + } + + if (evt.data.action !== 'render') { + return; + } + // eslint-disable-next-line no-console + if (debug) console.time('render'); + // eslint-disable-next-line no-console + if (debug) console.log('render', evt.data.frameState); + + let nbRenderedTrajectories = 0; + const { frameState } = evt.data; + const { + time = Date.now(), + size = [], + center, + resolution, + zoom, + rotation, + pixelRatio, + interpolate = true, + iconScale, + hoverVehicleId, + selectedVehicleId, + delayDisplay, + delayOutlineColor, + useDelayStyle, + } = evt.data; + + const canvas = new OffscreenCanvas(size[0], size[1]); + const canvasContext = canvas.getContext('2d'); + const [width, height] = size; + if (width && height && (canvas.width !== width || canvas.height !== height)) { + [canvas.width, canvas.height] = [width, height]; + } + + const coordinateToPixelTransform = compose( + create(), + size[0] / 2, + size[1] / 2, + 1 / resolution, + -1 / resolution, + -rotation, + -center[0], + -center[1], + ); + + let hoverVehicleImg; + let hoverVehiclePx; + let hoverVehicleWidth; + let hoverVehicleHeight; + let selectedVehicleImg; + let selectedVehiclePx; + let selectedVehicleWidth; + let selectedVehicleHeight; + + for (let i = (trajectories || []).length - 1; i >= 0; i -= 1) { + const traj = trajectories[i]; + + // We simplify the traj object + let { geometry } = traj; + const { coordinate, timeIntervals, timeOffset } = traj; + + if (Array.isArray(geometry.coordinates)) { + if (geometry.type === 'Point') { + geometry = new Point(geometry.coordinates); + } else if (geometry.type === 'LineString') { + geometry = new LineString(geometry.coordinates); + } + } + + // Filter should apply when we request the data + // if (filter && !filter(traj, i, trajectories)) { + // // eslint-disable-next-line no-continue + // continue; + // } + + let coord = null; + let rotationIcon; + + if (coordinate && !interpolate) { + coord = coordinate; + } else if (timeIntervals && timeIntervals.length > 1) { + const now = time - (timeOffset || 0); + let start; + let end; + let startFrac; + let endFrac; + let timeFrac; + + // Search th time interval. + for (let j = 0; j < timeIntervals.length - 1; j += 1) { + // Rotation only available in tralis layer. + [start, startFrac, rotationIcon] = timeIntervals[j]; + [end, endFrac] = timeIntervals[j + 1]; + + if (start <= now && now <= end) { + break; + } else { + start = null; + end = null; + } + } + // The geometry can also be a Point + if (geometry.getType() === GeomType.POINT) { + coord = geometry.getCoordinates(); + } else if (geometry.getType() === GeomType.LINE_STRING) { + if (start && end) { + // interpolate position inside the time interval. + timeFrac = interpolate + ? Math.min((now - start) / (end - start), 1) + : 0; + + const geomFrac = interpolate + ? timeFrac * (endFrac - startFrac) + startFrac + : 0; + + coord = geometry.getCoordinateAt(geomFrac); + + // We set the rotation and the timeFraction of the trajectory (used by tralis). + trajectories[i].rotation = rotation; + trajectories[i].endFraction = timeFrac; + + // It happens that the now date was some ms before the first timeIntervals we have. + } else if (now < timeIntervals[0][0]) { + [[, , rotationIcon]] = timeIntervals; + timeFrac = 0; + coord = geometry.getFirstCoordinate(); + } else if (now > timeIntervals[timeIntervals.length - 1][0]) { + [, , rotationIcon] = timeIntervals[timeIntervals.length - 1]; + timeFrac = 1; + coord = geometry.getLastCoordinate(); + } + } else { + // eslint-disable-next-line no-console + console.error( + 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', + geometry, + ); + } + // We set the rotation and the timeFraction of the trajectory (used by tralis). + // if rotation === null that seems there is no rotation available. + trajectories[i].rotation = rotationIcon; + trajectories[i].endFraction = timeFrac || 0; + } + + if (coord) { + // We set the rotation of the trajectory (used by tralis). + trajectories[i].coordinate = coord; + + let px = apply(coordinateToPixelTransform, [...coord]); // [...toLonLat(coord)]); + + if (!px) { + // eslint-disable-next-line no-continue + continue; + } + nbRenderedTrajectories += 1; + + px = px.map((p) => { + return p * pixelRatio; + }); + + const vehicleImg = style( + traj, + zoom, + pixelRatio, + hoverVehicleId, + selectedVehicleId, + delayDisplay, + delayOutlineColor, + useDelayStyle, + ); + + if (!vehicleImg) { + // eslint-disable-next-line no-continue + continue; + } + + let imgWidth = vehicleImg.width; + let imgHeight = vehicleImg.height; + + if (iconScale) { + imgHeight = Math.floor(imgHeight * iconScale); + imgWidth = Math.floor(imgWidth * iconScale); + } + + if (hoverVehicleId !== traj.id && selectedVehicleId !== traj.id) { + canvasContext.drawImage( + vehicleImg, + px[0] - imgWidth / 2, + px[1] - imgHeight / 2, + imgWidth, + imgHeight, + ); + } + if (hoverVehicleId === traj.id) { + // Store the canvas to draw it at the end + hoverVehicleImg = vehicleImg; + hoverVehiclePx = px; + hoverVehicleWidth = imgWidth; + hoverVehicleHeight = imgHeight; + } + + if (selectedVehicleId === traj.id) { + // Store the canvas to draw it at the end + selectedVehicleImg = vehicleImg; + selectedVehiclePx = px; + selectedVehicleWidth = imgWidth; + selectedVehicleHeight = imgHeight; + } + } + } + + if (selectedVehicleImg) { + canvasContext.drawImage( + selectedVehicleImg, + selectedVehiclePx[0] - selectedVehicleWidth / 2, + selectedVehiclePx[1] - selectedVehicleHeight / 2, + selectedVehicleWidth, + selectedVehicleHeight, + ); + } + + if (hoverVehicleImg) { + canvasContext.drawImage( + hoverVehicleImg, + hoverVehiclePx[0] - hoverVehicleWidth / 2, + hoverVehiclePx[1] - hoverVehicleHeight / 2, + hoverVehicleWidth, + hoverVehicleHeight, + ); + } + + if (debug) console.timeEnd('render'); + + const imageData = canvas.transferToImageBitmap(); + const state = { ...frameState }; + delete state.layerStatesArray; + delete state.viewState.projection; + + // eslint-disable-next-line no-restricted-globals + self.postMessage( + { + action: 'rendered', + imageData, + // transform: rendererTransform, + nbRenderedTrajectories, + frameState: JSON.parse(stringify(state)), + }, + [imageData], + ); +}; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index d27ca750..3e02fbe0 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -2,10 +2,39 @@ import { Layer as OLLayer, Group } from 'ol/layer'; import { unByKey } from 'ol/Observable'; import Source from 'ol/source/Source'; import { composeCssTransform } from 'ol/transform'; +import stringify from 'json-stringify-safe'; import mixin from '../../common/mixins/TrackerLayerMixin'; import Layer from './Layer'; +import Worker from '../../common/tracker.worker'; + +const updateContainerTransform = (layer) => { + if (layer.workerFrameState) { + const { + center, + resolution, + rotation, + } = layer.mainThreadFrameState.viewState; + const { + center: renderedCenter, + resolution: renderedResolution, + rotation: renderedRotation, + } = layer.workerFrameState.viewState; + + // eslint-disable-next-line no-param-reassign + layer.transformContainer.style.transform = composeCssTransform( + (renderedCenter[0] - center[0]) / resolution, + (center[1] - renderedCenter[1]) / resolution, + renderedResolution / resolution, + renderedResolution / resolution, + rotation - renderedRotation, + 0, + 0, + ); + } +}; + /** * Responsible for loading tracker data. * @@ -26,6 +55,31 @@ class TrackerLayer extends mixin(Layer) { super({ ...options, }); + const that = this; + + // Worker that render trajectories. + this.worker = new Worker(); + // Worker messaging and actions + this.worker.addEventListener('message', (message) => { + if (message.data.action === 'requestRender') { + // Worker requested a new render frame + that.map.render(); + } else if (that.canvas && message.data.action === 'rendered') { + // Worker provies a new render frame + console.log('icic'); + + requestAnimationFrame(() => { + const { imageData } = message.data; + that.canvas.width = imageData.width; + that.canvas.height = imageData.height; + that.canvas.getContext('2d').drawImage(imageData, 0, 0); + // this.canvas.style.transform = message.data.transform; + that.workerFrameState = message.data.frameState; + updateContainerTransform(that); + }); + that.rendering = false; + } + }); /** * Boolean that defines if the layer is allow to renderTrajectories when the map is zooming, rotating or poanning true. @@ -54,42 +108,45 @@ class TrackerLayer extends mixin(Layer) { this.transformContainer.style.width = '100%'; this.transformContainer.style.height = '100%'; this.container.appendChild(this.transformContainer); - this.tracker.canvas.style.position = 'absolute'; - this.tracker.canvas.style.top = '0'; - this.tracker.canvas.style.left = '0'; - this.tracker.canvas.style.transformOrigin = 'top left'; - this.transformContainer.appendChild(this.tracker.canvas); + this.canvas = document.createElement('canvas'); + this.canvas.style.position = 'absolute'; + this.canvas.style.left = '0'; + this.canvas.style.transformOrigin = 'top left'; + this.transformContainer.appendChild(this.canvas); } + this.mainThreadFrameState = frameState; - if (this.renderedViewState) { - const { center, resolution, rotation } = frameState.viewState; + if (this.workerFrameState) { + const { resolution } = this.mainThreadFrameState.viewState; const { - center: renderedCenter, resolution: renderedResolution, - rotation: renderedRotation, - } = this.renderedViewState; + } = this.workerFrameState.viewState; + + updateContainerTransform(this); + + // Avoid having really big points when zooming fast. + if (this.canvas && renderedResolution / resolution >= 3) { + const context = this.canvas.getContext('2d'); + context.clearRect( + 0, + 0, + this.canvas.width, + this.canvas.height, + ); + } if ( + !this.rendering && this.renderWhenInteracting && this.renderWhenInteracting( - frameState.viewState, - this.renderedViewState, + this.mainThreadFrameState, + this.workerFrameState, ) ) { - this.renderTrajectories(true); - } else if (renderedResolution / resolution >= 3) { - // Avoid having really big points when zooming fast. - this.tracker.clear(); - } else { - this.transformContainer.style.transform = composeCssTransform( - (renderedCenter[0] - center[0]) / resolution, - (center[1] - renderedCenter[1]) / resolution, - renderedResolution / resolution, - renderedResolution / resolution, - rotation - renderedRotation, - 0, - 0, - ); + this.renderTrajectories(false); + } else if (this.rendering) { + // eslint-disable-next-line no-param-reassign + frameState.animate = true; } } return this.container; @@ -135,24 +192,24 @@ class TrackerLayer extends mixin(Layer) { this.startUpdateTime(z); } }), - this.map.on('pointermove', (evt) => { - if ( - this.map.getView().getInteracting() || - this.map.getView().getAnimating() || - !this.isHoverActive - ) { - return; - } - const [vehicle] = this.getVehiclesAtCoordinate(evt.coordinate, 1); - const id = vehicle && vehicle.id; - if (this.hoverVehicleId !== id) { - this.map.getTargetElement().style.cursor = vehicle - ? 'pointer' - : 'auto'; - this.hoverVehicleId = id; - this.renderTrajectories(); - } - }), + // this.map.on('pointermove', (evt) => { + // if ( + // this.map.getView().getInteracting() || + // this.map.getView().getAnimating() || + // !this.isHoverActive + // ) { + // return; + // } + // const [vehicle] = this.getVehiclesAtCoordinate(evt.coordinate, 1); + // const id = vehicle && vehicle.id; + // if (this.hoverVehicleId !== id) { + // this.map.getTargetElement().style.cursor = vehicle + // ? 'pointer' + // : 'auto'; + // this.hoverVehicleId = id; + // this.renderTrajectories(); + // } + // }), ]; } @@ -175,7 +232,7 @@ class TrackerLayer extends mixin(Layer) { const view = this.map.getView(); super.renderTrajectories( this.map.getSize(), - this.map.getView().getCenter(), + view.getCenter(), view.calculateExtent(), view.getResolution(), view.getRotation(), @@ -195,6 +252,67 @@ class TrackerLayer extends mixin(Layer) { rotation, noInterpolate, ) { + if (this.worker) { + if (!this.tracker) { + return false; + } + + const renderTime = this.live ? Date.now() : this.time; + + // Avoid useless render before the next tick. + if ( + this.live && + resolution === this.lastRenderResolution && + rotation === this.lastRenderRotation && + renderTime - this.lastRenderTime < this.updateTimeDelay + ) { + return false; + } + + this.lastRenderTime = renderTime; + this.lastRenderResolution = resolution; + this.lastRenderRotation = rotation; + + if (this.mainThreadFrameState && this.mainThreadFrameState) { + this.rendering = true; + const frameState = { ...this.mainThreadFrameState }; + delete frameState.layerStatesArray; + delete frameState.viewState.projection; + const { + // center, resolution, rotation, + zoom, + } = frameState.viewState; + + if ( + this.map.getView().getInteracting() || + this.map.getView().getAnimating() + ) { + return false; + } + + this.worker.postMessage({ + action: 'render', + time: renderTime, + size, + center, + resolution, + extent, + zoom, + rotation, + pixelRatio: this.pixelRatio, + interpolate: !noInterpolate, + iconScale: this.iconScale, + hoverVehicleId: this.hoverVehicleId, + selectedVehicleId: this.selectedVehicleId, + delayDisplay: this.delayDisplay, + delayOutlineColor: this.delayOutlineColor, + useDelayStyle: this.useDelayStyle, + frameState: JSON.parse(stringify(frameState)), + }); + } + return true; + } + let isRendered = false; isRendered = super.renderTrajectoriesInternal( @@ -220,6 +338,7 @@ class TrackerLayer extends mixin(Layer) { this.transformContainer.style.transform = ''; } } + return isRendered; } /** diff --git a/yarn.lock b/yarn.lock index d3e03912..120c7d69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8269,7 +8269,7 @@ json-stringify-pretty-compact@^2.0.0: resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz#e77c419f52ff00c45a31f07f4c820c2433143885" integrity sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ== -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= From 771cfa8e22e563240111d5260ddcf33bb2fb5c86 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 21 Oct 2021 15:39:28 +0200 Subject: [PATCH 036/109] Fetch trajectories in a worker --- src/api/trajserv/TrajservAPI.js | 24 ++++++++++ src/api/trajserv/fetchTrajectories.worker.js | 23 ++++++++++ src/common/mixins/TrajservLayerMixin.js | 46 +++++++++++++++----- src/common/tracker.worker.js | 2 +- src/ol/layers/TrackerLayer.js | 2 - 5 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 src/api/trajserv/fetchTrajectories.worker.js diff --git a/src/api/trajserv/TrajservAPI.js b/src/api/trajserv/TrajservAPI.js index 93778436..336211d0 100644 --- a/src/api/trajserv/TrajservAPI.js +++ b/src/api/trajserv/TrajservAPI.js @@ -1,8 +1,10 @@ +import qs from 'query-string'; import { translateTrajCollResponse, translateTrajStationsResp, } from './TrajservAPIUtils'; import API from '../../common/api/api'; +import FetchTrajectoriesWorker from './fetchTrajectories.worker'; /** * Access to the [Realtime service](https://developer.geops.io/apis/5dcbd5c9a256d90001cf1360/). @@ -26,6 +28,7 @@ class TrajservAPI extends API { */ constructor(options = {}) { super({ url: 'https://api.geops.io/tracker/v1', ...options }); + this.fetchTrajectoriesWorker = new FetchTrajectoriesWorker(); } /** @@ -41,6 +44,27 @@ class TrajservAPI extends API { }); } + /** + * Fetch trajectories using the worker. + * + * @param {GetTrajectoriesParams} params Request parameters. See [Realtime service documentation](https://developer.geops.io/apis/5dcbd5c9a256d90001cf1360/#/default/get_trajectory_collection). + * @param {AbortController} abortController Abort controller used to cancel the request. + // * @returns {Promise} A list of trajectories. + */ + // eslint-disable-next-line class-methods-use-this + fetchTrajectoriesWorkerr(params) { + // Clean requets parameters, removing undefined and null values. + const urlParams = { ...params, key: this.apiKey }; + const clone = { ...urlParams }; + Object.keys(urlParams).forEach( + (key) => + (clone[key] === undefined || clone[key] === null) && delete clone[key], + ); + this.fetchTrajectoriesWorker.postMessage( + `${this.url}/trajectory_collection?${qs.stringify(clone)}`, + ); + } + /** * Fetch trajectories. * diff --git a/src/api/trajserv/fetchTrajectories.worker.js b/src/api/trajserv/fetchTrajectories.worker.js new file mode 100644 index 00000000..4c508f2c --- /dev/null +++ b/src/api/trajserv/fetchTrajectories.worker.js @@ -0,0 +1,23 @@ +import { translateTrajCollResponse } from './TrajservAPIUtils'; + +let abortController = new AbortController(); + +// eslint-disable-next-line no-restricted-globals, func-names +self.onmessage = function (evt) { + // console.log('Worker: Message received from main script', evt.data); + abortController.abort(); + abortController = new AbortController(); + fetch(evt.data, { + signal: abortController.signal, + }) + .then((res) => res.json()) + .then((data) => { + const a = translateTrajCollResponse(data.features); + // eslint-disable-next-line no-restricted-globals + self.postMessage(a); + }) + .catch(() => { + // eslint-disable-next-line no-restricted-globals + self.postMessage(null); + }); +}; diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index a9418b57..8199c426 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -177,6 +177,19 @@ const createFilters = (line, route, operator, regexLine) => { */ const TrajservLayerMixin = (TrackerLayer) => class extends TrackerLayer { + constructor(options) { + super(options); + + if (this.api.fetchTrajectoriesWorkerr) { + const that = this; + this.api.fetchTrajectoriesWorker.onmessage = (evt) => { + if (evt.data) { + that.onReceiveTrajectories(evt.data); + } + }; + } + } + /** * Define layer's properties. * @@ -396,6 +409,11 @@ const TrajservLayerMixin = (TrackerLayer) => this.abortFetchTrajectories(); this.abortController = new AbortController(); + if (this.api.fetchTrajectoriesWorkerr) { + this.api.fetchTrajectoriesWorkerr(this.getParams({ attr_det: 1 })); + return; + } + this.api .fetchTrajectories( this.getParams({ @@ -411,21 +429,25 @@ const TrajservLayerMixin = (TrackerLayer) => throw err; }) .then((trajectories) => { - // Don't set trajectories when the user has aborted the request. - if (trajectories) { - this.tracker.setTrajectories(trajectories); - if (this.worker) { - this.worker.postMessage({ - action: 'sendData', - trajectories, - }); - } else { - this.renderTrajectories(); - } - } + this.onReceiveTrajectories(trajectories); }); } + onReceiveTrajectories(trajectories) { + // Don't set trajectories when the user has aborted the request. + if (trajectories) { + this.tracker.setTrajectories(trajectories); + if (this.worker) { + this.worker.postMessage({ + action: 'sendData', + trajectories, + }); + } else { + this.renderTrajectories(); + } + } + } + defaultStyle(props, zoom) { const { type, diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index b172f0de..600e70e6 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -11,7 +11,7 @@ import { getTextSize, } from './trackerConfig'; -const debug = true; +const debug = false; let trajectories = []; const styleCache = {}; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 3e02fbe0..9de2a800 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -66,8 +66,6 @@ class TrackerLayer extends mixin(Layer) { that.map.render(); } else if (that.canvas && message.data.action === 'rendered') { // Worker provies a new render frame - console.log('icic'); - requestAnimationFrame(() => { const { imageData } = message.data; that.canvas.width = imageData.width; From a211560a732b7a20336e6d4429de7bde507a108b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Oct 2021 16:24:43 +0200 Subject: [PATCH 037/109] Don't send message on aborted request --- src/api/trajserv/fetchTrajectories.worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/trajserv/fetchTrajectories.worker.js b/src/api/trajserv/fetchTrajectories.worker.js index 4c508f2c..edbfe6da 100644 --- a/src/api/trajserv/fetchTrajectories.worker.js +++ b/src/api/trajserv/fetchTrajectories.worker.js @@ -18,6 +18,6 @@ self.onmessage = function (evt) { }) .catch(() => { // eslint-disable-next-line no-restricted-globals - self.postMessage(null); + // self.postMessage(null); }); }; From 5b42f02fe229958ebc4c385a3975ec4f9b171fd5 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Oct 2021 16:25:58 +0200 Subject: [PATCH 038/109] Abort render when the function is too slow --- src/common/tracker.worker.js | 214 +++++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 88 deletions(-) diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index 600e70e6..322b8d52 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -2,6 +2,12 @@ import { Point, LineString } from 'ol/geom'; import GeomType from 'ol/geom/GeometryType'; import stringify from 'json-stringify-safe'; import { compose, apply, create } from 'ol/transform'; +import { + getDelayBgCanvas, + getDelayTextCanvas, + getCircleCanvas, + getTextCanvas, +} from './trackerStyleConfig'; import { getRadius, getDelayColor, @@ -11,7 +17,7 @@ import { getTextSize, } from './trackerConfig'; -const debug = false; +const debug = true; let trajectories = []; const styleCache = {}; @@ -38,7 +44,7 @@ const styleCache = {}; // styleCache[text] = canvas; // return styleCache[text]; // }; - +let count = 0; const style = ( trajectory, zoom, @@ -54,10 +60,17 @@ const style = ( const z = Math.min(Math.floor(zoom || 1), 16); const hover = hoverVehicleId === id; const selected = selectedVehicleId === id; - const key = `${z}${type}${name}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + let key = `${z}${type}${name}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + + let radius = getRadius(type, z) * pixelRatio; + const mustDrawText = radius > 10 * pixelRatio; + + if (!mustDrawText) { + key = `${z}${type}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + } if (!styleCache[key]) { - let radius = getRadius(type, z) * pixelRatio; + count += 1; const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio; if (radius === 0) { @@ -73,21 +86,21 @@ const style = ( const margin = 1 * pixelRatio; const radiusDelay = radius + 2; const markerSize = radius * 2; - - const size = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + const textWidth = 100; + const size = (radiusDelay * 2 + margin * 2 + textWidth) * pixelRatio; const canvas = new OffscreenCanvas(size, size); + // console.log('size: ', size); const ctx = canvas.getContext('2d'); const origin = canvas.width / 2; if (isDisplayStrokeAndDelay && delay !== null) { // Draw circle delay background - ctx.save(); - ctx.beginPath(); - ctx.arc(origin, origin, radiusDelay, 0, 2 * Math.PI, false); - ctx.fillStyle = getDelayColor(delay, cancelled); - ctx.filter = 'blur(1px)'; - ctx.fill(); - ctx.restore(); + const delayBg = getDelayBgCanvas( + origin, + radiusDelay, + getDelayColor(delay, cancelled), + ); + ctx.drawImage(delayBg, 0, 0); } // Show delay if feature is hovered or if delay is above 5mins. @@ -96,21 +109,24 @@ const style = ( (hover || delay >= delayDisplay || cancelled) ) { // Draw delay text - ctx.save(); - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - ctx.font = `bold ${Math.max( + const fontSize = Math.max( cancelled ? 19 : 14, Math.min(cancelled ? 19 : 17, radius * 1.2), - )}px arial, sans-serif`; - ctx.fillStyle = getDelayColor(delay, cancelled, true); - - ctx.strokeStyle = delayOutlineColor; - ctx.lineWidth = 1.5 * pixelRatio; - const delayText = getDelayText(delay, cancelled); - ctx.strokeText(delayText, origin + radiusDelay + margin, origin); - ctx.fillText(delayText, origin + radiusDelay + margin, origin); - ctx.restore(); + ); + const delayText = getDelayTextCanvas( + textWidth, + getDelayText(delay, cancelled), + fontSize, + `bold ${fontSize}px arial, sans-serif`, + getDelayColor(delay, cancelled, true), + delayOutlineColor, + pixelRatio, + ); + ctx.drawImage( + delayText, + origin + radiusDelay + margin, + origin - fontSize, + ); } // Draw colored circle with black border @@ -121,64 +137,71 @@ const style = ( circleFillColor = color || getBgColor(type); } - ctx.save(); - if (isDisplayStrokeAndDelay || hover || selected) { - ctx.lineWidth = 1 * pixelRatio; - ctx.strokeStyle = '#000000'; - } - ctx.fillStyle = circleFillColor; - ctx.beginPath(); - ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); - ctx.fill(); + const hasStroke = isDisplayStrokeAndDelay || hover || selected; - // Dashed outline if a provider provides realtime but we don't use it. - if ( + const hasDash = isDisplayStrokeAndDelay && useDelayStyle && delay === null && - operatorProvidesRealtime === 'yes' - ) { - ctx.setLineDash([5, 3]); - } - - if (isDisplayStrokeAndDelay || hover || selected) { - ctx.stroke(); - } + operatorProvidesRealtime === 'yes'; + + const circle = getCircleCanvas( + origin, + radius, + circleFillColor, + hasStroke, + hasDash, + pixelRatio, + ); - ctx.restore(); + ctx.drawImage(circle, 0, 0); // Draw text in the circle - if (radius > 10) { + if (mustDrawText) { const fontSize = Math.max(radius, 10); const textSize = getTextSize(ctx, markerSize, name, fontSize); - - // Draw a stroke to the text only if a provider provides realtime but we don't use it. - if ( - useDelayStyle && - delay === null && - operatorProvidesRealtime === 'yes' - ) { - ctx.save(); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.font = `bold ${textSize + 2}px Arial`; - ctx.strokeStyle = circleFillColor; - ctx.strokeText(name, origin, origin); - ctx.restore(); - } - - // Draw a text - ctx.save(); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.fillStyle = !useDelayStyle + const textColor2 = !useDelayStyle ? textColor || getTextColor(type) : '#000000'; - ctx.font = `bold ${textSize}px Arial`; - ctx.strokeStyle = circleFillColor; - ctx.strokeText(name, origin, origin); - ctx.fillText(name, origin, origin); - ctx.restore(); + const hasStroke2 = + useDelayStyle && delay === null && operatorProvidesRealtime === 'yes'; + + const text = getTextCanvas( + name, + origin, + textSize, + textColor2, + circleFillColor, + hasStroke2, + pixelRatio, + ); + + ctx.drawImage(text, 0, 0); + // Draw a stroke to the text only if a provider provides realtime but we don't use it. + // if ( + // useDelayStyle && + // delay === null && + // operatorProvidesRealtime === 'yes' + // ) { + // ctx.save(); + // ctx.textBaseline = 'middle'; + // ctx.textAlign = 'center'; + // ctx.font = `bold ${textSize + 2}px Arial`; + // ctx.strokeStyle = circleFillColor; + // ctx.strokeText(name, origin, origin); + // ctx.restore(); + // } + + // // Draw a text + // ctx.save(); + // ctx.textBaseline = 'middle'; + // ctx.textAlign = 'center'; + // ctx.fillStyle = ; + // ctx.font = `bold ${textSize}px Arial`; + // ctx.strokeStyle = circleFillColor; + // ctx.strokeText(name, origin, origin); + // ctx.fillText(name, origin, origin); + // ctx.restore(); } styleCache[key] = canvas; @@ -187,24 +210,13 @@ const style = ( return styleCache[key]; }; -// eslint-disable-next-line no-restricted-globals -self.onmessage = function (evt) { - // debugger; - if (evt.data.action === 'sendData') { - // eslint-disable-next-line no-console - if (debug) console.log('sendData', evt.data); - trajectories = evt.data.trajectories; - return; - } - - if (evt.data.action !== 'render') { - return; - } +let renderTimeout; +const render = (evt) => { // eslint-disable-next-line no-console if (debug) console.time('render'); // eslint-disable-next-line no-console if (debug) console.log('render', evt.data.frameState); - + count = 0; let nbRenderedTrajectories = 0; const { frameState } = evt.data; const { @@ -427,8 +439,8 @@ self.onmessage = function (evt) { hoverVehicleHeight, ); } - if (debug) console.timeEnd('render'); + if (debug) console.log('NUMBER OF STYLES CREATED', count); const imageData = canvas.transferToImageBitmap(); const state = { ...frameState }; @@ -446,4 +458,30 @@ self.onmessage = function (evt) { }, [imageData], ); + renderTimeout = null; +}; + +// eslint-disable-next-line no-restricted-globals +self.onmessage = function (evt) { + // debugger; + if (evt.data.action === 'sendData') { + // eslint-disable-next-line no-console + if (debug) console.log('sendData', evt.data); + if (debug) console.time('sendData'); + trajectories = evt.data.trajectories; + if (debug) console.timeEnd('sendData'); + return; + } + + if (evt.data.action !== 'render') { + return; + } + + if (renderTimeout) { + clearTimeout(renderTimeout); + } + + renderTimeout = setTimeout(() => { + render(evt); + }, 0); }; From e62df4817a94656b8e481233824400d2c8c74af7 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Oct 2021 16:27:38 +0200 Subject: [PATCH 039/109] Optimize time between request and apply of the render result --- src/common/mixins/TrajservLayerMixin.js | 5 +- src/common/tracker.worker.js | 2 +- src/common/trackerStyleConfig.js | 134 ++++++++++++++++++++++++ src/doc/examples/ol-tracker.js | 4 +- src/ol/layers/TrackerLayer.js | 60 ++++++++--- 5 files changed, 186 insertions(+), 19 deletions(-) create mode 100644 src/common/trackerStyleConfig.js diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index 8199c426..adea226f 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -437,14 +437,15 @@ const TrajservLayerMixin = (TrackerLayer) => // Don't set trajectories when the user has aborted the request. if (trajectories) { this.tracker.setTrajectories(trajectories); + if (this.worker) { this.worker.postMessage({ action: 'sendData', trajectories, }); - } else { - this.renderTrajectories(); } + + this.renderTrajectories(); } } diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index 322b8d52..64804548 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -17,7 +17,7 @@ import { getTextSize, } from './trackerConfig'; -const debug = true; +const debug = false; let trajectories = []; const styleCache = {}; diff --git a/src/common/trackerStyleConfig.js b/src/common/trackerStyleConfig.js new file mode 100644 index 00000000..1424305a --- /dev/null +++ b/src/common/trackerStyleConfig.js @@ -0,0 +1,134 @@ +const cacheDelayBg = {}; + +// Draw circle delay background +export const getDelayBgCanvas = (origin, radius, color) => { + const key = `${origin}, ${radius}, ${color}`; + if (!cacheDelayBg[key]) { + // console.log('cacheDelayBg'); + const canvas = new OffscreenCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fillStyle = color; + ctx.filter = 'blur(1px)'; + ctx.fill(); + cacheDelayBg[key] = canvas; + } + return cacheDelayBg[key]; +}; + +// Draw delay text +const cacheDelayText = {}; +export const getDelayTextCanvas = ( + width, + text, + fontSize, + font, + delayColor, + delayOutlineColor, + pixelRatio, +) => { + const key = `${width}, ${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`; + if (!cacheDelayText[key]) { + const canvas = new OffscreenCanvas(width, fontSize + 8); + const ctx = canvas.getContext('2d'); + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.font = font; + ctx.fillStyle = delayColor; + ctx.strokeStyle = delayOutlineColor; + ctx.lineWidth = 1.5 * pixelRatio; + const delayText = text; + ctx.strokeText(delayText, 0, fontSize); + ctx.fillText(delayText, 0, fontSize); + cacheDelayText[key] = canvas; + } + return cacheDelayText[key]; +}; + +// Draw colored circle with black border +const cacheCircle = {}; +export const getCircleCanvas = ( + origin, + radius, + color, + hasStroke, + hasDash, + pixelRatio, +) => { + const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`; + if (!cacheCircle[key]) { + // console.log('cacheDelayBg'); + const canvas = new OffscreenCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + ctx.fillStyle = color; + + if (hasStroke) { + ctx.lineWidth = 1 * pixelRatio; + ctx.strokeStyle = '#000000'; + } + + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fill(); + + if (hasDash) { + ctx.setLineDash([5, 3]); + } + + if (hasStroke) { + ctx.stroke(); + } + + cacheCircle[key] = canvas; + } + return cacheCircle[key]; +}; + +// Draw text in the circle +const cacheText = {}; +export const getTextCanvas = ( + text, + origin, + textSize, + fillColor, + strokeColor, + hasStroke, + pixelRatio, +) => { + const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`; + if (!cacheText[key]) { + const canvas = new OffscreenCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + + // Draw a stroke to the text only if a provider provides realtime but we don't use it. + if (hasStroke) { + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.font = `bold ${textSize + 2}px Arial`; + ctx.strokeStyle = strokeColor; + ctx.strokeText(text, origin, origin); + ctx.restore(); + } + + // Draw a text + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.fillStyle = fillColor; + ctx.font = `bold ${textSize}px Arial`; + ctx.strokeStyle = strokeColor; + ctx.strokeText(text, origin, origin); + ctx.fillText(text, origin, origin); + + cacheText[key] = canvas; + } + return cacheText[key]; +}; + +export default { + getDelayBgCanvas, + getDelayTextCanvas, + getCircleCanvas, + getTextCanvas, +}; diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 1b27631e..0ccb342c 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -7,8 +7,8 @@ export default () => { const map = new Map({ target: 'map', view: new View({ - center: [831634, 5933959], - zoom: 13, + center: [955156.2377088447, 6096616.996190854], + zoom: 7, }), controls: [new CopyrightControl()], }); diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 9de2a800..f49c5c8d 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -55,18 +55,31 @@ class TrackerLayer extends mixin(Layer) { super({ ...options, }); + this.firstRender = true; const that = this; // Worker that render trajectories. this.worker = new Worker(); // Worker messaging and actions - this.worker.addEventListener('message', (message) => { + this.worker.onmessage = (message) => { if (message.data.action === 'requestRender') { // Worker requested a new render frame that.map.render(); } else if (that.canvas && message.data.action === 'rendered') { + if ( + that.map.getView().getInteracting() || + that.map.getView().getAnimating() + ) { + return; + } // Worker provies a new render frame requestAnimationFrame(() => { + if ( + that.map.getView().getInteracting() || + that.map.getView().getAnimating() + ) { + return; + } const { imageData } = message.data; that.canvas.width = imageData.width; that.canvas.height = imageData.height; @@ -77,7 +90,7 @@ class TrackerLayer extends mixin(Layer) { }); that.rendering = false; } - }); + }; /** * Boolean that defines if the layer is allow to renderTrajectories when the map is zooming, rotating or poanning true. @@ -187,7 +200,10 @@ class TrackerLayer extends mixin(Layer) { * @type {number} */ this.currentZoom = z; - this.startUpdateTime(z); + + // This will restart the timeouts. + // TODO maybe find a caluclation a bit less approximative one. + this.requestIntervalSeconds = 200 / z || 1000; } }), // this.map.on('pointermove', (evt) => { @@ -257,21 +273,37 @@ class TrackerLayer extends mixin(Layer) { const renderTime = this.live ? Date.now() : this.time; - // Avoid useless render before the next tick. if ( - this.live && - resolution === this.lastRenderResolution && - rotation === this.lastRenderRotation && - renderTime - this.lastRenderTime < this.updateTimeDelay + this.map.getView().getAnimating() || + this.map.getView().getInteracting() ) { return false; } - - this.lastRenderTime = renderTime; - this.lastRenderResolution = resolution; - this.lastRenderRotation = rotation; - - if (this.mainThreadFrameState && this.mainThreadFrameState) { + // // Avoid useless render before the next tick. + // if ( + // this.live + // // center[0] === (this.lastRenderCenter || [])[0] && + // // center[1] === (this.lastRenderCenter || [])[1] && + // // resolution === this.lastRenderResolution && + // // rotation === this.lastRenderRotation + // // !this.isFirstRender && + // // renderTime - this.lastRenderTime < 150 + // ) { + // console.log('la', this.updateTimeDelay); + // return false; + // } + + // if (this.firstRender && this.tracker.trajectories.length > 0) { + // console.log('LA', this.mainThreadFrameState); + // this.isFirstRender = false; + // } + + // this.lastRenderCenter = center; + // this.lastRenderTime = renderTime; + // this.lastRenderResolution = resolution; + // this.lastRenderRotation = rotation; + + if (this.mainThreadFrameState) { this.rendering = true; const frameState = { ...this.mainThreadFrameState }; delete frameState.layerStatesArray; From 8ff9af8fb34fc5f441a8c5c6ff6c13d13bf31815 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Oct 2021 16:47:18 +0200 Subject: [PATCH 040/109] Manage properly the pixel ratio --- src/common/tracker.worker.js | 10 +++++----- src/ol/layers/TrackerLayer.js | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index 64804548..863080ca 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -236,12 +236,12 @@ const render = (evt) => { useDelayStyle, } = evt.data; - const canvas = new OffscreenCanvas(size[0], size[1]); + const canvas = new OffscreenCanvas( + size[0] * pixelRatio, + size[1] * pixelRatio, + ); + const canvasContext = canvas.getContext('2d'); - const [width, height] = size; - if (width && height && (canvas.width !== width || canvas.height !== height)) { - [canvas.width, canvas.height] = [width, height]; - } const coordinateToPixelTransform = compose( create(), diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index f49c5c8d..e6877a4d 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -83,6 +83,11 @@ class TrackerLayer extends mixin(Layer) { const { imageData } = message.data; that.canvas.width = imageData.width; that.canvas.height = imageData.height; + this.canvas.style.transform = ``; + this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; + this.canvas.style.height = `${ + this.canvas.height / this.pixelRatio + }px`; that.canvas.getContext('2d').drawImage(imageData, 0, 0); // this.canvas.style.transform = message.data.transform; that.workerFrameState = message.data.frameState; From fe1f5aba5bb098df727317b4b7a475afe260417b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Oct 2021 17:01:31 +0200 Subject: [PATCH 041/109] Tsest separate build and lint test --- .github/workflows/nodejs.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 78dd2be0..f935c5e0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -3,7 +3,7 @@ name: Build on: [push] jobs: - build: + lintAndTest: runs-on: ubuntu-latest strategy: @@ -23,6 +23,26 @@ jobs: yarn install yarn lint yarn test + env: + CI: true + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: yarn install, build, and test + run: | + echo '[]' > src/doc/components/Esdoc/index.json + npm install -g yarn + yarn install yarn build env: CI: true From f254ca2305f0d677c2362d7a32d91044a70d7862 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 18 Nov 2021 15:50:09 +0100 Subject: [PATCH 042/109] Set bbox on each move end --- src/common/mixins/TralisLayerMixin.js | 3 +++ src/doc/examples.js | 12 ++++----- src/doc/examples/ol-tralis.js | 35 ++++++++++++++++++--------- src/ol/layers/TralisLayer.js | 16 ++++++++++++ 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index c3abf815..b05fe9c7 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -96,6 +96,9 @@ const TralisLayerMixin = (TrackerLayer) => this.onDeleteMessage = this.onDeleteMessage.bind(this); this.api = options.api || new TralisAPI(options); this.format = new GeoJSON(); + + // This property will call api.setBbox on each movend event + this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false; } start() { diff --git a/src/doc/examples.js b/src/doc/examples.js index f014bd69..83c9ccc3 100644 --- a/src/doc/examples.js +++ b/src/doc/examples.js @@ -45,12 +45,12 @@ export default [ }, }, // These examples are used for the dbug of TralisLayer don't remove them pls. - // { - // name: 'Tralis tracker with OpenLayers', - // key: 'ol-tralis', - // description: 'Show moving tramway in an OpenLayers map.', - // img: liveTrackerOLImg, - // }, + { + name: 'Tralis tracker with OpenLayers', + key: 'ol-tralis', + description: 'Show moving tramway in an OpenLayers map.', + img: liveTrackerOLImg, + }, // { // name: 'Tralis tracker with Mapbox', // key: 'mapbox-tralis', diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 01f6670c..958df54c 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -1,26 +1,37 @@ -import TileLayer from 'ol/layer/Tile'; -import OSMSource from 'ol/source/OSM'; import View from 'ol/View'; -import { Map, Layer, TralisLayer } from '../../ol'; +import { fromLonLat } from 'ol/proj'; +import { Map, TralisLayer, MapboxLayer } from '../../ol'; import 'ol/ol.css'; +import CopyrightControl from '../../ol/controls/CopyrightControl'; +import LINE_IMAGES from './assets/tralis-live-map'; export default () => { const map = new Map({ target: 'map', view: new View({ - center: [1282278, 6128615], - zoom: 9, + center: fromLonLat([11.55, 48.14]), + zoom: 11, }), + controls: [new CopyrightControl()], }); - const osm = new Layer({ - olLayer: new TileLayer({ - source: new OSMSource(), - }), + + const layer = new MapboxLayer({ + url: 'https://maps.geops.io/styles/travic_v2/style.json', + apiKey: window.apiKey, }); const tracker = new TralisLayer({ - url: '', - apiKey: '', + url: 'wss://api.geops.io/realtime-ws/dev/', + apiKey: window.apiKey, + bbox: [1152072, 6048052, 1433666, 6205578], + isUpdateBboxOnMoveEnd: true, + style: (props) => { + const img = new Image(); + img.src = LINE_IMAGES[(props.line || {}).name || 'unknown']; + img.width = 25 * window.devicePixelRatio; + img.height = 25 * window.devicePixelRatio; + return img; + }, }); tracker.onClick(({ features: [feature] }) => { @@ -30,6 +41,6 @@ export default () => { } }); - map.addLayer(osm); + map.addLayer(layer); map.addLayer(tracker); }; diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index 1e683763..87a00e2f 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -19,6 +19,22 @@ import mixin from '../../common/mixins/TralisLayerMixin'; * @implements {TralisLayerInterface} */ class TralisLayer extends mixin(TrackerLayer) { + init(map) { + super.init(map); + if (this.map) { + if (this.isUpdateBboxOnMoveEnd) { + this.olListenersKeys.push( + this.map.on('moveend', () => { + this.api.conn.setBbox([ + ...this.map.getView().calculateExtent(), + Math.floor(this.map.getView().getZoom()), + ]); + }), + ); + } + } + } + /** * Create a copy of the TralisLayer. * @param {Object} newOptions Options to override From f7c4d488d0e36c353f501eec209ff3c950b05063 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 18 Nov 2021 16:18:40 +0100 Subject: [PATCH 043/109] Deactivate the GET request after the bbox is set --- src/api/tralis/WebSocketConnector.js | 6 +++--- src/api/tralis/WebSocketConnector.test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 5e1493bc..2bcf7a6d 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -124,9 +124,9 @@ class WebSocketConnector { */ this.currentBbox = coordinates; this.send(`BBOX ${coordinates.join(' ')}`); - this.subscriptions.forEach((s) => { - this.get(s.params, s.cb, s.errorCb); - }); + // this.subscriptions.forEach((s) => { + // this.get(s.params, s.cb, s.errorCb); + // }); } /** diff --git a/src/api/tralis/WebSocketConnector.test.js b/src/api/tralis/WebSocketConnector.test.js index f113922d..9f4c2174 100644 --- a/src/api/tralis/WebSocketConnector.test.js +++ b/src/api/tralis/WebSocketConnector.test.js @@ -103,8 +103,8 @@ describe('WebSocketConnector', () => { client.setBbox([0, 0, 0, 0]); expect(client.subscriptions.length).toBe(3); - expect(client.websocket.removeEventListener).toBeCalledTimes(3); - expect(client.websocket.addEventListener).toBeCalledTimes(3); + expect(client.websocket.removeEventListener).toBeCalledTimes(0); + expect(client.websocket.addEventListener).toBeCalledTimes(0); client.unsubscribe('foo'); expect(client.subscriptions.length).toBe(1); From 3388303761b69a202552f0443a45d24d77e3e429 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 18 Nov 2021 16:19:32 +0100 Subject: [PATCH 044/109] v1.5.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 62556414..6c2a2795 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.5.0-beta.2", + "version": "1.5.0-beta.3", "main": "index.js", "module": "module.js", "dependencies": { From 99938debfd18f3fb68c5df3a023edaebf7721ae1 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 18 Nov 2021 17:35:49 +0100 Subject: [PATCH 045/109] v1.5.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c2a2795..1c515a1e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.5.0-beta.3", + "version": "1.5.0-beta.4", "main": "index.js", "module": "module.js", "dependencies": { From 77beff4f3e6f0b57871d1b621955ccf50407b426 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 18 Nov 2021 17:36:30 +0100 Subject: [PATCH 046/109] Add eslint --- src/doc/examples/ol-tralis.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index afdefe24..c4be30b3 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -36,6 +36,7 @@ export default () => { tracker.onClick(([feature]) => { if (feature) { + // eslint-disable-next-line no-console console.log(feature.getProperties()); } }); From bb8167a593732e3bf349a65099949b26a2e55b2e Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 10:51:03 +0100 Subject: [PATCH 047/109] Manage the new api --- src/api/tralis/TralisAPI.js | 20 +++++++--- src/api/tralis/WebSocketConnector.js | 41 +++++++++---------- src/common/mixins/TrackerLayerMixin.js | 22 +++++++++-- src/common/mixins/TralisLayerMixin.js | 13 +----- src/doc/examples/tralis-live-map.js | 21 +++++++--- src/mapbox/layers/TrackerLayer.js | 12 ++++-- src/mapbox/layers/TralisLayer.js | 52 +++++++++--------------- src/ol/layers/TrackerLayer.js | 55 +++++++++++++++++++++++++- src/ol/layers/TrajservLayer.test.js | 5 +-- src/ol/layers/TralisLayer.js | 29 +++++++------- 10 files changed, 166 insertions(+), 104 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index b52860c2..59f458df 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -75,13 +75,18 @@ class TralisAPI { /** @ignore */ this.prefix = options.prefix || ''; + this.isUpdateBboxOnMoveEnd = options.isUpateBboxOnMoveEnd || false; + /** @ignore */ this.conn = new WebSocketConnector(wsUrl); - // this.conn.setProjection(options.projection || 'epsg:3857'); - // if (options.bbox) { - // this.conn.setBbox(options.bbox); - // } + if (!this.isUpdateBboxOnMoveEnd) { + this.conn.setProjection(options.projection || 'epsg:3857'); + + if (options.bbox) { + this.conn.setBbox(options.bbox); + } + } } /** @@ -377,7 +382,12 @@ class TralisAPI { */ subscribeTrajectory(mode, onMessage) { this.unsubscribeTrajectory(onMessage); - this.subscribe(`trajectory${getModeSuffix(mode, TralisModes)}`, onMessage); + this.subscribe( + `trajectory${getModeSuffix(mode, TralisModes)}`, + onMessage, + null, + this.isUpdateBboxOnMoveEnd, + ); } /** diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 425eb823..9c9d16a3 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -52,17 +52,16 @@ class WebSocketConnector { // this.setProjection(this.currentProj); // } - [...this.subscriptions].forEach((s) => { - this.subscribe(s.params, s.cb, s.errorCb, true); - }); + // [...this.subscriptions].forEach((s) => { + // this.subscribe(s.params, s.cb, s.errorCb, s.quiet); + // }); // if (this.currentBbox) { // this.setBbox(this.currentBbox); // } // reconnect on close - this.websocket.onclose = (e) => { - console.log(e); + this.websocket.onclose = () => { window.clearTimeout(this.reconnectTimeout); /** @ignore */ this.reconnectTimeout = window.setTimeout(() => this.connect(url), 100); @@ -125,7 +124,6 @@ class WebSocketConnector { */ this.currentBbox = coordinates; - console.log(`BBOX ${coordinates.join(' ')}`); this.send(`BBOX ${coordinates.join(' ')}`); // this.subscriptions.forEach((s) => { // this.get(s.params, s.cb, s.errorCb); @@ -146,7 +144,6 @@ class WebSocketConnector { const onMessage = (e) => { const data = JSON.parse(e.data); - console.log(`message`, data.source); let source = params.channel; source += params.args ? ` ${params.args}` : ''; @@ -154,7 +151,6 @@ class WebSocketConnector { data.source === source && (!params.id || params.id === data.client_reference) ) { - console.log(`cb`); cb(data); } }; @@ -189,28 +185,27 @@ class WebSocketConnector { * @param {Object} params Parameters for the websocket get request * @param {function} cb callback on listen * @param {function} errorCb Callback on error - * @param {boolean} quiet if subscribe should be quiet + * @param {boolean} quiet if false, no GET or SUB requests are send, only the callback is registered. */ - subscribe(params, cb, errorCb, quiet) { + subscribe(params, cb, errorCb, quiet = false) { const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb); const reqStr = WebSocketConnector.getRequestString('', params); - if (!quiet) { - const index = this.subscriptions.findIndex((subcr) => { - return params.channel === subcr.params.channel && cb === subcr.cb; - }); - const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb }; - if (index > -1) { - this.subscriptions[index] = newSubscr; - } else { - this.subscriptions.push(newSubscr); - } + const index = this.subscriptions.findIndex((subcr) => { + return params.channel === subcr.params.channel && cb === subcr.cb; + }); + const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet }; + if (index > -1) { + this.subscriptions[index] = newSubscr; + } else { + this.subscriptions.push(newSubscr); } if (!this.subscribed[reqStr]) { - console.log(`SUB reqStr ${reqStr}`); - // this.send(`GET ${reqStr}`); - // this.send(`SUB ${reqStr}`); + if (!newSubscr.quiet) { + this.send(`GET ${reqStr}`); + this.send(`SUB ${reqStr}`); + } this.subscribed[reqStr] = true; } } diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 719ca097..4f6376b1 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -44,10 +44,8 @@ export class TrackerLayerInterface { /** * Start the timeout for the next update. * @private - * @param {number} zoom */ - // eslint-disable-next-line no-unused-vars - startUpdateTime(zoom) {} + startUpdateTime() {} /** * Stop the clock. @@ -426,6 +424,9 @@ const TrackerLayerMixin = (Base) => } } + /** + * + /** * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method. * @@ -547,9 +548,21 @@ const TrackerLayerMixin = (Base) => }); } + /** + * On zoomend we adjust the time interval of the update of vehicles positions. + * + * @param evt Event that triggered the function. + * @private + */ + // eslint-disable-next-line no-unused-vars + onZoomEnd(evt) { + this.startUpdateTime(); + } + /** * Define beahvior when a vehicle is clicked * To be defined in child classes. + * * @private * @override */ @@ -558,6 +571,7 @@ const TrackerLayerMixin = (Base) => /** * Define behavior when a vehicle is hovered * To be defined in child classes. + * * @private * @override */ @@ -565,6 +579,7 @@ const TrackerLayerMixin = (Base) => /** * Get the duration before the next update depending on zoom level. + * * @private * @param {number} zoom */ @@ -572,6 +587,7 @@ const TrackerLayerMixin = (Base) => const roundedZoom = Math.round(zoom); const timeStep = timeSteps[roundedZoom] || 25; const nextTick = Math.max(25, timeStep / this.speed); + console.log(`Next render in ${nextTick} ms.`); return nextTick; } diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 1a039bc6..e4ffb8d5 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -91,7 +91,6 @@ const TralisLayerMixin = (TrackerLayer) => super({ ...options }); this.debug = options.debug; this.mode = options.mode || TralisModes.TOPOGRAPHIC; - this.refreshTimeInMs = 1000 / 30; this.onMessage = this.onMessage.bind(this); this.onDeleteMessage = this.onDeleteMessage.bind(this); this.api = options.api || new TralisAPI(options); @@ -215,9 +214,8 @@ const TralisLayerMixin = (TrackerLayer) => this.tracker.trajectories.splice(idx, 1); } } - console.log(this.trajectories.length); - this.updateTrajectories(); + this.tracker.setTrajectories(this.trajectories); } removeTrajectory(id) { @@ -237,15 +235,6 @@ const TralisLayerMixin = (TrackerLayer) => } } } - - updateTrajectories() { - this.tracker.setTrajectories(this.trajectories); - this.renderTrajectories(); - } - - getRefreshTimeInMs() { - return this.refreshTimeInMs; - } }; export default TralisLayerMixin; diff --git a/src/doc/examples/tralis-live-map.js b/src/doc/examples/tralis-live-map.js index 3899e739..ae22a508 100644 --- a/src/doc/examples/tralis-live-map.js +++ b/src/doc/examples/tralis-live-map.js @@ -13,16 +13,27 @@ export default () => { pitchWithRotate: false, }); + const cache = {}; const tracker = new TralisLayer({ + // url: 'wss://tralis-tracker-api.geops.io/ws', + // isUpdateBboxOnMoveEnd: true, url: 'wss://api.geops.io/realtime-ws/v1/', + isUpdateBboxOnMoveEnd: false, apiKey: window.apiKey, bbox: [1152072, 6048052, 1433666, 6205578], style: (props) => { - const img = new Image(); - img.src = LINE_IMAGES[(props.line || {}).name || 'unknown']; - img.width = 25 * window.devicePixelRatio; - img.height = 25 * window.devicePixelRatio; - return img; + let { name } = props.line || {}; + if (!name || !LINE_IMAGES[name]) { + name = 'unknown'; + } + if (!cache[name]) { + const img = new Image(); + img.src = LINE_IMAGES[name]; + img.width = 25 * window.devicePixelRatio; + img.height = 25 * window.devicePixelRatio; + cache[name] = img; + } + return cache[name]; }, }); diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index 9ac954b7..8ff83f6e 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -22,6 +22,9 @@ class TrackerLayer extends mixin(Layer) { /** @ignore */ this.onMove = this.onMove.bind(this); + /** @ignore */ + this.onMoveEnd = this.onMoveEnd.bind(this); + /** @ignore */ this.onZoomEnd = this.onZoomEnd.bind(this); @@ -106,6 +109,7 @@ class TrackerLayer extends mixin(Layer) { super.start(); this.map.on('move', this.onMove); + this.map.on('moveend', this.onMoveEnd); this.map.on('zoomend', this.onZoomEnd); } @@ -118,6 +122,7 @@ class TrackerLayer extends mixin(Layer) { super.stop(); if (this.map) { this.map.off('move', this.onMove); + this.map.off('moveend', this.onMoveEnd); this.map.off('zoomend', this.onZoomEnd); } } @@ -248,13 +253,12 @@ class TrackerLayer extends mixin(Layer) { } /** - * On zoomend we adjust the time interval of the update of vehicles positions. + * Callback on 'moveend' event. * * @private */ - onZoomEnd() { - this.startUpdateTime(this.map.getZoom()); - } + // eslint-disable-next-line class-methods-use-this + onMoveEnd() {} /** * Update the cursor style when hovering a vehicle. diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 559faa02..19cc244d 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -1,3 +1,4 @@ +import { transformExtent } from 'ol/proj'; import TrackerLayer from './TrackerLayer'; import mixin from '../../common/mixins/TralisLayerMixin'; @@ -19,45 +20,28 @@ import mixin from '../../common/mixins/TralisLayerMixin'; * @implements {TralisLayerInterface} */ class TralisLayer extends mixin(TrackerLayer) { - constructor(options = {}) { - super({ ...options }); - - /** @ignore */ - this.onMoveEnd = this.onMoveEnd.bind(this); - } - /** - * Add listeners from the Mapbox Map. + * Send the new BBOX to the websocket. * - * @param {mapboxgl.Map} map - * @param {string} beforeId See [mapboxgl.Map#addLayer](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer) beforeId documentation. + * @param {ol/MapEvent~MapEvent} evt Moveend event + * @private + * @override */ - init(map, beforeId) { - super.init(map, beforeId); + onMoveEnd(evt) { + super.onMoveEnd(evt); + console.log('onMoveEnd'); - if (!this.map) { - return; + if (this.isUpdateBboxOnMoveEnd) { + const bounds = this.map.getBounds().toArray(); + this.api.conn.setBbox([ + ...transformExtent( + [...bounds[0], ...bounds[1]], + 'EPSG:4326', + 'EPSG:3857', + ), + Math.floor(this.map.getZoom() + 1), + ]); } - this.map.on('moveend', this.onMoveEnd); - } - - /** - * Remove listeners from the Mapbox Map. - */ - terminate() { - if (this.map) { - this.map.off('moveend', this.onMoveEnd); - } - super.terminate(); - } - - /** - * Callback on 'moveend' event. - * - * @private - */ - onMoveEnd() { - this.updateTrajectories(); } } diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index b1f9963a..6c76cc8f 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -120,6 +120,29 @@ class TrackerLayer extends mixin(Layer) { }; } + init(map) { + super.init(map); + if (this.map) { + this.olListenersKeys.push( + this.map.on('moveend', (evt) => { + const view = this.map.getView(); + if (view.getAnimating() || view.getInteracting()) { + return; + } + const zoom = view.getZoom(); + + // Update the interval between render updates + if (this.currentZoom !== zoom) { + this.onZoomEnd(evt); + } + this.currentZoom = zoom; + + this.onMoveEnd(evt); + }), + ); + } + } + /** * Render the trajectories using current map's size, resolution and rotation. * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions. @@ -149,7 +172,15 @@ class TrackerLayer extends mixin(Layer) { renderTrajectoriesInternal(viewState, noInterpolate) { let isRendered = false; - isRendered = super.renderTrajectoriesInternal(viewState, noInterpolate); + const blockRendering = + !this.renderWhenInteracting(viewState, this.renderedViewState) && + (this.map.getView().getAnimating() || + this.map.getView().getInteracting()); + + // Don't render the map when the map is animating or interacting. + isRendered = blockRendering + ? false + : super.renderTrajectoriesInternal(viewState, noInterpolate); // We update the current render state. if (isRendered) { @@ -186,6 +217,28 @@ class TrackerLayer extends mixin(Layer) { return super.getFeatureInfoAtCoordinate(coordinate, { resolution }); } + /** + * Function called on moveend event. + * To be defined in inherited classes + * + * @param {ol/MapEvent~MapEvent} evt Moveend event. + * @private + */ + // eslint-disable-next-line no-unused-vars,class-methods-use-this + onMoveEnd(evt) {} + + /** + * Function called on moveend event only when the zoom has changed. + * + * @param {ol/MapEvent~MapEvent} evt Moveend event. + * @private + * @override + */ + // eslint-disable-next-line no-unused-vars + onZoomEnd(evt) { + super.onZoomEnd(evt); + } + /** * Update the cursor style when hovering a vehicle. * diff --git a/src/ol/layers/TrajservLayer.test.js b/src/ol/layers/TrajservLayer.test.js index 8601a784..b5fc7200 100644 --- a/src/ol/layers/TrajservLayer.test.js +++ b/src/ol/layers/TrajservLayer.test.js @@ -76,11 +76,10 @@ describe('TrajservLayer', () => { // dispatchEvent uses mockResponse const evt2 = { type: 'moveend', olMap }; - // On the first movend requestIntervalInSec will be updated so it will unlisten the event then read them. - olMap.dispatchEvent(evt2); - expect(spy2).toHaveBeenCalledTimes(1); olMap.dispatchEvent(evt2); expect(spy2).toHaveBeenCalledTimes(2); + olMap.dispatchEvent(evt2); + expect(spy2).toHaveBeenCalledTimes(4); }); test('should create a default api with default url.', () => { diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index 986c2d06..c795d70d 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -19,20 +19,21 @@ import mixin from '../../common/mixins/TralisLayerMixin'; * @implements {TralisLayerInterface} */ class TralisLayer extends mixin(TrackerLayer) { - init(map) { - super.init(map); - if (this.map) { - if (this.isUpdateBboxOnMoveEnd) { - this.olListenersKeys.push( - this.map.on('moveend', () => { - console.log('ici'); - this.api.conn.setBbox([ - ...this.map.getView().calculateExtent(), - Math.floor(this.map.getView().getZoom()), - ]); - }), - ); - } + /** + * Send the new BBOX to the websocket. + * + * @param {ol/MapEvent~MapEvent} evt Moveend event + * @private + * @override + */ + onMoveEnd(evt) { + super.onMoveEnd(evt); + + if (this.isUpdateBboxOnMoveEnd) { + this.api.conn.setBbox([ + ...this.map.getView().calculateExtent(), + Math.floor(this.map.getView().getZoom()), + ]); } } From 77f705d7a4baa7b849bb839cc9909f7de7e0235f Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 10:53:20 +0100 Subject: [PATCH 048/109] v1.6.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c515a1e..dab22ad5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.5.0-beta.4", + "version": "1.6.0-beta.1", "main": "index.js", "module": "module.js", "dependencies": { From ab240b0878cc11dbc668e1d786978af119088b12 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 11:45:16 +0100 Subject: [PATCH 049/109] v1.6.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dab22ad5..c6c5e039 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.1", + "version": "1.6.0-beta.2", "main": "index.js", "module": "module.js", "dependencies": { From 0f7f0e0006d816c5f144d95cda91ad3abb85b2f3 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 11:48:56 +0100 Subject: [PATCH 050/109] v1.6.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6c5e039..3dc42131 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.2", + "version": "1.6.0-beta.3", "main": "index.js", "module": "module.js", "dependencies": { From 2725f42d35f7497719764f292b3f2d476cedb6d5 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 11:52:28 +0100 Subject: [PATCH 051/109] v1.6.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3dc42131..1083bea8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.3", + "version": "1.6.0-beta.4", "main": "index.js", "module": "module.js", "dependencies": { From 1a5c6e599fe2eb3ad068d648a24d7ac3de96b777 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 12:43:45 +0100 Subject: [PATCH 052/109] Request full trajectory --- src/api/tralis/TralisAPI.js | 10 ++- src/api/tralis/WebSocketConnector.js | 27 ++++--- src/ol/layers/TrackerLayer.js | 10 ++- src/ol/layers/TrajservLayer.js | 41 ----------- src/ol/layers/TralisLayer.js | 101 +++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 52 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index 59f458df..2b4e3716 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -75,11 +75,19 @@ class TralisAPI { /** @ignore */ this.prefix = options.prefix || ''; - this.isUpdateBboxOnMoveEnd = options.isUpateBboxOnMoveEnd || false; + this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false; /** @ignore */ this.conn = new WebSocketConnector(wsUrl); + this.conn.isSUBAllow = !this.isUpdateBboxOnMoveEnd; + this.conn.isDELAllow = !this.isUpdateBboxOnMoveEnd; + console.log( + this.conn.isSUBAllow, + this.isUpdateBboxOnMoveEnd, + !this.isUpdateBboxOnMoveEnd, + ); + if (!this.isUpdateBboxOnMoveEnd) { this.conn.setProjection(options.projection || 'epsg:3857'); diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 9c9d16a3..002e026f 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -11,6 +11,9 @@ class WebSocketConnector { this.subscriptions = []; this.connect(url); + this.isSUBAllow = false; + this.isDELAllow = false; + // keep websocket alive setInterval(() => { this.send('PING'); @@ -52,13 +55,13 @@ class WebSocketConnector { // this.setProjection(this.currentProj); // } - // [...this.subscriptions].forEach((s) => { - // this.subscribe(s.params, s.cb, s.errorCb, s.quiet); - // }); + [...this.subscriptions].forEach((s) => { + this.subscribe(s.params, s.cb, s.errorCb, s.quiet); + }); - // if (this.currentBbox) { - // this.setBbox(this.currentBbox); - // } + if (this.currentBbox) { + this.setBbox(this.currentBbox); + } // reconnect on close this.websocket.onclose = () => { @@ -202,10 +205,14 @@ class WebSocketConnector { } if (!this.subscribed[reqStr]) { - if (!newSubscr.quiet) { - this.send(`GET ${reqStr}`); + // if (!newSubscr.quiet) { + this.send(`GET ${reqStr}`); + + console.log(this.isSUBAllow); + if (this.isSUBAllow) { this.send(`SUB ${reqStr}`); } + this.subscribed[reqStr] = true; } } @@ -239,7 +246,9 @@ class WebSocketConnector { this.subscribed[source] && !this.subscriptions.find((s) => s.params.channel === source) ) { - this.send(`DEL ${source}`); + if (this.isDELAllow) { + this.send(`DEL ${source}`); + } this.subscribed[source] = false; } } diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 4789368d..72920c3e 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -1,7 +1,9 @@ -import { Layer as OLLayer, Group } from 'ol/layer'; +import { Layer as OLLayer, Group, Vector as VectorLayer } from 'ol/layer'; import Source from 'ol/source/Source'; import { composeCssTransform } from 'ol/transform'; +import { Vector as VectorSource } from 'ol/source'; import mixin from '../../common/mixins/TrackerLayerMixin'; + import Layer from './Layer'; /** @@ -47,6 +49,9 @@ class TrackerLayer extends mixin(Layer) { options.olLayer || new Group({ layers: [ + new VectorLayer({ + source: new VectorSource({ features: [] }), + }), new OLLayer({ source: new Source({}), render: (frameState) => { @@ -110,6 +115,9 @@ class TrackerLayer extends mixin(Layer) { ], }); + // We store the layer used to highlight the full Trajectory + this.vectorLayer = this.olLayer.getLayers().get(0); + // Options the last render run did happen. If something changes // we have to render again /** @ignore */ diff --git a/src/ol/layers/TrajservLayer.js b/src/ol/layers/TrajservLayer.js index 0990b5ea..c663d25b 100644 --- a/src/ol/layers/TrajservLayer.js +++ b/src/ol/layers/TrajservLayer.js @@ -3,8 +3,6 @@ import { transform as transformCoords } from 'ol/proj'; import { buffer, getWidth } from 'ol/extent'; import { MultiPoint, LineString } from 'ol/geom'; import { Style, Fill, Stroke, Circle } from 'ol/style'; -import { Vector as VectorLayer } from 'ol/layer'; -import { Vector as VectorSource } from 'ol/source'; import { unByKey } from 'ol/Observable'; import TrackerLayer from './TrackerLayer'; import { getUTCTimeString } from '../../common/timeUtils'; @@ -30,45 +28,6 @@ import mixin from '../../common/mixins/TrajservLayerMixin'; * @implements {TrajservLayerInterface} */ class TrajservLayer extends mixin(TrackerLayer) { - constructor(options = {}) { - // We use a group to be able to add custom vector layer in extended class. - // For example TrajservLayer use a vectorLayer to display the complete trajectory. - super({ - ...options, - }); - - /** @ignore */ - this.vectorLayer = new VectorLayer({ - source: new VectorSource({ features: [] }), - }); - this.olLayer.getLayers().insertAt(0, this.vectorLayer); - } - - /** - * Initialize the layer. - * @param {mapboxgl.Map} map the mapbox map. - * @override - */ - init(map) { - if (!map) { - return; - } - - map.addLayer(this.vectorLayer); - super.init(map); - } - - /** - * Terminate the layer. - * @override - */ - terminate() { - if (this.map) { - this.map.removeLayer(this.vectorLayer); - } - super.terminate(); - } - /** * Start the layer. * @override diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index c795d70d..d69f3a77 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -1,6 +1,9 @@ +import GeoJSON from 'ol/format/GeoJSON'; import TrackerLayer from './TrackerLayer'; import mixin from '../../common/mixins/TralisLayerMixin'; +const format = new GeoJSON(); + /** * Responsible for loading and display data from a Tralis service. * @@ -35,6 +38,104 @@ class TralisLayer extends mixin(TrackerLayer) { Math.floor(this.map.getView().getZoom()), ]); } + console.log(this.selectedVehicleId); + if (this.selectedVehicleId) { + this.highlightTrajectory(); + } + } + + /** + * Highlight the trajectory of journey. + * @private + */ + highlightTrajectory() { + this.api + .getFullTrajectory(this.selectedVehicleId) + .then((geojson) => { + const features = format.readFeatures(geojson); + this.drawFullTrajectory(features); + // .then((traj) => { + // const { p: multiLine, t, c } = traj; + // const lineCoords = []; + // multiLine.forEach((line) => { + // line.forEach((point) => { + // lineCoords.push([point.x, point.y]); + // }); + // }); + + // this.drawFullTrajectory( + // this.stationsCoords, + // new LineString(lineCoords), + // c ? `#${c}` : getBgColor(t), + // ); + }) + .catch(() => { + this.vectorLayer.getSource().clear(); + }); + } + + /** + * Draw the trajectory as a line with points for each stop. + * @param {Array} stationsCoords Array of station coordinates. + * @param {LineString|MultiLineString} lineGeometry A LineString or a MultiLineString. + * @param {string} color The color of the line. + * @private + */ + drawFullTrajectory(features, stationsCoords, lineGeometry, color) { + // Don't allow white lines, use red instead. + // const vehiculeColor = /#ffffff/i.test(color) ? '#ff0000' : color; + const vectorSource = this.vectorLayer.getSource(); + vectorSource.clear(); + + // if (stationsCoords) { + // const geometry = new MultiPoint(stationsCoords); + // const aboveStationsFeature = new Feature(geometry); + // aboveStationsFeature.setStyle( + // new Style({ + // zIndex: 1, + // image: new Circle({ + // radius: 5, + // fill: new Fill({ + // color: '#000000', + // }), + // }), + // }), + // ); + // const belowStationsFeature = new Feature(geometry); + // belowStationsFeature.setStyle( + // new Style({ + // zIndex: 4, + // image: new Circle({ + // radius: 4, + // fill: new Fill({ + // color: this.useDelayStyle ? '#a0a0a0' : vehiculeColor, + // }), + // }), + // }), + // ); + // vectorSource.addFeatures([aboveStationsFeature, belowStationsFeature]); + // } + + // const lineFeat = new Feature({ + // geometry: lineGeometry, + // }); + // lineFeat.setStyle([ + // new Style({ + // zIndex: 2, + // stroke: new Stroke({ + // color: '#000000', + // width: 6, + // }), + // }), + // new Style({ + // zIndex: 3, + // stroke: new Stroke({ + // color: this.useDelayStyle ? '#a0a0a0' : vehiculeColor, + // width: 4, + // }), + // }), + // ]); + vectorSource.addFeatures(features); } /** From f46e90cd9cdae9d4a3afdc84b530d3ff5b9e148c Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 13:55:17 +0100 Subject: [PATCH 053/109] Request full trajectory --- src/common/mixins/TrajservLayerMixin.js | 1 + src/common/mixins/TralisLayerMixin.js | 27 +++++++++++++++++++++++++ src/ol/layers/TrackerLayer.js | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index 306b361d..c35908b6 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -339,6 +339,7 @@ const TrajservLayerMixin = (TrackerLayer) => onFeatureClick(features, layer, coordinate) { const [feature] = features; if (feature) { + console.log(feature); /** @ignore */ this.selectedVehicleId = feature.get('id'); /** @ignore */ diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 8504db96..1f3c92b0 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -153,8 +153,10 @@ const TralisLayerMixin = (TrackerLayer) => onFeatureClick(features, layer, coordinate) { const [feature] = features; if (feature) { + console.log(feature); /** @ignore */ this.selectedVehicleId = feature.get('train_id'); + this.onFeatureSelected(); } else { this.selectedVehicleId = null; } @@ -195,6 +197,31 @@ const TralisLayerMixin = (TrackerLayer) => } } + /** + * When a vehicle is selected, we request the complete stop sequence and the complete full trajectory. + * Then we combine them in one response and send them to inherited layers. + * + * @private + * @override + */ + onFeatureSelected() { + // When a vehicle is selected, we request the complete stop sequence and the complete full trajectory. + // Then we combine them in one response and send them to inherited layers. + const promises = [ + this.api.getStopSequence(this.selectedVehicleId, this.mode), + this.api.getFullTrajectory(this.selectedVehicleId, this.mode), + ]; + + return Promise.all(promises).then(([stopSequence, fullTrajectory]) => { + console.log(this.journeyId, stopSequence, fullTrajectory); + const response = { + stopSequence, + fullTrajectory, + }; + return response; + }); + } + addTrajectory(id, traj, addOnTop) { const idx = this.trajectories.findIndex((t) => t.train_id === id); const { time_intervals: timeIntervals } = traj; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 72920c3e..fdd4ffdf 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -116,7 +116,7 @@ class TrackerLayer extends mixin(Layer) { }); // We store the layer used to highlight the full Trajectory - this.vectorLayer = this.olLayer.getLayers().get(0); + this.vectorLayer = this.olLayer.getLayers().item(0); // Options the last render run did happen. If something changes // we have to render again From 40745c6cf9f9edfd09bd35cf0afd8301110df152 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:03:26 +0100 Subject: [PATCH 054/109] Fix highlight of tracker trajectory --- src/api/tralis/TralisAPI.js | 5 - src/api/tralis/WebSocketConnector.js | 1 - src/common/mixins/TrajservLayerMixin.js | 5 +- src/common/mixins/TralisLayerMixin.js | 14 ++- src/common/utils/index.js | 1 + src/doc/examples/mb-tracker.js | 2 +- src/doc/examples/ol-tralis.js | 25 ++-- src/ol/layers/TralisLayer.js | 160 +++++++++++------------- 8 files changed, 98 insertions(+), 115 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index 2b4e3716..c35975f7 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -82,11 +82,6 @@ class TralisAPI { this.conn.isSUBAllow = !this.isUpdateBboxOnMoveEnd; this.conn.isDELAllow = !this.isUpdateBboxOnMoveEnd; - console.log( - this.conn.isSUBAllow, - this.isUpdateBboxOnMoveEnd, - !this.isUpdateBboxOnMoveEnd, - ); if (!this.isUpdateBboxOnMoveEnd) { this.conn.setProjection(options.projection || 'epsg:3857'); diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 002e026f..cb2c174f 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -208,7 +208,6 @@ class WebSocketConnector { // if (!newSubscr.quiet) { this.send(`GET ${reqStr}`); - console.log(this.isSUBAllow); if (this.isSUBAllow) { this.send(`SUB ${reqStr}`); } diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index dc166a46..6d58a4ba 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -338,7 +338,6 @@ const TrajservLayerMixin = (TrackerLayer) => onFeatureClick(features, layer, coordinate) { const [feature] = features; if (feature) { - console.log(feature); /** @ignore */ this.selectedVehicleId = feature.get('id'); /** @ignore */ @@ -393,9 +392,9 @@ const TrajservLayerMixin = (TrackerLayer) => }); const lineColor = color ? `#${color}` : getBgColor(t); - // Don't allow white lines, use red instead. + // Don't allow white lines, use black instead. const vehiculeColor = /#ffffff/i.test(lineColor) - ? '#ff0000' + ? '#000000' : lineColor; this.drawFullTrajectory( diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 1f3c92b0..5bdde212 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -153,10 +153,9 @@ const TralisLayerMixin = (TrackerLayer) => onFeatureClick(features, layer, coordinate) { const [feature] = features; if (feature) { - console.log(feature); /** @ignore */ this.selectedVehicleId = feature.get('train_id'); - this.onFeatureSelected(); + this.highlightTrajectory(); } else { this.selectedVehicleId = null; } @@ -204,7 +203,7 @@ const TralisLayerMixin = (TrackerLayer) => * @private * @override */ - onFeatureSelected() { + highlightTrajectory() { // When a vehicle is selected, we request the complete stop sequence and the complete full trajectory. // Then we combine them in one response and send them to inherited layers. const promises = [ @@ -213,7 +212,14 @@ const TralisLayerMixin = (TrackerLayer) => ]; return Promise.all(promises).then(([stopSequence, fullTrajectory]) => { - console.log(this.journeyId, stopSequence, fullTrajectory); + console.log( + `stopSequence for ${this.selectedVehicleId}:`, + stopSequence, + ); + console.log( + `fullTrajectory for ${this.selectedVehicleId}:`, + fullTrajectory, + ); const response = { stopSequence, fullTrajectory, diff --git a/src/common/utils/index.js b/src/common/utils/index.js index f569b2a7..33402bd7 100644 --- a/src/common/utils/index.js +++ b/src/common/utils/index.js @@ -1,3 +1,4 @@ export { default as getMapboxStyleUrl } from './getMapboxStyleUrl'; export { default as getMapboxMapCopyrights } from './getMapboxMapCopyrights'; export { default as removeDuplicate } from './removeDuplicate'; +export { default as trackerStyle } from './trackerStyle'; diff --git a/src/doc/examples/mb-tracker.js b/src/doc/examples/mb-tracker.js index a5d38e6d..6417f32a 100644 --- a/src/doc/examples/mb-tracker.js +++ b/src/doc/examples/mb-tracker.js @@ -20,7 +20,7 @@ export default () => { tracker.onClick(([feature]) => { if (feature) { // eslint-disable-next-line no-console - console.log(feature.getProperties()); + console.log(`trajectory: ${feature.getProperties()}`); } }); diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index bd375410..859e0081 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -3,7 +3,8 @@ import { fromLonLat } from 'ol/proj'; import { Map, TralisLayer, MapboxLayer } from '../../ol'; import 'ol/ol.css'; import CopyrightControl from '../../ol/controls/CopyrightControl'; -import LINE_IMAGES from './assets/tralis-live-map'; +import { trackerStyle } from '../../common/utils'; +// import LINE_IMAGES from './assets/tralis-live-map'; // const min = [1254752.0378, 6115573.759]; // const max = [1321443.345, 6148938.5219]; @@ -13,7 +14,8 @@ export default () => { target: 'map', view: new View({ // center: max, - center: fromLonLat([7.841148, 47.996542]), // freiburg + center: [831634, 5933959], + // center: fromLonLat([7.841148, 47.996542]), // freiburg // center: fromLonLat([11.55, 48.14]), // munich zoom: 11, }), @@ -24,25 +26,14 @@ export default () => { url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); - const cache = {}; + const tracker = new TralisLayer({ url: 'wss://tralis-tracker-api.geops.io/ws', apiKey: window.apiKey, - bbox: [1152072, 6048052, 1433666, 6205578], + // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, - style: (props) => { - let { name } = props.line || {}; - if (!name || !LINE_IMAGES[name]) { - name = 'unknown'; - } - if (!cache[name]) { - const img = new Image(); - img.src = LINE_IMAGES[name]; - img.width = 25 * window.devicePixelRatio; - img.height = 25 * window.devicePixelRatio; - cache[name] = img; - } - return cache[name]; + style: (obj, viewState) => { + return trackerStyle(obj, viewState, tracker); }, }); diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index d69f3a77..d24f2bff 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -1,4 +1,7 @@ import GeoJSON from 'ol/format/GeoJSON'; +import Feature from 'ol/Feature'; +import { MultiPoint } from 'ol/geom'; +import { Style, Fill, Stroke, Circle } from 'ol/style'; import TrackerLayer from './TrackerLayer'; import mixin from '../../common/mixins/TralisLayerMixin'; @@ -38,7 +41,7 @@ class TralisLayer extends mixin(TrackerLayer) { Math.floor(this.map.getView().getZoom()), ]); } - console.log(this.selectedVehicleId); + if (this.selectedVehicleId) { this.highlightTrajectory(); } @@ -49,93 +52,82 @@ class TralisLayer extends mixin(TrackerLayer) { * @private */ highlightTrajectory() { - this.api - .getFullTrajectory(this.selectedVehicleId) - .then((geojson) => { - const features = format.readFeatures(geojson); - this.drawFullTrajectory(features); - // .then((traj) => { - // const { p: multiLine, t, c } = traj; - // const lineCoords = []; - // multiLine.forEach((line) => { - // line.forEach((point) => { - // lineCoords.push([point.x, point.y]); - // }); - // }); + super.highlightTrajectory().then(({ stopSequence, fullTrajectory }) => { + const vectorSource = this.vectorLayer.getSource(); + vectorSource.clear(); + const color = + (stopSequence && + stopSequence[0] && + stopSequence[0].color && + `#${stopSequence[0].color}`) || + '#ff0000'; - // this.drawFullTrajectory( - // this.stationsCoords, - // new LineString(lineCoords), - // c ? `#${c}` : getBgColor(t), - // ); - }) - .catch(() => { - this.vectorLayer.getSource().clear(); - }); - } + // const lineColor = color ? `#${color}` : getBgColor(color); + // // Don't allow white lines, use red instead. + // const vehiculeColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor; - /** - * Draw the trajectory as a line with points for each stop. - * @param {Array} stationsCoords Array of station coordinates. - * @param {LineString|MultiLineString} lineGeometry A LineString or a MultiLineString. - * @param {string} color The color of the line. - * @private - */ - drawFullTrajectory(features, stationsCoords, lineGeometry, color) { - // Don't allow white lines, use red instead. - // const vehiculeColor = /#ffffff/i.test(color) ? '#ff0000' : color; - const vectorSource = this.vectorLayer.getSource(); - vectorSource.clear(); + if ( + stopSequence && + stopSequence.stations && + stopSequence.stations.length && + stopSequence.stations[0].coordinate + ) { + console.log(stopSequence); + const geometry = new MultiPoint( + stopSequence.stations.map((station) => station.coordinates), + ); - // if (stationsCoords) { - // const geometry = new MultiPoint(stationsCoords); - // const aboveStationsFeature = new Feature(geometry); - // aboveStationsFeature.setStyle( - // new Style({ - // zIndex: 1, - // image: new Circle({ - // radius: 5, - // fill: new Fill({ - // color: '#000000', - // }), - // }), - // }), - // ); - // const belowStationsFeature = new Feature(geometry); - // belowStationsFeature.setStyle( - // new Style({ - // zIndex: 4, - // image: new Circle({ - // radius: 4, - // fill: new Fill({ - // color: this.useDelayStyle ? '#a0a0a0' : vehiculeColor, - // }), - // }), - // }), - // ); - // vectorSource.addFeatures([aboveStationsFeature, belowStationsFeature]); - // } + const aboveStationsFeature = new Feature(geometry); + aboveStationsFeature.setStyle( + new Style({ + zIndex: 1, + image: new Circle({ + radius: 5, + fill: new Fill({ + color: '#000000', + }), + }), + }), + ); + const belowStationsFeature = new Feature(geometry); + belowStationsFeature.setStyle( + new Style({ + zIndex: 4, + image: new Circle({ + radius: 4, + fill: new Fill({ + color, + }), + }), + }), + ); + vectorSource.addFeatures([aboveStationsFeature, belowStationsFeature]); + } - // const lineFeat = new Feature({ - // geometry: lineGeometry, - // }); - // lineFeat.setStyle([ - // new Style({ - // zIndex: 2, - // stroke: new Stroke({ - // color: '#000000', - // width: 6, - // }), - // }), - // new Style({ - // zIndex: 3, - // stroke: new Stroke({ - // color: this.useDelayStyle ? '#a0a0a0' : vehiculeColor, - // width: 4, - // }), - // }), - // ]); - vectorSource.addFeatures(features); + if (fullTrajectory) { + const features = format.readFeatures(fullTrajectory); + const style = [ + new Style({ + zIndex: 2, + stroke: new Stroke({ + color, + width: 6, + }), + }), + new Style({ + zIndex: 3, + stroke: new Stroke({ + color, + width: 4, + }), + }), + ]; + features.forEach((feature) => { + feature.setStyle(style); + }); + this.vectorLayer.getSource().addFeatures(features); + } + }); } /** From c4beb5bb32754075ea9e16c66c0c0ebedff90b5b Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:05:18 +0100 Subject: [PATCH 055/109] Use black border --- src/ol/layers/TralisLayer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index d24f2bff..bd4479e4 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -72,7 +72,6 @@ class TralisLayer extends mixin(TrackerLayer) { stopSequence.stations.length && stopSequence.stations[0].coordinate ) { - console.log(stopSequence); const geometry = new MultiPoint( stopSequence.stations.map((station) => station.coordinates), ); @@ -110,7 +109,7 @@ class TralisLayer extends mixin(TrackerLayer) { new Style({ zIndex: 2, stroke: new Stroke({ - color, + color: '#000000', width: 6, }), }), From 551587f6f6864ddc94dd5c966c30f0dd6e0e080a Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:06:43 +0100 Subject: [PATCH 056/109] Use black border --- src/common/mixins/TrajservLayerMixin.js | 4 ++-- src/ol/layers/TralisLayer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index 6d58a4ba..99722809 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -392,9 +392,9 @@ const TrajservLayerMixin = (TrackerLayer) => }); const lineColor = color ? `#${color}` : getBgColor(t); - // Don't allow white lines, use black instead. + // Don't allow white lines, use red instead. const vehiculeColor = /#ffffff/i.test(lineColor) - ? '#000000' + ? '#ff0000' : lineColor; this.drawFullTrajectory( diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index bd4479e4..d17d5448 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -60,7 +60,7 @@ class TralisLayer extends mixin(TrackerLayer) { stopSequence[0] && stopSequence[0].color && `#${stopSequence[0].color}`) || - '#ff0000'; + '#000000'; // const lineColor = color ? `#${color}` : getBgColor(color); // // Don't allow white lines, use red instead. From 3222b97f6c980cdaff6b9b10aeaa739f0cac2b76 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:09:52 +0100 Subject: [PATCH 057/109] Use black border --- src/doc/examples/ol-tralis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 859e0081..49ec202d 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -15,9 +15,9 @@ export default () => { view: new View({ // center: max, center: [831634, 5933959], + zoom: 13, // center: fromLonLat([7.841148, 47.996542]), // freiburg // center: fromLonLat([11.55, 48.14]), // munich - zoom: 11, }), controls: [new CopyrightControl()], }); From d7a7481edfa2ddf65a28542ff04341858df7cd5f Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:13:05 +0100 Subject: [PATCH 058/109] Add missing file --- src/common/utils/trackerStyle.js | 183 +++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/common/utils/trackerStyle.js diff --git a/src/common/utils/trackerStyle.js b/src/common/utils/trackerStyle.js new file mode 100644 index 00000000..894aacda --- /dev/null +++ b/src/common/utils/trackerStyle.js @@ -0,0 +1,183 @@ +import { + getRadius, + getBgColor, + getDelayColor, + getDelayText, + getTextColor, + getTextSize, +} from '../trackerConfig'; + +const styleCache = {}; + +const style = (trajectory, viewState, trackerLayer) => { + const { + hoverVehicleId, + selectedVehicleId, + useDelayStyle, + delayOutlineColor, + delayDisplay, + } = trackerLayer; + + const { zoom, pixelRatio } = viewState; + + let { line = {} } = trajectory; + + const { + id, + delay, + type = 'Rail', + cancelled = false, + operatorProvidesRealtime = 'no', + } = trajectory; + + if (!line) { + line = {}; + } + + const { name = 'I', color = '000000' } = line; + let { text_color: textColor } = line; + if (!textColor) { + textColor = '#ffffff'; + } + const z = Math.min(Math.floor(zoom || 1), 16); + const hover = hoverVehicleId === id; + const selected = selectedVehicleId === id; + let key = `${z}${type}${name}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + + // Calcul the radius of the circle + let radius = getRadius(type, z) * pixelRatio; + const isDisplayStrokeAndDelay = radius >= 7 * pixelRatio; + if (hover || selected) { + radius = isDisplayStrokeAndDelay + ? radius + 5 * pixelRatio + : 14 * pixelRatio; + } + const mustDrawText = radius > 10 * pixelRatio; + + // Optimize the cache key, very important in high zoom level + if (!mustDrawText) { + key = `${z}${type}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + } + + if (!styleCache[key]) { + if (radius === 0) { + styleCache[key] = null; + return null; + } + + const margin = 1 * pixelRatio; + const radiusDelay = radius + 2; + const markerSize = radius * 2; + + const canvas = document.createElement('canvas'); + // add space for delay information + canvas.width = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + canvas.height = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + const ctx = canvas.getContext('2d'); + const origin = canvas.width / 2; + + if (isDisplayStrokeAndDelay && delay !== null) { + // Draw circle delay background + ctx.save(); + ctx.beginPath(); + ctx.arc(origin, origin, radiusDelay, 0, 2 * Math.PI, false); + ctx.fillStyle = getDelayColor(delay, cancelled); + ctx.filter = 'blur(1px)'; + ctx.fill(); + ctx.restore(); + } + + // Show delay if feature is hovered or if delay is above 5mins. + if ( + isDisplayStrokeAndDelay && + (hover || delay >= delayDisplay || cancelled) + ) { + // Draw delay text + ctx.save(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.font = `bold ${Math.max( + cancelled ? 19 : 14, + Math.min(cancelled ? 19 : 17, radius * 1.2), + )}px arial, sans-serif`; + ctx.fillStyle = getDelayColor(delay, cancelled, true); + + ctx.strokeStyle = delayOutlineColor; + ctx.lineWidth = 1.5 * pixelRatio; + const delayText = getDelayText(delay, cancelled); + ctx.strokeText(delayText, origin + radiusDelay + margin, origin); + ctx.fillText(delayText, origin + radiusDelay + margin, origin); + ctx.restore(); + } + + // Draw colored circle with black border + let circleFillColor; + if (useDelayStyle) { + circleFillColor = getDelayColor(delay, cancelled); + } else { + circleFillColor = `#${color}` || getBgColor(type); + } + + ctx.save(); + if (isDisplayStrokeAndDelay || hover || selected) { + ctx.lineWidth = 1 * pixelRatio; + ctx.strokeStyle = '#000000'; + } + ctx.fillStyle = circleFillColor; + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fill(); + // Dashed outline if a provider provides realtime but we don't use it. + if ( + isDisplayStrokeAndDelay && + useDelayStyle && + delay === null && + operatorProvidesRealtime === 'yes' + ) { + ctx.setLineDash([5, 3]); + } + if (isDisplayStrokeAndDelay || hover || selected) { + ctx.stroke(); + } + ctx.restore(); + + // Draw text in the circle + if (mustDrawText) { + const fontSize = Math.max(radius, 10 * pixelRatio); + const textSize = getTextSize(ctx, markerSize, name, fontSize); + + // Draw a stroke to the text only if a provider provides realtime but we don't use it. + if ( + useDelayStyle && + delay === null && + operatorProvidesRealtime === 'yes' + ) { + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.font = `bold ${textSize + 2}px Arial`; + ctx.strokeStyle = circleFillColor; + ctx.strokeText(name, origin, origin); + ctx.restore(); + } + + // Draw a text + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.fillStyle = !useDelayStyle + ? textColor || getTextColor(type) + : '#000000'; + ctx.font = `bold ${textSize}px Arial`; + ctx.strokeStyle = circleFillColor; + ctx.strokeText(name, origin, origin); + ctx.fillText(name, origin, origin); + ctx.restore(); + } + + styleCache[key] = canvas; + } + + return styleCache[key]; +}; +export default style; From 92c0bf68f303ffd4713ed87412a902c5669b0674 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 23 Nov 2021 16:14:41 +0100 Subject: [PATCH 059/109] v1.6.0-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1083bea8..88046e6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.4", + "version": "1.6.0-beta.5", "main": "index.js", "module": "module.js", "dependencies": { From 4b7fd1d144702106a9f75c8e27bbf1c495c138cb Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 25 Nov 2021 14:16:34 +0100 Subject: [PATCH 060/109] v1.6.0-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88046e6c..fe0dda4b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.5", + "version": "1.6.0-beta.6", "main": "index.js", "module": "module.js", "dependencies": { From d8eb29db6f41990b297e55fc8322f827deb3f2d0 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 25 Nov 2021 16:02:38 +0100 Subject: [PATCH 061/109] Fix color --- src/common/utils/trackerStyle.js | 24 ++++++++++++++++++++---- src/doc/examples/ol-tralis.js | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/common/utils/trackerStyle.js b/src/common/utils/trackerStyle.js index 894aacda..699a1634 100644 --- a/src/common/utils/trackerStyle.js +++ b/src/common/utils/trackerStyle.js @@ -34,15 +34,31 @@ const style = (trajectory, viewState, trackerLayer) => { line = {}; } - const { name = 'I', color = '000000' } = line; - let { text_color: textColor } = line; + const { name = 'I' } = line; + let { text_color: textColor, color } = line; + + if (!color) { + color = '#000000'; + } + if (!textColor) { textColor = '#ffffff'; } + + // Make sure color used have a # at the start + if (color[0] !== '#') { + color = `#${color}`; + } + + if (textColor[0] !== '#') { + textColor = `#${textColor}`; + } + console.log(color, textColor); + const z = Math.min(Math.floor(zoom || 1), 16); const hover = hoverVehicleId === id; const selected = selectedVehicleId === id; - let key = `${z}${type}${name}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + let key = `${z}${type}${name}${color}${textColor}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; // Calcul the radius of the circle let radius = getRadius(type, z) * pixelRatio; @@ -115,7 +131,7 @@ const style = (trajectory, viewState, trackerLayer) => { if (useDelayStyle) { circleFillColor = getDelayColor(delay, cancelled); } else { - circleFillColor = `#${color}` || getBgColor(type); + circleFillColor = color || getBgColor(type); } ctx.save(); diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 49ec202d..9e33c015 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -1,5 +1,4 @@ import View from 'ol/View'; -import { fromLonLat } from 'ol/proj'; import { Map, TralisLayer, MapboxLayer } from '../../ol'; import 'ol/ol.css'; import CopyrightControl from '../../ol/controls/CopyrightControl'; @@ -32,6 +31,7 @@ export default () => { apiKey: window.apiKey, // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, + useDelayStyle: false, style: (obj, viewState) => { return trackerStyle(obj, viewState, tracker); }, From 7b4ab10965a4b4bf35fcb2ba31e267b99a6d63b9 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 25 Nov 2021 16:02:52 +0100 Subject: [PATCH 062/109] v1.6.0-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe0dda4b..c74223cb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.6", + "version": "1.6.0-beta.7", "main": "index.js", "module": "module.js", "dependencies": { From 1d59f92fa735cb3ea76901650c76382cad77613e Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 16:09:42 +0100 Subject: [PATCH 063/109] Add filters and useDelayXXX properety to TrackerLayer --- src/common/mixins/TrackerLayerMixin.js | 99 ++++++++++++ src/common/mixins/TrajservLayerMixin.js | 142 +----------------- src/common/mixins/TralisLayerMixin.js | 6 + src/common/trackerConfig.js | 29 ++-- src/common/trackerConfig.test.js | 38 +++++ src/common/utils/createTrackerFilters.js | 82 ++++++++++ src/common/utils/createTrackerFilters.test.js | 89 +++++++++++ src/common/utils/trackerStyle.js | 46 +++--- src/doc/examples/ol-tralis.js | 3 +- src/ol/layers/TralisLayer.js | 16 +- 10 files changed, 364 insertions(+), 186 deletions(-) create mode 100644 src/common/trackerConfig.test.js create mode 100644 src/common/utils/createTrackerFilters.js create mode 100644 src/common/utils/createTrackerFilters.test.js diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 4f6376b1..aa9aa82d 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -3,8 +3,15 @@ import { buffer, containsCoordinate } from 'ol/extent'; import { unByKey } from 'ol/Observable'; import Feature from 'ol/Feature'; +import qs from 'query-string'; import Tracker from '../Tracker'; import { timeSteps } from '../trackerConfig'; +import createFilters from '../utils/createTrackerFilters'; + +/* Permalink parameter used to filters vehicles */ +const LINE_FILTER = 'publishedlinename'; +const ROUTE_FILTER = 'tripnumber'; +const OPERATOR_FILTER = 'operator'; /** * TrackerLayerInterface. @@ -129,6 +136,13 @@ const TrackerLayerMixin = (Base) => live, } = options; + let { + regexPublishedLineName, + publishedLineName, + tripNumber, + operator, + } = options; + const initTrackerOptions = { pixelRatio: pixelRatio || window.devicePixelRatio || 1, interpolate, @@ -314,6 +328,74 @@ const TrackerLayerMixin = (Base) => default: false, writable: true, }, + + /** + * Filter properties used in combination with permalink parameters. + */ + publishedLineName: { + get: () => { + return publishedLineName; + }, + set: (newPublishedLineName) => { + publishedLineName = newPublishedLineName; + this.updateFilters(); + }, + }, + tripNumber: { + get: () => { + return tripNumber; + }, + set: (newTripNumber) => { + tripNumber = newTripNumber; + this.updateFilters(); + }, + }, + operator: { + get: () => { + return operator; + }, + set: (newOperator) => { + operator = newOperator; + this.updateFilters(); + }, + }, + regexPublishedLineName: { + get: () => { + return regexPublishedLineName; + }, + set: (newRegex) => { + regexPublishedLineName = newRegex; + this.updateFilters(); + }, + }, + + /** + * Style properties. + */ + delayDisplay: { + value: options.delayDisplay || 300000, + writable: true, + }, + delayOutlineColor: { + value: options.delayOutlineColor || '#000000', + writable: true, + }, + useDelayStyle: { + value: options.useDelayStyle || false, + writable: true, + }, + + /** + * Debug properties. + */ + // Not used anymore, but could be useful for debugging. + // showVehicleTraj: { + // value: + // options.showVehicleTraj !== undefined + // ? options.showVehicleTraj + // : true, + // writable: true, + // }, }); } @@ -375,6 +457,7 @@ const TrackerLayerMixin = (Base) => */ start() { this.stop(); + this.updateFilters(); this.tracker.setVisible(true); this.renderTrajectories(); this.startUpdateTime(); @@ -591,6 +674,22 @@ const TrackerLayerMixin = (Base) => return nextTick; } + /** + * Update filter provided by properties or permalink. + */ + updateFilters() { + // Setting filters from the permalink if no values defined by the layer. + const parameters = qs.parse(window.location.search.toLowerCase()); + // filter is the property in TrackerLayerMixin. + console.log(this.regexPublishedLineName); + this.filter = createFilters( + this.publishedLineName || parameters[LINE_FILTER], + this.tripNumber || parameters[ROUTE_FILTER], + this.operator || parameters[OPERATOR_FILTER], + this.regexPublishedLineName, + ); + } + /** * @private */ diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index b5bce3f7..1984ca54 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -101,74 +101,6 @@ export class TrajservLayerInterface { defaultStyle(trajectory, viewState) {} } -const LINE_FILTER = 'publishedlinename'; -const ROUTE_FILTER = 'tripnumber'; -const OPERATOR_FILTER = 'operator'; - -/** - * Create a array of filter functions based on some parameters. - * @param {string} line - * @param {string} route - * @param {string} operator - * @param {string} regexLine - * @private - */ -const createFilters = (line, route, operator, regexLine) => { - const filterList = []; - - if (!line && !route && !operator && !regexLine) { - return null; - } - - if (regexLine) { - const regexLineList = - typeof regexLine === 'string' ? [regexLine] : regexLine; - const lineFilter = (t) => - regexLineList.some((tr) => new RegExp(tr, 'i').test(t.name)); - filterList.push(lineFilter); - } - - if (line) { - const lineFiltersList = typeof line === 'string' ? line.split(',') : line; - const lineList = lineFiltersList.map((l) => - l.replace(/\s+/g, '').toUpperCase(), - ); - const lineFilter = (l) => - lineList.some((filter) => filter === l.name.toUpperCase()); - filterList.push(lineFilter); - } - - if (route) { - const routes = typeof route === 'string' ? route.split(',') : route; - const routeList = routes.map((item) => parseInt(item, 10)); - const routeFilter = (item) => { - const routeId = parseInt(item.routeIdentifier.split('.')[0], 10); - return routeList.some((id) => id === routeId); - }; - filterList.push(routeFilter); - } - - if (operator) { - const operatorList = typeof operator === 'string' ? [operator] : operator; - const operatorFilter = (t) => - operatorList.some((op) => new RegExp(op, 'i').test(t.operator)); - filterList.push(operatorFilter); - } - - if (!filterList.length) { - return null; - } - - return (t) => { - for (let i = 0; i < filterList.length; i += 1) { - if (!filterList[i](t)) { - return false; - } - } - return true; - }; -}; - /** * Mixin for TrajservLayerInterface. * @@ -185,12 +117,6 @@ const TrajservLayerMixin = (TrackerLayer) => */ defineProperties(options) { super.defineProperties(options); - let { - regexPublishedLineName, - publishedLineName, - tripNumber, - operator, - } = options; let requestIntervalSeconds = 3; let defaultApi; @@ -205,25 +131,6 @@ const TrajservLayerMixin = (TrackerLayer) => defaultApi = new TrajservAPI(apiOptions); } Object.defineProperties(this, { - showVehicleTraj: { - value: - options.showVehicleTraj !== undefined - ? options.showVehicleTraj - : true, - writable: true, - }, - delayDisplay: { - value: options.delayDisplay || 300000, - writable: true, - }, - delayOutlineColor: { - value: options.delayOutlineColor || '#000000', - writable: true, - }, - useDelayStyle: { - value: options.useDelayStyle || false, - writable: true, - }, requestIntervalSeconds: { get: () => { return requestIntervalSeconds; @@ -238,42 +145,7 @@ const TrajservLayerMixin = (TrackerLayer) => } }, }, - publishedLineName: { - get: () => { - return publishedLineName; - }, - set: (newPublishedLineName) => { - publishedLineName = newPublishedLineName; - this.updateFilters(); - }, - }, - tripNumber: { - get: () => { - return tripNumber; - }, - set: (newTripNumber) => { - tripNumber = newTripNumber; - this.updateFilters(); - }, - }, - operator: { - get: () => { - return operator; - }, - set: (newOperator) => { - operator = newOperator; - this.updateFilters(); - }, - }, - regexPublishedLineName: { - get: () => { - return regexPublishedLineName; - }, - set: (newRegex) => { - regexPublishedLineName = newRegex; - this.updateFilters(); - }, - }, + api: { value: options.api || defaultApi, }, @@ -407,18 +279,6 @@ const TrajservLayerMixin = (TrackerLayer) => }); } - updateFilters() { - // Setting filters from the permalink if no values defined by the layer. - const parameters = qs.parse(window.location.search.toLowerCase()); - // filter is the property in TrackerLayerMixin. - this.filter = createFilters( - this.publishedLineName || parameters[LINE_FILTER], - this.tripNumber || parameters[ROUTE_FILTER], - this.operator || parameters[OPERATOR_FILTER], - this.regexPublishedLineName, - ); - } - abortFetchTrajectories() { if (this.abortController) { this.abortController.abort(); diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index ff3b4c35..4d089ad0 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -164,6 +164,12 @@ const TralisLayerMixin = (TrackerLayer) => if (!data.content) { return; } + + // Temporary fix to avoid displaying old trains cached . + if (data.content.properties.mot) { + return; + } + const feat = this.format.readFeature(data.content); feat.set('timeOffset', Date.now() - data.timestamp); diff --git a/src/common/trackerConfig.js b/src/common/trackerConfig.js index d2a4c440..30c5eb4e 100644 --- a/src/common/trackerConfig.js +++ b/src/common/trackerConfig.js @@ -12,19 +12,23 @@ const trackerRadiusMapping = { }; /** + * Trajserv value: 'Tram', 'Subway / Metro / S-Bahn', 'Train', 'Bus', 'Ferry', 'Cable Car', 'Gondola', 'Funicular', 'Long distance bus', 'Rail', + * New endpoint use Rail instead of Train. + * New tracker values: null, "tram", "subway", "rail", "bus", "ferry", "cablecar", "gondola", "funicular", "coach". + * * @ignore */ export const types = [ - 'Tram', - 'Subway / Metro / S-Bahn', - 'Train', - 'Bus', - 'Ferry', - 'Cable Car', - 'Gondola', - 'Funicular', - 'Long distance bus', - 'Rail', // New endpoint use Rail instead of Train. + /^Tram/i, + /^Subway( \/ Metro \/ S-Bahn)?/i, + /^Train/i, + /^Bus/i, + /^Ferry/i, + /^Cable ?Car/i, + /^Gondola/i, + /^Funicular/i, + /^(Long distance bus|coach)/i, + /^Rail/i, // New endpoint use Rail instead of Train. ]; /** @@ -89,10 +93,9 @@ export const timeSteps = [ /** * @ignore */ -const getTypeIndex = (type) => { +export const getTypeIndex = (type) => { if (typeof type === 'string') { - const matched = types.find((t) => new RegExp(type).test(t)); - return types.indexOf(matched); + return types.findIndex((t) => t.test(type)); } return type; }; diff --git a/src/common/trackerConfig.test.js b/src/common/trackerConfig.test.js new file mode 100644 index 00000000..035467ad --- /dev/null +++ b/src/common/trackerConfig.test.js @@ -0,0 +1,38 @@ +import { getTypeIndex } from './trackerConfig'; + +describe('trackerConfig', () => { + describe('#getTypeIndex()', () => { + test("retrurn the type is it's not a string", () => { + const obj = { foo: 'foo' }; + expect(getTypeIndex(obj)).toBe(obj); + expect(getTypeIndex(0)).toBe(0); + expect(getTypeIndex(null)).toBe(null); + expect(getTypeIndex(undefined)).toBe(undefined); + }); + + test('find good index for old trajserv values', () => { + expect(getTypeIndex('Tram')).toBe(0); + expect(getTypeIndex('Subway / Metro / S-Bahn')).toBe(1); + expect(getTypeIndex('Train')).toBe(2); + expect(getTypeIndex('Bus')).toBe(3); + expect(getTypeIndex('Ferry')).toBe(4); + expect(getTypeIndex('Cable Car')).toBe(5); + expect(getTypeIndex('Gondola')).toBe(6); + expect(getTypeIndex('Funicular')).toBe(7); + expect(getTypeIndex('Long distance bus')).toBe(8); + expect(getTypeIndex('Rail')).toBe(9); + }); + + test('find good index for new tracker values', () => { + expect(getTypeIndex('tram')).toBe(0); + expect(getTypeIndex('subway')).toBe(1); + expect(getTypeIndex('bus')).toBe(3); + expect(getTypeIndex('ferry')).toBe(4); + expect(getTypeIndex('cablecar')).toBe(5); + expect(getTypeIndex('gondola')).toBe(6); + expect(getTypeIndex('funicular')).toBe(7); + expect(getTypeIndex('coach')).toBe(8); + expect(getTypeIndex('rail')).toBe(9); + }); + }); +}); diff --git a/src/common/utils/createTrackerFilters.js b/src/common/utils/createTrackerFilters.js new file mode 100644 index 00000000..4ad553d2 --- /dev/null +++ b/src/common/utils/createTrackerFilters.js @@ -0,0 +1,82 @@ +/** + * Return a filter functions based on some parameters of a vehicle. + * + * @param {string|Array} line - A list of vehicle's name to filter. Names can be separated by a comma. Ex: 'S1,S2,S3' + * @param {string|Array { + const filterList = []; + + if (!line && !route && !operator && !regexLine) { + return null; + } + + if (regexLine) { + const regexLineList = + typeof regexLine === 'string' ? [regexLine] : regexLine; + const lineFilter = (item) => { + const name = item.name || (item.line && item.line.name) || ''; + if (!name) { + return false; + } + return regexLineList.some((regexStr) => + new RegExp(regexStr, 'i').test(name), + ); + }; + filterList.push(lineFilter); + } + + if (line) { + const lineFiltersList = typeof line === 'string' ? line.split(',') : line; + const lineList = lineFiltersList.map((l) => + l.replace(/\s+/g, '').toUpperCase(), + ); + const lineFilter = (item) => { + const name = ( + item.name || + (item.line && item.line.name) || + '' + ).toUpperCase(); + if (!name) { + return false; + } + return lineList.includes(name); + }; + filterList.push(lineFilter); + } + + if (route) { + const routes = typeof route === 'string' ? route.split(',') : route; + const routeList = routes.map((item) => parseInt(item, 10)); + const routeFilter = (item) => { + const routeId = parseInt(item.routeIdentifier.split('.')[0], 10); + return routeList.includes(routeId); + }; + filterList.push(routeFilter); + } + + if (operator) { + const operatorList = typeof operator === 'string' ? [operator] : operator; + const operatorFilter = (item) => + operatorList.some((op) => new RegExp(op, 'i').test(item.operator)); + filterList.push(operatorFilter); + } + + if (!filterList.length) { + return null; + } + + return (t) => { + for (let i = 0; i < filterList.length; i += 1) { + if (!filterList[i](t)) { + return false; + } + } + return true; + }; +}; + +export default createFilters; diff --git a/src/common/utils/createTrackerFilters.test.js b/src/common/utils/createTrackerFilters.test.js new file mode 100644 index 00000000..61fb96aa --- /dev/null +++ b/src/common/utils/createTrackerFilters.test.js @@ -0,0 +1,89 @@ +import createTrackerFilters from './createTrackerFilters'; + +const u1 = { + routeIdentifier: '001.000827.004:7', + operator: 'FoO', + line: { + name: 'U1', + }, +}; +const ireta = { + routeIdentifier: '0022.000827.004:7', + operator: 'BAR', + line: { + name: 'IRETA', + }, +}; +const arb = { + routeIdentifier: '00333.000827.004:7', + operator: 'qux', + line: { + name: 'ARB', + }, +}; + +const trajectories = [u1, ireta, arb]; + +describe('#createTrackerFilter()', () => { + test('returns null', () => { + const filterFunc = createTrackerFilters(); + expect(filterFunc).toBe(null); + }); + + describe('using line', () => { + test('as string', () => { + const filterFunc = createTrackerFilters('u1,foo'); + expect(trajectories.filter(filterFunc)).toEqual([u1]); + }); + + test('as array of string', () => { + const filterFunc = createTrackerFilters(['u1', 'foo', 'IRETA']); + expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]); + }); + }); + + describe('using route identifier', () => { + test('as string', () => { + const filterFunc = createTrackerFilters(null, '1,foo'); + expect(trajectories.filter(filterFunc)).toEqual([u1]); + }); + + test('as array of string', () => { + const filterFunc = createTrackerFilters(null, ['22', 'foo', '1']); + expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]); + }); + }); + + describe('using operator', () => { + test('as string', () => { + const filterFunc = createTrackerFilters(null, null, 'foo'); + expect(trajectories.filter(filterFunc)).toEqual([u1]); + }); + + test('as array of string', () => { + const filterFunc = createTrackerFilters(null, null, ['bar', 'foo', '1']); + expect(trajectories.filter(filterFunc)).toEqual([u1, ireta]); + }); + }); + + describe('using regexLine', () => { + test('as string', () => { + const filterFunc = createTrackerFilters( + null, + null, + null, + '^(S|R$|RE|PE|D|IRE|RB|TER)', + ); + expect(trajectories.filter(filterFunc)).toEqual([ireta]); + }); + + test('as array of string', () => { + const filterFunc = createTrackerFilters(null, null, null, [ + '^IR', + '^ARB$', + 'foo', + ]); + expect(trajectories.filter(filterFunc)).toEqual([ireta, arb]); + }); + }); +}); diff --git a/src/common/utils/trackerStyle.js b/src/common/utils/trackerStyle.js index 699a1634..e079ae5b 100644 --- a/src/common/utils/trackerStyle.js +++ b/src/common/utils/trackerStyle.js @@ -18,47 +18,43 @@ const style = (trajectory, viewState, trackerLayer) => { delayDisplay, } = trackerLayer; - const { zoom, pixelRatio } = viewState; - - let { line = {} } = trajectory; - const { - id, - delay, - type = 'Rail', - cancelled = false, - operatorProvidesRealtime = 'no', - } = trajectory; + zoom, + pixelRatio, + operator_provides_realtime_journey: operatorProvidesRealtime, + } = viewState; + let { line, type } = trajectory; + const { id, delay, cancelled = false } = trajectory; + + if (!type) { + type = 'Rail'; + } if (!line) { line = {}; } - const { name = 'I' } = line; - let { text_color: textColor, color } = line; + let { name, text_color: textColor, color } = line; - if (!color) { - color = '#000000'; + if (!name) { + name = 'I'; } - + // console.log(type); if (!textColor) { - textColor = '#ffffff'; + textColor = '#000000'; } - // Make sure color used have a # at the start - if (color[0] !== '#') { + if (color && color[0] !== '#') { color = `#${color}`; } if (textColor[0] !== '#') { textColor = `#${textColor}`; } - console.log(color, textColor); const z = Math.min(Math.floor(zoom || 1), 16); const hover = hoverVehicleId === id; const selected = selectedVehicleId === id; - let key = `${z}${type}${name}${color}${textColor}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; // Calcul the radius of the circle let radius = getRadius(type, z) * pixelRatio; @@ -71,8 +67,14 @@ const style = (trajectory, viewState, trackerLayer) => { const mustDrawText = radius > 10 * pixelRatio; // Optimize the cache key, very important in high zoom level - if (!mustDrawText) { - key = `${z}${type}${color}${operatorProvidesRealtime}${delay}${hover}${selected}${cancelled}`; + let key = `${z}${type}${color}${hover}${selected}${cancelled}${delay}`; + + if (useDelayStyle) { + key += `${operatorProvidesRealtime}`; + } + + if (mustDrawText) { + key += `${name}${textColor}`; } if (!styleCache[key]) { diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 9e33c015..2c066ffe 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -31,7 +31,8 @@ export default () => { apiKey: window.apiKey, // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, - useDelayStyle: false, + useDelayStyle: true, + regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', style: (obj, viewState) => { return trackerStyle(obj, viewState, tracker); }, diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index d17d5448..ae79dcbb 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -52,6 +52,7 @@ class TralisLayer extends mixin(TrackerLayer) { * @private */ highlightTrajectory() { + console.log('ici'); super.highlightTrajectory().then(({ stopSequence, fullTrajectory }) => { const vectorSource = this.vectorLayer.getSource(); vectorSource.clear(); @@ -65,15 +66,12 @@ class TralisLayer extends mixin(TrackerLayer) { // const lineColor = color ? `#${color}` : getBgColor(color); // // Don't allow white lines, use red instead. // const vehiculeColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor; - - if ( - stopSequence && - stopSequence.stations && - stopSequence.stations.length && - stopSequence.stations[0].coordinate - ) { + stopSequence.forEach((sequence) => { + if (!sequence.stations) { + return; + } const geometry = new MultiPoint( - stopSequence.stations.map((station) => station.coordinates), + sequence.stations.map((station) => station.coordinate), ); const aboveStationsFeature = new Feature(geometry); @@ -101,7 +99,7 @@ class TralisLayer extends mixin(TrackerLayer) { }), ); vectorSource.addFeatures([aboveStationsFeature, belowStationsFeature]); - } + }); if (fullTrajectory) { const features = format.readFeatures(fullTrajectory); From 380dfeed62fdfbfb81ba2e2c9e70e4a2ca26fb59 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 16:35:05 +0100 Subject: [PATCH 064/109] USe good color for fullTrajectory --- src/common/mixins/TrackerLayerMixin.js | 3 +- src/common/utils/trackerStyle.js | 2 +- src/doc/examples/ol-tralis.js | 2 +- src/mapbox/layers/TralisLayer.js | 1 - src/ol/layers/TralisLayer.js | 38 +++++++++++++++++--------- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index aa9aa82d..84bece61 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -670,7 +670,7 @@ const TrackerLayerMixin = (Base) => const roundedZoom = Math.round(zoom); const timeStep = timeSteps[roundedZoom] || 25; const nextTick = Math.max(25, timeStep / this.speed); - console.log(`Next render in ${nextTick} ms.`); + // console.log(`Next render in ${nextTick} ms.`); return nextTick; } @@ -681,7 +681,6 @@ const TrackerLayerMixin = (Base) => // Setting filters from the permalink if no values defined by the layer. const parameters = qs.parse(window.location.search.toLowerCase()); // filter is the property in TrackerLayerMixin. - console.log(this.regexPublishedLineName); this.filter = createFilters( this.publishedLineName || parameters[LINE_FILTER], this.tripNumber || parameters[ROUTE_FILTER], diff --git a/src/common/utils/trackerStyle.js b/src/common/utils/trackerStyle.js index e079ae5b..f336c163 100644 --- a/src/common/utils/trackerStyle.js +++ b/src/common/utils/trackerStyle.js @@ -39,7 +39,7 @@ const style = (trajectory, viewState, trackerLayer) => { if (!name) { name = 'I'; } - // console.log(type); + if (!textColor) { textColor = '#000000'; } diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 2c066ffe..3094fb57 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -32,7 +32,7 @@ export default () => { // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, useDelayStyle: true, - regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', + // regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', style: (obj, viewState) => { return trackerStyle(obj, viewState, tracker); }, diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 19cc244d..f780eb5e 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -29,7 +29,6 @@ class TralisLayer extends mixin(TrackerLayer) { */ onMoveEnd(evt) { super.onMoveEnd(evt); - console.log('onMoveEnd'); if (this.isUpdateBboxOnMoveEnd) { const bounds = this.map.getBounds().toArray(); diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index ae79dcbb..c19a1c85 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -4,6 +4,7 @@ import { MultiPoint } from 'ol/geom'; import { Style, Fill, Stroke, Circle } from 'ol/style'; import TrackerLayer from './TrackerLayer'; import mixin from '../../common/mixins/TralisLayerMixin'; +import { getBgColor } from '../../common/trackerConfig'; const format = new GeoJSON(); @@ -52,20 +53,31 @@ class TralisLayer extends mixin(TrackerLayer) { * @private */ highlightTrajectory() { - console.log('ici'); super.highlightTrajectory().then(({ stopSequence, fullTrajectory }) => { const vectorSource = this.vectorLayer.getSource(); vectorSource.clear(); - const color = - (stopSequence && - stopSequence[0] && - stopSequence[0].color && - `#${stopSequence[0].color}`) || - '#000000'; + let lineColor = '#ffffff'; // white + + if (this.useDelayStyle) { + lineColor = '#a0a0a0'; // grey + } else { + // We get the color of the first feature. + if (fullTrajectory) { + const props = fullTrajectory.features[0].properties; + const { type } = props; + let { stroke } = props; + + if (stroke && stroke[0] !== '#') { + stroke = `#${stroke}`; + } + + lineColor = stroke || getBgColor(type); + } + + // Don't allow white lines, use red instead. + lineColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor; + } - // const lineColor = color ? `#${color}` : getBgColor(color); - // // Don't allow white lines, use red instead. - // const vehiculeColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor; stopSequence.forEach((sequence) => { if (!sequence.stations) { return; @@ -93,7 +105,7 @@ class TralisLayer extends mixin(TrackerLayer) { image: new Circle({ radius: 4, fill: new Fill({ - color, + color: lineColor, }), }), }), @@ -102,7 +114,6 @@ class TralisLayer extends mixin(TrackerLayer) { }); if (fullTrajectory) { - const features = format.readFeatures(fullTrajectory); const style = [ new Style({ zIndex: 2, @@ -114,11 +125,12 @@ class TralisLayer extends mixin(TrackerLayer) { new Style({ zIndex: 3, stroke: new Stroke({ - color, + color: lineColor, width: 4, }), }), ]; + const features = format.readFeatures(fullTrajectory); features.forEach((feature) => { feature.setStyle(style); }); From 3eb2abf5994ed3e79f725729fbbc1182b7c61c27 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 16:35:52 +0100 Subject: [PATCH 065/109] v1.6.0-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c74223cb..4b93328f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.7", + "version": "1.6.0-beta.8", "main": "index.js", "module": "module.js", "dependencies": { From d72a434fdc8cf5c2bc127bce6125074a095c1287 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 17:07:57 +0100 Subject: [PATCH 066/109] Use delayTrackerStyle by default --- src/common/mixins/TrackerLayerMixin.js | 26 +++---------------- src/common/mixins/TralisLayerMixin.js | 5 ---- .../{trackerStyle.js => delayTrackerStyle.js} | 12 +++++++-- src/common/utils/index.js | 3 ++- src/common/utils/simpleTrackerStyle.js | 20 ++++++++++++++ src/doc/examples/ol-tralis.js | 10 +------ 6 files changed, 36 insertions(+), 40 deletions(-) rename src/common/utils/{trackerStyle.js => delayTrackerStyle.js} (94%) create mode 100644 src/common/utils/simpleTrackerStyle.js diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 84bece61..d981a2f9 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -7,6 +7,7 @@ import qs from 'query-string'; import Tracker from '../Tracker'; import { timeSteps } from '../trackerConfig'; import createFilters from '../utils/createTrackerFilters'; +import { delayTrackerStyle } from '../utils'; /* Permalink parameter used to filters vehicles */ const LINE_FILTER = 'publishedlinename'; @@ -692,29 +693,8 @@ const TrackerLayerMixin = (Base) => /** * @private */ - defaultStyle(trajectory) { - const { id: text } = trajectory; - if (this.styleCache[text]) { - return this.styleCache[text]; - } - const canvas = document.createElement('canvas'); - canvas.width = 200; - canvas.height = 15; - const ctx = canvas.getContext('2d'); - ctx.arc(8, 8, 5, 0, 2 * Math.PI, false); - ctx.fillStyle = '#8ED6FF'; - ctx.fill(); - ctx.lineWidth = 3; - ctx.strokeStyle = 'black'; - ctx.stroke(); - ctx.font = 'bold 12px arial'; - ctx.strokeStyle = 'white'; - ctx.lineWidth = 3; - ctx.strokeText(text, 20, 10); - ctx.fillStyle = 'black'; - ctx.fillText(text, 20, 10); - this.styleCache[text] = canvas; - return this.styleCache[text]; + defaultStyle(trajectory, viewState) { + return delayTrackerStyle(trajectory, viewState, this); } }; diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 4d089ad0..e71f304f 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -165,11 +165,6 @@ const TralisLayerMixin = (TrackerLayer) => return; } - // Temporary fix to avoid displaying old trains cached . - if (data.content.properties.mot) { - return; - } - const feat = this.format.readFeature(data.content); feat.set('timeOffset', Date.now() - data.timestamp); diff --git a/src/common/utils/trackerStyle.js b/src/common/utils/delayTrackerStyle.js similarity index 94% rename from src/common/utils/trackerStyle.js rename to src/common/utils/delayTrackerStyle.js index f336c163..f4beb738 100644 --- a/src/common/utils/trackerStyle.js +++ b/src/common/utils/delayTrackerStyle.js @@ -9,14 +9,22 @@ import { const styleCache = {}; -const style = (trajectory, viewState, trackerLayer) => { +/** + * A tracker style that take in account the delay. + * + * @param {*} trajectory The trajectory to render. + * @param {*} viewState The view state of the map. + * @param {*} options Some options to change the rendering + * @returns a canvas + */ +const style = (trajectory, viewState, options) => { const { hoverVehicleId, selectedVehicleId, useDelayStyle, delayOutlineColor, delayDisplay, - } = trackerLayer; + } = options; const { zoom, diff --git a/src/common/utils/index.js b/src/common/utils/index.js index 33402bd7..617a91ae 100644 --- a/src/common/utils/index.js +++ b/src/common/utils/index.js @@ -1,4 +1,5 @@ export { default as getMapboxStyleUrl } from './getMapboxStyleUrl'; export { default as getMapboxMapCopyrights } from './getMapboxMapCopyrights'; export { default as removeDuplicate } from './removeDuplicate'; -export { default as trackerStyle } from './trackerStyle'; +export { default as delayTrackerStyle } from './delayTrackerStyle'; +export { default as simpleTrackerStyle } from './simpleTrackerStyle'; diff --git a/src/common/utils/simpleTrackerStyle.js b/src/common/utils/simpleTrackerStyle.js new file mode 100644 index 00000000..aff611f4 --- /dev/null +++ b/src/common/utils/simpleTrackerStyle.js @@ -0,0 +1,20 @@ +/** + * A very simple tracker style. + * Display blue point for each train. + */ +const canvas = document.createElement('canvas'); +canvas.width = 15; +canvas.height = 15; +const ctx = canvas.getContext('2d'); +ctx.arc(8, 8, 5, 0, 2 * Math.PI, false); +ctx.fillStyle = '#8ED6FF'; +ctx.fill(); +ctx.lineWidth = 3; +ctx.strokeStyle = 'black'; +ctx.stroke(); +ctx.lineWidth = 3; + +const style = () => { + return canvas; +}; +export default style; diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 3094fb57..76897ee9 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -2,11 +2,6 @@ import View from 'ol/View'; import { Map, TralisLayer, MapboxLayer } from '../../ol'; import 'ol/ol.css'; import CopyrightControl from '../../ol/controls/CopyrightControl'; -import { trackerStyle } from '../../common/utils'; -// import LINE_IMAGES from './assets/tralis-live-map'; - -// const min = [1254752.0378, 6115573.759]; -// const max = [1321443.345, 6148938.5219]; export default () => { const map = new Map({ @@ -31,11 +26,8 @@ export default () => { apiKey: window.apiKey, // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, - useDelayStyle: true, + // useDelayStyle: true, // regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', - style: (obj, viewState) => { - return trackerStyle(obj, viewState, tracker); - }, }); tracker.onClick(([feature]) => { From a17142c379e7212ba594be8d869cb45a645f7071 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 17:21:21 +0100 Subject: [PATCH 067/109] Use optimized version of the style --- src/common/utils/delayTrackerStyle.js | 263 +++++++++++++++++++------- src/doc/examples/ol-tralis.js | 2 +- 2 files changed, 193 insertions(+), 72 deletions(-) diff --git a/src/common/utils/delayTrackerStyle.js b/src/common/utils/delayTrackerStyle.js index f4beb738..16adfe6c 100644 --- a/src/common/utils/delayTrackerStyle.js +++ b/src/common/utils/delayTrackerStyle.js @@ -8,6 +8,140 @@ import { } from '../trackerConfig'; const styleCache = {}; +const cacheDelayBg = {}; + +const createCanvas = (width, height) => { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +}; + +// Draw circle delay background +export const getDelayBgCanvas = (origin, radius, color) => { + const key = `${origin}, ${radius}, ${color}`; + if (!cacheDelayBg[key]) { + // console.log('cacheDelayBg'); + const canvas = createCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fillStyle = color; + ctx.filter = 'blur(1px)'; + ctx.fill(); + cacheDelayBg[key] = canvas; + } + return cacheDelayBg[key]; +}; + +// Draw delay text +const cacheDelayText = {}; +export const getDelayTextCanvas = ( + width, + text, + fontSize, + font, + delayColor, + delayOutlineColor, + pixelRatio, +) => { + const key = `${width}, ${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`; + if (!cacheDelayText[key]) { + const canvas = createCanvas(width, fontSize + 8); + const ctx = canvas.getContext('2d'); + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.font = font; + ctx.fillStyle = delayColor; + ctx.strokeStyle = delayOutlineColor; + ctx.lineWidth = 1.5 * pixelRatio; + const delayText = text; + ctx.strokeText(delayText, 0, fontSize); + ctx.fillText(delayText, 0, fontSize); + cacheDelayText[key] = canvas; + } + return cacheDelayText[key]; +}; + +// Draw colored circle with black border +const cacheCircle = {}; +export const getCircleCanvas = ( + origin, + radius, + color, + hasStroke, + hasDash, + pixelRatio, +) => { + const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`; + if (!cacheCircle[key]) { + // console.log('cacheDelayBg'); + const canvas = createCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + ctx.fillStyle = color; + + if (hasStroke) { + ctx.lineWidth = 1 * pixelRatio; + ctx.strokeStyle = '#000000'; + } + + ctx.beginPath(); + ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); + ctx.fill(); + + if (hasDash) { + ctx.setLineDash([5, 3]); + } + + if (hasStroke) { + ctx.stroke(); + } + + cacheCircle[key] = canvas; + } + return cacheCircle[key]; +}; + +// Draw text in the circle +const cacheText = {}; +export const getTextCanvas = ( + text, + origin, + textSize, + fillColor, + strokeColor, + hasStroke, + pixelRatio, +) => { + const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`; + if (!cacheText[key]) { + const canvas = createCanvas(origin * 2, origin * 2); + const ctx = canvas.getContext('2d'); + + // Draw a stroke to the text only if a provider provides realtime but we don't use it. + if (hasStroke) { + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.font = `bold ${textSize + 2}px Arial`; + ctx.strokeStyle = strokeColor; + ctx.strokeText(text, origin, origin); + ctx.restore(); + } + + // Draw a text + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.fillStyle = fillColor; + ctx.font = `bold ${textSize}px Arial`; + ctx.strokeStyle = strokeColor; + ctx.strokeText(text, origin, origin); + ctx.fillText(text, origin, origin); + + cacheText[key] = canvas; + } + return cacheText[key]; +}; /** * A tracker style that take in account the delay. @@ -94,23 +228,22 @@ const style = (trajectory, viewState, options) => { const margin = 1 * pixelRatio; const radiusDelay = radius + 2; const markerSize = radius * 2; + const textWidth = 100; + const size = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; // add space for delay information + const origin = size / 2; - const canvas = document.createElement('canvas'); - // add space for delay information - canvas.width = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; - canvas.height = radiusDelay * 2 + margin * 2 + 100 * pixelRatio; + // Create the canvas + const canvas = createCanvas(size, size); const ctx = canvas.getContext('2d'); - const origin = canvas.width / 2; if (isDisplayStrokeAndDelay && delay !== null) { // Draw circle delay background - ctx.save(); - ctx.beginPath(); - ctx.arc(origin, origin, radiusDelay, 0, 2 * Math.PI, false); - ctx.fillStyle = getDelayColor(delay, cancelled); - ctx.filter = 'blur(1px)'; - ctx.fill(); - ctx.restore(); + const delayBg = getDelayBgCanvas( + origin, + radiusDelay, + getDelayColor(delay, cancelled), + ); + ctx.drawImage(delayBg, 0, 0); } // Show delay if feature is hovered or if delay is above 5mins. @@ -119,21 +252,24 @@ const style = (trajectory, viewState, options) => { (hover || delay >= delayDisplay || cancelled) ) { // Draw delay text - ctx.save(); - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - ctx.font = `bold ${Math.max( + const fontSize = Math.max( cancelled ? 19 : 14, Math.min(cancelled ? 19 : 17, radius * 1.2), - )}px arial, sans-serif`; - ctx.fillStyle = getDelayColor(delay, cancelled, true); - - ctx.strokeStyle = delayOutlineColor; - ctx.lineWidth = 1.5 * pixelRatio; - const delayText = getDelayText(delay, cancelled); - ctx.strokeText(delayText, origin + radiusDelay + margin, origin); - ctx.fillText(delayText, origin + radiusDelay + margin, origin); - ctx.restore(); + ); + const delayText = getDelayTextCanvas( + textWidth, + getDelayText(delay, cancelled), + fontSize, + `bold ${fontSize}px arial, sans-serif`, + getDelayColor(delay, cancelled, true), + delayOutlineColor, + pixelRatio, + ); + ctx.drawImage( + delayText, + origin + radiusDelay + margin, + origin - fontSize, + ); } // Draw colored circle with black border @@ -144,61 +280,46 @@ const style = (trajectory, viewState, options) => { circleFillColor = color || getBgColor(type); } - ctx.save(); - if (isDisplayStrokeAndDelay || hover || selected) { - ctx.lineWidth = 1 * pixelRatio; - ctx.strokeStyle = '#000000'; - } - ctx.fillStyle = circleFillColor; - ctx.beginPath(); - ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); - ctx.fill(); - // Dashed outline if a provider provides realtime but we don't use it. - if ( + const hasStroke = isDisplayStrokeAndDelay || hover || selected; + + const hasDash = isDisplayStrokeAndDelay && useDelayStyle && delay === null && - operatorProvidesRealtime === 'yes' - ) { - ctx.setLineDash([5, 3]); - } - if (isDisplayStrokeAndDelay || hover || selected) { - ctx.stroke(); - } - ctx.restore(); + operatorProvidesRealtime === 'yes'; + + const circle = getCircleCanvas( + origin, + radius, + circleFillColor, + hasStroke, + hasDash, + pixelRatio, + ); + + ctx.drawImage(circle, 0, 0); // Draw text in the circle if (mustDrawText) { - const fontSize = Math.max(radius, 10 * pixelRatio); + const fontSize = Math.max(radius, 10); const textSize = getTextSize(ctx, markerSize, name, fontSize); - - // Draw a stroke to the text only if a provider provides realtime but we don't use it. - if ( - useDelayStyle && - delay === null && - operatorProvidesRealtime === 'yes' - ) { - ctx.save(); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.font = `bold ${textSize + 2}px Arial`; - ctx.strokeStyle = circleFillColor; - ctx.strokeText(name, origin, origin); - ctx.restore(); - } - - // Draw a text - ctx.save(); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.fillStyle = !useDelayStyle + const textColor2 = !useDelayStyle ? textColor || getTextColor(type) : '#000000'; - ctx.font = `bold ${textSize}px Arial`; - ctx.strokeStyle = circleFillColor; - ctx.strokeText(name, origin, origin); - ctx.fillText(name, origin, origin); - ctx.restore(); + const hasStroke2 = + useDelayStyle && delay === null && operatorProvidesRealtime === 'yes'; + + const text = getTextCanvas( + name, + origin, + textSize, + textColor2, + circleFillColor, + hasStroke2, + pixelRatio, + ); + + ctx.drawImage(text, 0, 0); } styleCache[key] = canvas; diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index 76897ee9..e6071df0 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -26,7 +26,7 @@ export default () => { apiKey: window.apiKey, // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, - // useDelayStyle: true, + useDelayStyle: true, // regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', }); From 87e0bc5f23964960696805d2e507ed4e523dc45e Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 17:21:55 +0100 Subject: [PATCH 068/109] Use optimized version of the style --- src/doc/examples/ol-tralis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/examples/ol-tralis.js b/src/doc/examples/ol-tralis.js index e6071df0..76897ee9 100644 --- a/src/doc/examples/ol-tralis.js +++ b/src/doc/examples/ol-tralis.js @@ -26,7 +26,7 @@ export default () => { apiKey: window.apiKey, // bbox: [1152072, 6048052, 1433666, 6205578], isUpdateBboxOnMoveEnd: true, - useDelayStyle: true, + // useDelayStyle: true, // regexPublishedLineName: '^(S|R$|RE|PE|D|IRE|RB|TER)', }); From e891f742649e83249b5264a32025cf5b88d25acb Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 17:24:12 +0100 Subject: [PATCH 069/109] Add possibility to parse worker --- .neutrinorc.js | 12 ++++++++++++ package.json | 3 ++- yarn.lock | 18 +++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.neutrinorc.js b/.neutrinorc.js index 367adfe0..3e78a035 100644 --- a/.neutrinorc.js +++ b/.neutrinorc.js @@ -22,6 +22,18 @@ if (process.env.REACT_APP_LIB_MODE) { }, }, use: [ + (neutrino) => { + neutrino.config.output + .globalObject('this') // will prevent `window` + .end() + .module.rule('worker') + .test(neutrino.regexFromExtensions(['worker.js'])) + .use('worker') + .loader(require.resolve('worker-loader')) + .options({ + // See: https://github.com/webpack-contrib/worker-loader#options + }); + }, library({ name: 'mobility-toolbox-js', targets: { diff --git a/package.json b/package.json index 4b93328f..a81af7f4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "taffydb": "^2.7.3", "turf": "^3.0.14", "typeface-lato": "^0.0.75", - "uuid": "^8.1.0" + "uuid": "^8.1.0", + "worker-loader": "2.0.0" }, "peerDependencies": { "mapbox-gl": "^1", diff --git a/yarn.lock b/yarn.lock index ad58e68d..d3e03912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8679,7 +8679,7 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.0, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -11925,6 +11925,14 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" +schema-utils@^0.4.0: + version "0.4.7" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" + integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -14609,6 +14617,14 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +worker-loader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac" + integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw== + dependencies: + loader-utils "^1.0.0" + schema-utils "^0.4.0" + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" From e8f6b83ef71f5fdd3e1d66e203c840ec83758df1 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 26 Nov 2021 17:25:25 +0100 Subject: [PATCH 070/109] v1.6.0-beta.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a81af7f4..dd02e045 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.6.0-beta.8", + "version": "1.6.0-beta.9", "main": "index.js", "module": "module.js", "dependencies": { From a88f9ab0205b9435965a52b90fee420a15ece407 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 29 Nov 2021 08:43:46 +0100 Subject: [PATCH 071/109] Manage new tralis --- src/common/mixins/TralisLayerMixin.js | 9 ++++++++- src/common/tracker.worker.js | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index e71f304f..8e3221bc 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -183,7 +183,7 @@ const TralisLayerMixin = (TrackerLayer) => this.addTrajectory( feat.get('train_id'), - feat.getProperties(), + data.content, !feat.get('line'), ); } @@ -246,6 +246,13 @@ const TralisLayerMixin = (TrackerLayer) => } this.tracker.setTrajectories(this.trajectories); + + if (this.worker) { + this.worker.postMessage({ + action: 'sendData', + trajectories: this.trajectories, + }); + } } removeTrajectory(id) { diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index c185946e..b21317fb 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -68,7 +68,18 @@ const render = (evt) => { // We simplify the traj object let { geometry } = traj; - const { coordinate, timeIntervals, timeOffset } = traj; + const { coordinate } = traj; + let { timeIntervals, timeOffset } = traj; + + // Tralis retuirn a feature in Geojson format + if (traj.properties) { + if (traj.properties.time_intervals) { + timeIntervals = traj.properties.time_intervals; + } + if (traj.properties.time_offset) { + timeOffset = traj.properties.time_offset; + } + } if (Array.isArray(geometry.coordinates)) { if (geometry.type === 'Point') { @@ -170,7 +181,7 @@ const render = (evt) => { }); const vehicleImg = delayTrackerStyle( - traj, + traj.properties || traj, { zoom, pixelRatio, @@ -185,6 +196,7 @@ const render = (evt) => { ); if (!vehicleImg) { + console.log('ici'); // eslint-disable-next-line no-continue continue; } From 70d067a5f6e84332f6d081c148df91692c59bb08 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 29 Nov 2021 08:49:45 +0100 Subject: [PATCH 072/109] Manage new tralis --- src/common/tracker.worker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index b21317fb..5ff0a42d 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -196,7 +196,6 @@ const render = (evt) => { ); if (!vehicleImg) { - console.log('ici'); // eslint-disable-next-line no-continue continue; } From 699706d3f01b4b595bb7f47d1f1d79c0d231277a Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 4 Feb 2022 12:16:23 +0100 Subject: [PATCH 073/109] Optimizw worker --- src/common/interpolate.js | 67 ++++++ src/common/mixins/TralisLayerMixin.js | 72 ++---- src/common/tracker.worker.js | 301 +++++++++++--------------- src/common/utils/delayTrackerStyle.js | 16 +- src/ol/layers/TrackerLayer.js | 67 +++--- 5 files changed, 268 insertions(+), 255 deletions(-) create mode 100644 src/common/interpolate.js diff --git a/src/common/interpolate.js b/src/common/interpolate.js new file mode 100644 index 00000000..9706b586 --- /dev/null +++ b/src/common/interpolate.js @@ -0,0 +1,67 @@ +import GeomType from 'ol/geom/GeometryType'; + +/** + * Interpolate a position along a geometry at a specific date. + * + * @param {number} now Current date to interpolate a position with. In ms. + * @param {ol/geom/LineString~LineString} geometry The geoemtry used to interpolate a position. + * @param {Array>} timeIntervals The time intervals used to interpolate a position, ex: [[dateInMs, fraction, rotation]...]. + * @returns + */ +const interpolate = (now, geometry, timeIntervals) => { + let coord; + let start; + let end; + let startFrac; + let endFrac; + let timeFrac; + let rotation; + + // Search th time interval. + for (let j = 0; j < timeIntervals.length - 1; j += 1) { + // Rotation only available in tralis layer. + [start, startFrac, rotation] = timeIntervals[j]; + [end, endFrac] = timeIntervals[j + 1]; + + if (start <= now && now <= end) { + break; + } else { + start = null; + end = null; + } + } + // The geometry can also be a Point + if (geometry.getType() === GeomType.POINT) { + coord = geometry.getCoordinates(); + } else if (geometry.getType() === GeomType.LINE_STRING) { + if (start && end) { + // interpolate position inside the time interval. + timeFrac = interpolate ? Math.min((now - start) / (end - start), 1) : 0; + + const geomFrac = interpolate + ? timeFrac * (endFrac - startFrac) + startFrac + : 0; + + coord = geometry.getCoordinateAt(geomFrac); + + // It happens that the now date was some ms before the first timeIntervals we have. + } else if (now < timeIntervals[0][0]) { + [[, , rotation]] = timeIntervals; + timeFrac = 0; + coord = geometry.getFirstCoordinate(); + } else if (now > timeIntervals[timeIntervals.length - 1][0]) { + [, , rotation] = timeIntervals[timeIntervals.length - 1]; + timeFrac = 1; + coord = geometry.getLastCoordinate(); + } + } else { + // eslint-disable-next-line no-console + console.error( + 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', + geometry, + ); + } + return { coord, rotation, timeFrac }; +}; + +export default interpolate; diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 998e3853..00cbe5ac 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -145,14 +145,14 @@ const TralisLayerMixin = (TrackerLayer) => // Purge trajectories: // - which are outside the extent // - when it's bus and zoom level is too low for them - for (let i = this.trajectories.length - 1; i >= 0; i -= 1) { - const trajectory = this.trajectories[i]; - if (this.mustNotBeDisplayed(trajectory, extent, zoom)) { - const temp = [...this.trajectories]; - temp.splice(i, 1); - this.tracker.setTrajectories(temp); - } - } + // for (let i = this.trajectories.length - 1; i >= 0; i -= 1) { + // const trajectory = this.trajectories[i]; + // if (this.mustNotBeDisplayed(trajectory, extent, zoom)) { + // const temp = [...this.trajectories]; + // temp.splice(i, 1); + // this.tracker.setTrajectories(temp); + // } + // } const bbox = [...extent]; @@ -241,26 +241,13 @@ const TralisLayerMixin = (TrackerLayer) => * @private */ addTrajectory(traj, addOnTop) { - const idx = this.trajectories.findIndex( - (t) => t.train_id === traj.train_id, - ); - const { time_intervals: timeIntervals } = traj; - - // Properties needed to display the vehicle. - const trajectory = { ...traj, id: traj.train_id, timeIntervals }; - if (addOnTop) { - this.trajectories.unshift(trajectory); - if (idx !== -1) { - this.tracker.trajectories.splice(idx + 1, 1); - } - } else { - this.trajectories.push(trajectory); - if (idx !== -1) { - this.tracker.trajectories.splice(idx, 1); - } + if (this.worker) { + // console.log('addTrajectory', traj.train_id); + this.worker.postMessage({ + action: 'addTrajectory', + trajectory: traj, + }); } - - this.tracker.setTrajectories(this.trajectories); } /** @@ -269,11 +256,11 @@ const TralisLayerMixin = (TrackerLayer) => * @private */ removeTrajectory(id) { - for (let i = 0, len = this.trajectories.length; i < len; i += 1) { - if (this.trajectories[i].train_id === id) { - this.trajectories.splice(i, 1); - break; - } + if (this.worker) { + this.worker.postMessage({ + action: 'removeTrajectory', + trajectoryId: id, + }); } } @@ -287,25 +274,12 @@ const TralisLayerMixin = (TrackerLayer) => if (!data.content) { return; } - - const feat = this.format.readFeature(data.content); - - feat.set('timeOffset', Date.now() - data.timestamp); + const trajectory = { ...data.content }; + trajectory.properties.timeOffset = Date.now() - data.timestamp; // ignore old events [SBAHNM-97] - if (feat.get('time_since_update') >= 0) { - if ( - this.debug && - this.mode === TralisModes.TOPOGRAPHIC && - feat.get('raw_coordinates') - ) { - const point = new Point(feat.get('raw_coordinates')); - point.transform('EPSG:4326', this.map.getView().getProjection()); - feat.setGeometry(point); - } - if (!this.mustNotBeDisplayed(feat.getProperties())) { - this.addTrajectory(feat.getProperties(), !feat.get('line')); - } + if (trajectory.properties.time_since_update >= 0) { + this.addTrajectory(trajectory); } } diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index 5ff0a42d..6061c021 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -1,15 +1,15 @@ -import { Point, LineString } from 'ol/geom'; -import GeomType from 'ol/geom/GeometryType'; import stringify from 'json-stringify-safe'; import { compose, apply, create } from 'ol/transform'; +import GeoJSON from 'ol/format/GeoJSON'; import { delayTrackerStyle } from './utils'; +import interpolatePosition from './interpolate'; const debug = false; -let trajectories = []; - +const trajectories = {}; let renderTimeout; let count = 0; +const format = new GeoJSON(); const render = (evt) => { // eslint-disable-next-line no-console @@ -25,7 +25,7 @@ const render = (evt) => { center, resolution, zoom, - rotation, + rotation = 0, pixelRatio, interpolate = true, iconScale, @@ -63,175 +63,125 @@ const render = (evt) => { let selectedVehicleWidth; let selectedVehicleHeight; - for (let i = (trajectories || []).length - 1; i >= 0; i -= 1) { - const traj = trajectories[i]; - - // We simplify the traj object - let { geometry } = traj; - const { coordinate } = traj; - let { timeIntervals, timeOffset } = traj; - - // Tralis retuirn a feature in Geojson format - if (traj.properties) { - if (traj.properties.time_intervals) { - timeIntervals = traj.properties.time_intervals; - } - if (traj.properties.time_offset) { - timeOffset = traj.properties.time_offset; - } - } - - if (Array.isArray(geometry.coordinates)) { - if (geometry.type === 'Point') { - geometry = new Point(geometry.coordinates); - } else if (geometry.type === 'LineString') { - geometry = new LineString(geometry.coordinates); - } - } - - // Filter should apply when we request the data - // if (filter && !filter(traj, i, trajectories)) { - // // eslint-disable-next-line no-continue - // continue; - // } + const keys = Object.keys(trajectories); + for (let i = (keys || []).length - 1; i >= 0; i -= 1) { + const trajectory = trajectories[keys[i]]; + const { + geometry, + properties: { + coordinate, + time_intervals: timeIntervals, + time_offset: timeOffset, + train_id: trainId, + }, + } = trajectory; let coord = null; - let rotationIcon; + let rotationIcon = null; + let endFraction = 0; if (coordinate && !interpolate) { coord = coordinate; } else if (timeIntervals && timeIntervals.length > 1) { - const now = time - (timeOffset || 0); - let start; - let end; - let startFrac; - let endFrac; - let timeFrac; - - // Search th time interval. - for (let j = 0; j < timeIntervals.length - 1; j += 1) { - // Rotation only available in tralis layer. - [start, startFrac, rotationIcon] = timeIntervals[j]; - [end, endFrac] = timeIntervals[j + 1]; - - if (start <= now && now <= end) { - break; - } else { - start = null; - end = null; - } - } - // The geometry can also be a Point - if (geometry.getType() === GeomType.POINT) { - coord = geometry.getCoordinates(); - } else if (geometry.getType() === GeomType.LINE_STRING) { - if (start && end) { - // interpolate position inside the time interval. - timeFrac = interpolate - ? Math.min((now - start) / (end - start), 1) - : 0; - - const geomFrac = interpolate - ? timeFrac * (endFrac - startFrac) + startFrac - : 0; - - coord = geometry.getCoordinateAt(geomFrac); - - // We set the rotation and the timeFraction of the trajectory (used by tralis). - trajectories[i].rotation = rotation; - trajectories[i].endFraction = timeFrac; - - // It happens that the now date was some ms before the first timeIntervals we have. - } else if (now < timeIntervals[0][0]) { - [[, , rotationIcon]] = timeIntervals; - timeFrac = 0; - coord = geometry.getFirstCoordinate(); - } else if (now > timeIntervals[timeIntervals.length - 1][0]) { - [, , rotationIcon] = timeIntervals[timeIntervals.length - 1]; - timeFrac = 1; - coord = geometry.getLastCoordinate(); - } - } else { - // eslint-disable-next-line no-console - console.error( - 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', - geometry, - ); - } + const interpolated = interpolatePosition( + time - (timeOffset || 0), + geometry, + timeIntervals, + ); + // We set the rotation and the timeFraction of the trajectory (used by tralis). // if rotation === null that seems there is no rotation available. - trajectories[i].rotation = rotationIcon; - trajectories[i].endFraction = timeFrac || 0; + coord = interpolated.coord; + rotationIcon = interpolated.rotation; + endFraction = interpolated.timeFrac; + } + + if (!coord) { + // eslint-disable-next-line no-continue + continue; + } + + // We apply the result of interpolation (or not) to the trajectory. + trajectories[keys[i]].coordinate = coord; + trajectories[keys[i]].rotation = rotationIcon; + trajectories[keys[i]].endFraction = endFraction; + + let px = apply(coordinateToPixelTransform, [...coord]); // [...toLonLat(coord)]); + + if (!px) { + // eslint-disable-next-line no-continue + continue; + } + + px = px.map((p) => p * pixelRatio); + + if ( + px[0] < 0 || + px[0] > canvas.width || + px[1] < 0 || + px[1] > canvas.height + ) { + // eslint-disable-next-line no-continue + continue; + } + + px = px.map((p) => { + return p * pixelRatio; + }); + + const vehicleImg = delayTrackerStyle( + trajectory, + { + zoom, + pixelRatio, + }, + { + hoverVehicleId, + selectedVehicleId, + delayDisplay, + delayOutlineColor, + useDelayStyle, + }, + ); + + if (!vehicleImg) { + // eslint-disable-next-line no-continue + continue; } - if (coord) { - // We set the rotation of the trajectory (used by tralis). - trajectories[i].coordinate = coord; - - let px = apply(coordinateToPixelTransform, [...coord]); // [...toLonLat(coord)]); - - if (!px) { - // eslint-disable-next-line no-continue - continue; - } - nbRenderedTrajectories += 1; - - px = px.map((p) => { - return p * pixelRatio; - }); - - const vehicleImg = delayTrackerStyle( - traj.properties || traj, - { - zoom, - pixelRatio, - }, - { - hoverVehicleId, - selectedVehicleId, - delayDisplay, - delayOutlineColor, - useDelayStyle, - }, + nbRenderedTrajectories += 1; + + let imgWidth = vehicleImg.width; + let imgHeight = vehicleImg.height; + + if (iconScale) { + imgHeight = Math.floor(imgHeight * iconScale); + imgWidth = Math.floor(imgWidth * iconScale); + } + + if (hoverVehicleId !== trainId && selectedVehicleId !== trainId) { + canvasContext.drawImage( + vehicleImg, + px[0] - imgWidth / 2, + px[1] - imgHeight / 2, + imgWidth, + imgHeight, ); + } + if (hoverVehicleId === trainId) { + // Store the canvas to draw it at the end + hoverVehicleImg = vehicleImg; + hoverVehiclePx = px; + hoverVehicleWidth = imgWidth; + hoverVehicleHeight = imgHeight; + } - if (!vehicleImg) { - // eslint-disable-next-line no-continue - continue; - } - - let imgWidth = vehicleImg.width; - let imgHeight = vehicleImg.height; - - if (iconScale) { - imgHeight = Math.floor(imgHeight * iconScale); - imgWidth = Math.floor(imgWidth * iconScale); - } - - if (hoverVehicleId !== traj.id && selectedVehicleId !== traj.id) { - canvasContext.drawImage( - vehicleImg, - px[0] - imgWidth / 2, - px[1] - imgHeight / 2, - imgWidth, - imgHeight, - ); - } - if (hoverVehicleId === traj.id) { - // Store the canvas to draw it at the end - hoverVehicleImg = vehicleImg; - hoverVehiclePx = px; - hoverVehicleWidth = imgWidth; - hoverVehicleHeight = imgHeight; - } - - if (selectedVehicleId === traj.id) { - // Store the canvas to draw it at the end - selectedVehicleImg = vehicleImg; - selectedVehiclePx = px; - selectedVehicleWidth = imgWidth; - selectedVehicleHeight = imgHeight; - } + if (selectedVehicleId === trainId) { + // Store the canvas to draw it at the end + selectedVehicleImg = vehicleImg; + selectedVehiclePx = px; + selectedVehicleWidth = imgWidth; + selectedVehicleHeight = imgHeight; } } @@ -277,17 +227,30 @@ const render = (evt) => { }; // eslint-disable-next-line no-restricted-globals -self.onmessage = function (evt) { +self.onmessage = (evt) => { // debugger; - if (evt.data.action === 'sendData') { - // eslint-disable-next-line no-console - if (debug) console.log('sendData', evt.data); - if (debug) console.time('sendData'); - trajectories = evt.data.trajectories; - if (debug) console.timeEnd('sendData'); + if (evt.data.action === 'addTrajectory') { + const { trajectory } = evt.data; + const id = trajectory.properties.train_id; + const geometry = format.readGeometry(trajectory.geometry); + trajectories[id] = { ...evt.data.trajectory, geometry }; return; } + if (evt.data.action === 'removeTrajectory') { + delete trajectories[evt.data.trajectoryId]; + return; + } + + // if (evt.data.action === 'sendData') { + // // eslint-disable-next-line no-console + // if (debug) console.log('sendData', evt.data); + // if (debug) console.time('sendData'); + // trajectories = evt.data.trajectories; + // if (debug) console.timeEnd('sendData'); + // return; + // } + if (evt.data.action !== 'render') { return; } diff --git a/src/common/utils/delayTrackerStyle.js b/src/common/utils/delayTrackerStyle.js index 42b07392..b06f3fbf 100644 --- a/src/common/utils/delayTrackerStyle.js +++ b/src/common/utils/delayTrackerStyle.js @@ -158,13 +158,14 @@ const style = (trajectory, viewState, options) => { delayDisplay, } = options; + const { zoom, pixelRatio } = viewState; + let { line, type } = trajectory.properties; const { - zoom, - pixelRatio, + train_id: id, + delay, + cancelled = false, operator_provides_realtime_journey: operatorProvidesRealtime, - } = viewState; - let { line, type } = trajectory; - const { id, delay, cancelled = false } = trajectory; + } = trajectory.properties; if (!type) { type = 'Rail'; @@ -193,8 +194,8 @@ const style = (trajectory, viewState, options) => { } const z = Math.min(Math.floor(zoom || 1), 16); - const hover = hoverVehicleId === id; - const selected = selectedVehicleId === id; + const hover = hoverVehicleId && hoverVehicleId === id; + const selected = selectedVehicleId && selectedVehicleId === id; // Calcul the radius of the circle let radius = getRadius(type, z) * pixelRatio; @@ -204,6 +205,7 @@ const style = (trajectory, viewState, options) => { ? radius + 5 * pixelRatio : 14 * pixelRatio; } + const mustDrawText = radius > 10 * pixelRatio; // Optimize the cache key, very important in high zoom level diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index aeb2ad14..7d6f91f2 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -30,8 +30,8 @@ const updateContainerTransform = (layer) => { rotation - renderedRotation, 0, 0, - this.canvas.width, - this.canvas.height, + layer.canvas.width, + layer.canvas.height, ); } }; @@ -69,33 +69,39 @@ class TrackerLayer extends mixin(Layer) { that.map.render(); } else if (that.canvas && message.data.action === 'rendered') { if ( - that.map.getView().getInteracting() || - that.map.getView().getAnimating() + !that.renderWhenInteracting( + that.mainThreadFrameState.viewState, + that.renderedViewState, + ) && + (that.map.getView().getInteracting() || + that.map.getView().getAnimating()) ) { return; } - // Worker provies a new render frame - requestAnimationFrame(() => { - if ( - that.map.getView().getInteracting() || - that.map.getView().getAnimating() - ) { - return; - } - const { imageData, nbRenderedTrajectories } = message.data; - this.nbRenderedTrajectories = nbRenderedTrajectories; - that.canvas.width = imageData.width; - that.canvas.height = imageData.height; - this.canvas.style.transform = ``; - this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; - this.canvas.style.height = `${ - this.canvas.height / this.pixelRatio - }px`; - that.canvas.getContext('2d').drawImage(imageData, 0, 0); - // this.canvas.style.transform = message.data.transform; - that.renderedViewState = message.data.frameState.viewState; - updateContainerTransform(that); - }); + // Worker provides a new render frame + // requestAnimationFrame(() => { + // if ( + // !that.renderWhenInteracting( + // that.mainThreadFrameState.viewState, + // that.renderedViewState, + // ) && + // (that.map.getView().getInteracting() || + // that.map.getView().getAnimating()) + // ) { + // return; + // } + const { imageData, nbRenderedTrajectories } = message.data; + this.nbRenderedTrajectories = nbRenderedTrajectories; + that.canvas.width = imageData.width; + that.canvas.height = imageData.height; + this.canvas.style.transform = ``; + this.canvas.style.width = `${this.canvas.width / this.pixelRatio}px`; + this.canvas.style.height = `${this.canvas.height / this.pixelRatio}px`; + // this.canvas.style.transform = message.data.transform; + that.renderedViewState = message.data.frameState.viewState; + updateContainerTransform(that); + that.canvas.getContext('2d').drawImage(imageData, 0, 0); + // }); that.rendering = false; } }; @@ -109,9 +115,7 @@ class TrackerLayer extends mixin(Layer) { options.renderWhenInteracting || (() => // Render trajectories on each render frame when the number of trajectories is small. - this.tracker && - this.tracker.renderedTrajectories && - this.tracker.renderedTrajectories.length <= 200); + this.nbRenderedTrajectories < 200); /** @ignore */ this.olLayer = @@ -160,7 +164,10 @@ class TrackerLayer extends mixin(Layer) { this.renderTrajectories(true); } else if (renderedResolution / resolution >= 3) { // Avoid having really big points when zooming fast. - this.tracker.clear(); + this.canvas + .getContext('2d') + .clearRect(0, 0, this.canvas.width, this.canvas.height); + // } else { // const pixelCenterRendered = // this.map.getPixelFromCoordinate(renderedCenter); From e6033df7e312ee71c711e87d8f86f27ecbd07028 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 15 Feb 2022 12:55:56 +0100 Subject: [PATCH 074/109] Replace array of trajectories by an object --- src/common/Tracker.js | 66 ++++------- src/common/mixins/TrackerLayerMixin.js | 23 ++-- src/common/mixins/TrajservLayerMixin.js | 6 +- src/common/mixins/TralisLayerMixin.js | 143 ++++++++++++++---------- src/common/utils/delayTrackerStyle.js | 10 +- src/mapbox/layers/TralisLayer.js | 4 +- src/ol/layers/TralisLayer.js | 4 +- 7 files changed, 127 insertions(+), 129 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 9d963997..39c93c18 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -109,26 +109,6 @@ export default class Tracker { } } - /** - * Define the trajectories. - * @param {array
    } trajectories - */ - setTrajectories(trajectories = []) { - if (this.sort) { - trajectories.sort(this.sort); - } - - this.trajectories = trajectories; - } - - /** - * Return the trajectories. - * @return {array} trajectories - */ - getTrajectories() { - return this.trajectories || []; - } - /** * Clear the canvas. * @private @@ -147,7 +127,7 @@ export default class Tracker { * during map navigation. * @private */ - renderTrajectories(viewState, noInterpolate = false) { + renderTrajectories(trajectories, viewState, noInterpolate = false) { const { time = Date.now(), size = [], @@ -199,14 +179,22 @@ export default class Tracker { let selectedVehicleHeight; this.renderedTrajectories = []; + const keys = Object.keys(this.trajectories); - for (let i = (this.trajectories || []).length - 1; i >= 0; i -= 1) { - const traj = this.trajectories[i]; + for (let i = (keys || []).length - 1; i >= 0; i -= 1) { + const key = keys[i]; + const traj = this.trajectories[key]; - this.trajectories[i].rendered = false; + this.trajectories[keys[i]].properties.rendered = false; // We simplify the traj object - const { geometry, timeIntervals, timeOffset } = traj; + const { + train_id: id, + time_intervals: timeIntervals, + timeOffset, + olGeometry: geometry, + coordinate, + } = traj.properties; if (this.filter && !this.filter(traj, i, this.trajectories)) { // eslint-disable-next-line no-continue @@ -216,8 +204,8 @@ export default class Tracker { let coord = null; let rotationIcon; - if (traj.coordinate && (noInterpolate || !this.interpolate)) { - coord = traj.coordinate; + if (coordinate && (noInterpolate || !this.interpolate)) { + coord = coordinate; } else if (timeIntervals && timeIntervals.length > 1) { const now = time - (timeOffset || 0); let start; @@ -255,10 +243,6 @@ export default class Tracker { coord = geometry.getCoordinateAt(geomFrac); - // We set the rotation and the timeFraction of the trajectory (used by tralis). - this.trajectories[i].rotation = rotationIcon; - this.trajectories[i].endFraction = timeFrac; - // It happens that the now date was some ms before the first timeIntervals we have. } else if (now < timeIntervals[0][0]) { [[, , rotationIcon]] = timeIntervals; @@ -278,13 +262,13 @@ export default class Tracker { } // We set the rotation and the timeFraction of the trajectory (used by tralis). // if rotation === null that seems there is no rotation available. - this.trajectories[i].rotation = rotationIcon; - this.trajectories[i].endFraction = timeFrac || 0; + this.trajectories[key].rotation = rotationIcon; + this.trajectories[key].endFraction = timeFrac || 0; } if (coord) { // We set the rotation of the trajectory (used by tralis). - this.trajectories[i].coordinate = coord; + this.trajectories[key].properties.coordinate = coord; let px = apply(coordinateToPixelTransform, [...coord]); if (!px) { // eslint-disable-next-line no-continue @@ -310,16 +294,13 @@ export default class Tracker { } // Trajectory with pixel (i.e. within map extent) will be in renderedTrajectories. - this.trajectories[i].rendered = true; - this.renderedTrajectories.push(this.trajectories[i]); + this.trajectories[key].properties.rendered = true; + this.renderedTrajectories.push(this.trajectories[key]); const imgWidth = vehicleImg.width; const imgHeight = vehicleImg.height; - if ( - this.hoverVehicleId !== traj.id && - this.selectedVehicleId !== traj.id - ) { + if (this.hoverVehicleId !== id && this.selectedVehicleId !== id) { this.canvasContext.drawImage( vehicleImg, px[0] - imgWidth / 2, @@ -328,7 +309,8 @@ export default class Tracker { imgHeight, ); } - if (this.hoverVehicleId === traj.id) { + + if (this.hoverVehicleId && this.hoverVehicleId === id) { // Store the canvas to draw it at the end hoverVehicleImg = vehicleImg; hoverVehiclePx = px; @@ -336,7 +318,7 @@ export default class Tracker { hoverVehicleHeight = imgHeight; } - if (this.selectedVehicleId === traj.id) { + if (this.selectedVehicleId && this.selectedVehicleId === id) { // Store the canvas to draw it at the end selectedVehicleImg = vehicleImg; selectedVehiclePx = px; diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 736355fe..4cc40ac4 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -2,7 +2,6 @@ /* eslint-disable max-classes-per-file */ import { buffer, containsCoordinate } from 'ol/extent'; import { unByKey } from 'ol/Observable'; -import Feature from 'ol/Feature'; import qs from 'query-string'; import Tracker from '../Tracker'; import { timeSteps } from '../trackerConfig'; @@ -540,7 +539,11 @@ const TrackerLayerMixin = (Base) => const time = this.live ? Date.now() : this.time; - this.tracker.renderTrajectories({ ...viewState, time }, noInterpolate); + this.tracker.renderTrajectories( + this.trajectories, + { ...viewState, time }, + noInterpolate, + ); return true; } @@ -580,7 +583,7 @@ const TrackerLayerMixin = (Base) => * @return {Array} Array of vehicle. */ getVehicle(filterFc) { - return this.tracker.getTrajectories().filter(filterFc); + return Object.values(this.trajectories).filter(filterFc); } /** @@ -596,12 +599,12 @@ const TrackerLayerMixin = (Base) => [...coordinate, ...coordinate], this.hitTolerance * resolution, ); - const trajectories = this.tracker.getTrajectories(); + const trajectories = Object.values(this.trajectories); const vehicles = []; for (let i = 0; i < trajectories.length; i += 1) { if ( - trajectories[i].coordinate && - containsCoordinate(ext, trajectories[i].coordinate) + trajectories[i].properties.coordinate && + containsCoordinate(ext, trajectories[i].properties.coordinate) ) { vehicles.push(trajectories[i]); } @@ -629,13 +632,7 @@ const TrackerLayerMixin = (Base) => return Promise.resolve({ layer: this, - features: vehicles.map((vehicle) => { - const feature = new Feature({ - geometry: vehicle.geometry, - }); - feature.setProperties({ ...vehicle }); - return feature; - }), + features: vehicles.map((vehicle) => this.format.readFeature(vehicle)), coordinate, }); } diff --git a/src/common/mixins/TrajservLayerMixin.js b/src/common/mixins/TrajservLayerMixin.js index f195440c..fb6d3626 100644 --- a/src/common/mixins/TrajservLayerMixin.js +++ b/src/common/mixins/TrajservLayerMixin.js @@ -188,7 +188,7 @@ const TrajservLayerMixin = (TrackerLayer) => const [feature] = features; let id = null; if (feature) { - id = feature.get('id'); + id = feature.get('train_id'); } if (this.hoverVehicleId !== id) { /** @ignore */ @@ -208,7 +208,7 @@ const TrajservLayerMixin = (TrackerLayer) => const [feature] = features; if (feature) { /** @ignore */ - this.selectedVehicleId = feature.get('id'); + this.selectedVehicleId = feature.get('train_id'); /** @ignore */ this.journeyId = feature.get('journeyIdentifier'); this.highlightTrajectory(); @@ -364,7 +364,7 @@ const TrajservLayerMixin = (TrackerLayer) => .then((trajectories) => { // Don't set trajectories when the user has aborted the request. if (trajectories) { - this.tracker.setTrajectories(trajectories); + this.trajectories = trajectories; this.renderTrajectories(); } }); diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 998e3853..99f81459 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -6,6 +6,7 @@ import GeoJSON from 'ol/format/GeoJSON'; import Point from 'ol/geom/Point'; import { intersects } from 'ol/extent'; +import { fromLonLat } from 'ol/proj'; import { TralisAPI, TralisModes } from '../../api'; /** @@ -145,13 +146,9 @@ const TralisLayerMixin = (TrackerLayer) => // Purge trajectories: // - which are outside the extent // - when it's bus and zoom level is too low for them - for (let i = this.trajectories.length - 1; i >= 0; i -= 1) { - const trajectory = this.trajectories[i]; - if (this.mustNotBeDisplayed(trajectory, extent, zoom)) { - const temp = [...this.trajectories]; - temp.splice(i, 1); - this.tracker.setTrajectories(temp); - } + const keys = Object.keys(this.trajectories); + for (let i = keys.length - 1; i >= 0; i -= 1) { + this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom); } const bbox = [...extent]; @@ -225,11 +222,16 @@ const TralisLayerMixin = (TrackerLayer) => * @return {boolean} if the trajectory must be displayed or not. * @ignore */ - mustNotBeDisplayed(trajectory, extent, zoom) { - return ( - !intersects(extent, trajectory.bounds) || - (trajectory.type !== 'rail' && zoom < (this.minZoomNonTrain || 9)) - ); + purgeTrajectory(trajectory, extent, zoom) { + const { type, bounds, train_id: id } = trajectory.properties; + if ( + !intersects(extent, bounds) || + (type !== 'rail' && zoom < (this.minZoomNonTrain || 9)) + ) { + this.removeTrajectory(id); + return true; + } + return false; } /** @@ -240,43 +242,38 @@ const TralisLayerMixin = (TrackerLayer) => * true, the trajectory is drawn first and appears on bottom. * @private */ - addTrajectory(traj, addOnTop) { - const idx = this.trajectories.findIndex( - (t) => t.train_id === traj.train_id, - ); - const { time_intervals: timeIntervals } = traj; - - // Properties needed to display the vehicle. - const trajectory = { ...traj, id: traj.train_id, timeIntervals }; - if (addOnTop) { - this.trajectories.unshift(trajectory); - if (idx !== -1) { - this.tracker.trajectories.splice(idx + 1, 1); - } - } else { - this.trajectories.push(trajectory); - if (idx !== -1) { - this.tracker.trajectories.splice(idx, 1); - } - } - - this.tracker.setTrajectories(this.trajectories); + addTrajectory(trajectory, addOnTop) { + // console.time(`addTrajectory${traj.train_id}`); + this.trajectories[trajectory.properties.train_id] = trajectory; + // const idx = this.trajectories.findIndex( + // (t) => t.train_id === traj.train_id, + // ); + // const { time_intervals: timeIntervals } = traj; + + // // Properties needed to display the vehicle. + // const trajectory = { ...traj, id: traj.train_id, timeIntervals }; + // if (addOnTop) { + // this.trajectories.unshift(trajectory); + // if (idx !== -1) { + // this.tracker.trajectories.splice(idx + 1, 1); + // } + // } else { + // this.trajectories.push(trajectory); + // if (idx !== -1) { + // this.tracker.trajectories.splice(idx, 1); + // } + // } + // console.timeEnd(`addTrajectory${traj.train_id}`); } - /** - * Remove a trajectory using its id. - * @param {number} id The trajectory's train_id property of the trajectory to remove - * @private - */ removeTrajectory(id) { - for (let i = 0, len = this.trajectories.length; i < len; i += 1) { - if (this.trajectories[i].train_id === id) { - this.trajectories.splice(i, 1); - break; - } - } + delete this.trajectories[id]; } + // getRefreshTimeInMs() { + // return 5000; + // } + /** * Callback on websocket's trajectory channel events. * It adds a trajectory to the list. @@ -287,26 +284,46 @@ const TralisLayerMixin = (TrackerLayer) => if (!data.content) { return; } + const trajectory = data.content; - const feat = this.format.readFeature(data.content); - - feat.set('timeOffset', Date.now() - data.timestamp); + const { + geometry, + properties: { + train_id: id, + time_since_update: timeSinceUpdate, + raw_coordinates: rawCoordinates, + }, + } = trajectory; // ignore old events [SBAHNM-97] - if (feat.get('time_since_update') >= 0) { - if ( - this.debug && - this.mode === TralisModes.TOPOGRAPHIC && - feat.get('raw_coordinates') - ) { - const point = new Point(feat.get('raw_coordinates')); - point.transform('EPSG:4326', this.map.getView().getProjection()); - feat.setGeometry(point); - } - if (!this.mustNotBeDisplayed(feat.getProperties())) { - this.addTrajectory(feat.getProperties(), !feat.get('line')); - } + if (timeSinceUpdate < 0) { + return; } + + // console.time(`onTrajectoryMessage${data.content.properties.train_id}`); + if (this.purgeTrajectory(trajectory)) { + return; + } + + if ( + this.debug && + this.mode === TralisModes.TOPOGRAPHIC && + rawCoordinates + ) { + trajectory.properties.olGeometry = { + type: 'Point', + coordinates: fromLonLat( + rawCoordinates, + this.map.getView().getProjection(), + ), + }; + } else { + trajectory.properties.olGeometry = this.format.readGeometry(geometry); + } + + trajectory.properties.timeOffset = Date.now() - data.timestamp; + this.addTrajectory(trajectory); + // console.timeEnd(`onTrajectoryMessage${data.content.properties.train_id}`); } /** @@ -317,9 +334,11 @@ const TralisLayerMixin = (TrackerLayer) => * @override */ onDeleteTrajectoryMessage(data) { - if (data.content) { - this.removeTrajectory(data.content); + if (!data.content) { + return; } + + this.removeTrajectory(data.content); } /** diff --git a/src/common/utils/delayTrackerStyle.js b/src/common/utils/delayTrackerStyle.js index ce7b83bd..d2ff37f7 100644 --- a/src/common/utils/delayTrackerStyle.js +++ b/src/common/utils/delayTrackerStyle.js @@ -158,13 +158,13 @@ const style = (trajectory, viewState, options) => { } = options; const { zoom, pixelRatio } = viewState; - let { line, type } = trajectory; + let { line, type } = trajectory.properties; const { - id, + train_id: id, delay, cancelled = false, operator_provides_realtime_journey: operatorProvidesRealtime, - } = trajectory; + } = trajectory.properties; if (!type) { type = 'Rail'; @@ -193,8 +193,8 @@ const style = (trajectory, viewState, options) => { } const z = Math.min(Math.floor(zoom || 1), 16); - const hover = hoverVehicleId === id; - const selected = selectedVehicleId === id; + const hover = hoverVehicleId && hoverVehicleId === id; + const selected = selectedVehicleId && selectedVehicleId === id; // Calcul the radius of the circle let radius = getRadius(type, z) * pixelRatio; diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 96c9f9ba..08437571 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -24,8 +24,8 @@ class TralisLayer extends mixin(TrackerLayer) { * * @private */ - mustNotBeDisplayed(trajectory, extent, zoom) { - return super.mustNotBeDisplayed( + purgeTrajectory(trajectory, extent, zoom) { + return super.purgeTrajectory( trajectory, extent || this.getMercatorExtent(), zoom || Math.floor(this.map.getZoom() + 1), diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index 6abc694b..2ea2876c 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -31,8 +31,8 @@ class TralisLayer extends mixin(TrackerLayer) { * * @private */ - mustNotBeDisplayed(trajectory, extent, zoom) { - return super.mustNotBeDisplayed( + purgeTrajectory(trajectory, extent, zoom) { + return super.purgeTrajectory( trajectory, extent || this.map.getView().calculateExtent(), zoom || this.map.getView().getZoom(), From e662cc0fdec9580dd5449a23f2f2cd5ff2483f39 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 18 Feb 2022 10:38:13 +0100 Subject: [PATCH 075/109] Clean code --- package.json | 1 + src/common/Tracker.js | 308 +++++++---------------- src/common/mixins/TrackerLayerMixin.js | 141 +++++------ src/common/mixins/TralisLayerMixin.js | 29 +-- src/common/utils/createTrackerFilters.js | 25 +- src/common/utils/getVehiclePosition.js | 52 ++++ src/mapbox/layers/TrackerLayer.js | 4 +- src/ol/layers/TrackerLayer.js | 5 +- yarn.lock | 5 + 9 files changed, 235 insertions(+), 335 deletions(-) create mode 100644 src/common/utils/getVehiclePosition.js diff --git a/package.json b/package.json index 6bfb7e75..27ea3fa2 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@turf/helpers": "^6.5.0", "@turf/transform-rotate": "^6.5.0", "codesandbox": "2.2.3", + "lodash.throttle": "^4.1.1", "path": "^0.12.7", "prop-types": "15.8.1", "query-string": "7.1.0", diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 39c93c18..754cf511 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -1,5 +1,6 @@ -import GeomType from 'ol/geom/GeometryType'; +/* eslint-disable no-param-reassign */ import { compose, apply, create } from 'ol/transform'; +import getVehiclePosition from './utils/getVehiclePosition'; /** * Tracker. This class stores and allows to draw trajectories on a canvas. @@ -12,81 +13,28 @@ export default class Tracker { * @private */ constructor(options) { - const opts = { - interpolate: true, - ...options, - }; - - /** - * Pixel ratio to use to draw the canvas. Default to window.devicePixelRatio - * @type {Array} - */ - this.pixelRatio = options.pixelRatio || window.devicePixelRatio || 1; - - /** - * Array of trajectories. - * @type {Array} - */ - this.trajectories = []; - - /** - * Array of trajectories that are currently drawn. - * @type {Array} - */ - this.renderedTrajectories = []; - - /** - * Active interpolation calculation or not. If false, the train will not move until we receive the next message for the websocket. - * @type {boolean} - */ - this.interpolate = !!opts.interpolate; - - /** - * Id of the trajectory which is hovered. - * @type {string} - */ - this.hoverVehicleId = opts.hoverVehicleId; - - /** - * Id of the trajectory which is selected. - * @type {string} - */ - this.selectedVehicleId = opts.selectedVehicleId; - - /** - * Function use to filter the features displayed. - * @type {function} - */ - this.filter = opts.filter; - - /** - * Function use to sort the features displayed. - * @type {function} - */ - this.sort = opts.sort; - /** * Function use to style the features displayed. * @type {function} */ - this.style = opts.style; + this.style = options.style; // we draw directly on the canvas since openlayers is too slow. /** * HTML element. * @type {Canvas} */ - this.canvas = opts.canvas || document.createElement('canvas'); - this.canvas.width = opts.width * this.pixelRatio; - this.canvas.height = opts.height * this.pixelRatio; + this.canvas = options.canvas || document.createElement('canvas'); + this.canvas.width = options.width * this.pixelRatio; + this.canvas.height = options.height * this.pixelRatio; this.canvas.setAttribute( 'style', [ 'position: absolute', 'top: 0', 'bottom: 0', - `width: ${opts.width}px`, - `height: ${opts.height}px`, + `width: ${options.width}px`, + `height: ${options.height}px`, 'pointer-events: none', 'visibility: visible', 'margin-top: inherit', // for scrolling behavior. @@ -114,20 +62,23 @@ export default class Tracker { * @private */ clear() { - if (this.canvasContext) { - this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height); + if (this.canvas) { + this.canvas + .getContext('2d') + .clearRect(0, 0, this.canvas.width, this.canvas.height); } } /** * Draw all the trajectories available to the canvas. + * @param {ViewState} trajectories An array of trajectories. * @param {ViewState} viewState The view state of the map. - * @param {boolean} noInterpolate If true trajectories are not interpolated but + * @param {boolean} options.noInterpolate If true trajectories are not interpolated but * drawn at the last known coordinate. Use this for performance optimization * during map navigation. * @private */ - renderTrajectories(trajectories, viewState, noInterpolate = false) { + renderTrajectories(trajectories, viewState, options) { const { time = Date.now(), size = [], @@ -136,18 +87,22 @@ export default class Tracker { rotation = 0, pixelRatio, } = viewState; + const { + noInterpolate = false, + hoverVehicleId, + selectedVehicleId, + } = options; this.clear(); + const { canvas } = this; + const context = canvas.getContext('2d'); const [width, height] = size; if ( width && height && - (this.canvas.width !== width || this.canvas.height !== height) + (canvas.width !== width || canvas.height !== height) ) { - [this.canvas.width, this.canvas.height] = [ - width * pixelRatio, - height * pixelRatio, - ]; + [canvas.width, canvas.height] = [width * pixelRatio, height * pixelRatio]; } const coordinateToPixelTransform = compose( @@ -161,14 +116,9 @@ export default class Tracker { -center[1], ); - this.canvas.style.width = `${this.canvas.width / pixelRatio}px`; - this.canvas.style.height = `${this.canvas.height / pixelRatio}px`; + canvas.style.width = `${canvas.width / pixelRatio}px`; + canvas.style.height = `${canvas.height / pixelRatio}px`; - /** - * Current resolution. - * @type {number} - */ - this.currResolution = resolution || this.currResolution; let hoverVehicleImg; let hoverVehiclePx; let hoverVehicleWidth; @@ -177,159 +127,89 @@ export default class Tracker { let selectedVehiclePx; let selectedVehicleWidth; let selectedVehicleHeight; + let nbRendered = 0; - this.renderedTrajectories = []; - const keys = Object.keys(this.trajectories); + for (let i = trajectories.length - 1; i >= 0; i -= 1) { + const trajectory = trajectories[i]; - for (let i = (keys || []).length - 1; i >= 0; i -= 1) { - const key = keys[i]; - const traj = this.trajectories[key]; + // We simplify the trajectory object + const { train_id: id, timeOffset } = trajectory.properties; - this.trajectories[keys[i]].properties.rendered = false; + // We set the rotation and the timeFraction of the trajectory (used by tralis). + // if rotation === null that seems there is no rotation available. + const { coord, rotation: rotationIcon } = getVehiclePosition( + time - (timeOffset || 0), + trajectory, + noInterpolate, + ); - // We simplify the traj object - const { - train_id: id, - time_intervals: timeIntervals, - timeOffset, - olGeometry: geometry, - coordinate, - } = traj.properties; + // We store the current vehicle position to the trajectory. + trajectories[i].properties.coordinate = coord; + trajectories[i].properties.rotation = rotationIcon; - if (this.filter && !this.filter(traj, i, this.trajectories)) { + if (!coord) { // eslint-disable-next-line no-continue continue; } - let coord = null; - let rotationIcon; - - if (coordinate && (noInterpolate || !this.interpolate)) { - coord = coordinate; - } else if (timeIntervals && timeIntervals.length > 1) { - const now = time - (timeOffset || 0); - let start; - let end; - let startFrac; - let endFrac; - let timeFrac; - - // Search th time interval. - for (let j = 0; j < timeIntervals.length - 1; j += 1) { - // Rotation only available in tralis layer. - [start, startFrac, rotationIcon] = timeIntervals[j]; - [end, endFrac] = timeIntervals[j + 1]; - - if (start <= now && now <= end) { - break; - } else { - start = null; - end = null; - } - } - // The geometry can also be a Point - if (geometry.getType() === GeomType.POINT) { - coord = geometry.getCoordinates(); - } else if (geometry.getType() === GeomType.LINE_STRING) { - if (start && end) { - // interpolate position inside the time interval. - timeFrac = this.interpolate - ? Math.min((now - start) / (end - start), 1) - : 0; - - const geomFrac = this.interpolate - ? timeFrac * (endFrac - startFrac) + startFrac - : 0; - - coord = geometry.getCoordinateAt(geomFrac); - - // It happens that the now date was some ms before the first timeIntervals we have. - } else if (now < timeIntervals[0][0]) { - [[, , rotationIcon]] = timeIntervals; - timeFrac = 0; - coord = geometry.getFirstCoordinate(); - } else if (now > timeIntervals[timeIntervals.length - 1][0]) { - [, , rotationIcon] = timeIntervals[timeIntervals.length - 1]; - timeFrac = 1; - coord = geometry.getLastCoordinate(); - } - } else { - // eslint-disable-next-line no-console - console.error( - 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', - geometry, - ); - } - // We set the rotation and the timeFraction of the trajectory (used by tralis). - // if rotation === null that seems there is no rotation available. - this.trajectories[key].rotation = rotationIcon; - this.trajectories[key].endFraction = timeFrac || 0; + let px = apply(coordinateToPixelTransform, [...coord]); + if (!px) { + // eslint-disable-next-line no-continue + continue; } - if (coord) { - // We set the rotation of the trajectory (used by tralis). - this.trajectories[key].properties.coordinate = coord; - let px = apply(coordinateToPixelTransform, [...coord]); - if (!px) { - // eslint-disable-next-line no-continue - continue; - } - - px = px.map((p) => p * pixelRatio); + px = px.map((p) => p * pixelRatio); - if ( - px[0] < 0 || - px[0] > this.canvas.width || - px[1] < 0 || - px[1] > this.canvas.height - ) { - // eslint-disable-next-line no-continue - continue; - } + if ( + px[0] < 0 || + px[0] > canvas.width || + px[1] < 0 || + px[1] > canvas.height + ) { + // eslint-disable-next-line no-continue + continue; + } - const vehicleImg = this.style(traj, viewState); - if (!vehicleImg) { - // eslint-disable-next-line no-continue - continue; - } + const vehicleImg = this.style(trajectory, viewState, options); + if (!vehicleImg) { + // eslint-disable-next-line no-continue + continue; + } - // Trajectory with pixel (i.e. within map extent) will be in renderedTrajectories. - this.trajectories[key].properties.rendered = true; - this.renderedTrajectories.push(this.trajectories[key]); + nbRendered += 1; - const imgWidth = vehicleImg.width; - const imgHeight = vehicleImg.height; + const imgWidth = vehicleImg.width; + const imgHeight = vehicleImg.height; - if (this.hoverVehicleId !== id && this.selectedVehicleId !== id) { - this.canvasContext.drawImage( - vehicleImg, - px[0] - imgWidth / 2, - px[1] - imgHeight / 2, - imgWidth, - imgHeight, - ); - } + if (hoverVehicleId !== id && selectedVehicleId !== id) { + context.drawImage( + vehicleImg, + px[0] - imgWidth / 2, + px[1] - imgHeight / 2, + imgWidth, + imgHeight, + ); + } - if (this.hoverVehicleId && this.hoverVehicleId === id) { - // Store the canvas to draw it at the end - hoverVehicleImg = vehicleImg; - hoverVehiclePx = px; - hoverVehicleWidth = imgWidth; - hoverVehicleHeight = imgHeight; - } + if (hoverVehicleId && hoverVehicleId === id) { + // Store the canvas to draw it at the end + hoverVehicleImg = vehicleImg; + hoverVehiclePx = px; + hoverVehicleWidth = imgWidth; + hoverVehicleHeight = imgHeight; + } - if (this.selectedVehicleId && this.selectedVehicleId === id) { - // Store the canvas to draw it at the end - selectedVehicleImg = vehicleImg; - selectedVehiclePx = px; - selectedVehicleWidth = imgWidth; - selectedVehicleHeight = imgHeight; - } + if (selectedVehicleId && selectedVehicleId === id) { + // Store the canvas to draw it at the end + selectedVehicleImg = vehicleImg; + selectedVehiclePx = px; + selectedVehicleWidth = imgWidth; + selectedVehicleHeight = imgHeight; } } if (selectedVehicleImg) { - this.canvasContext.drawImage( + context.drawImage( selectedVehicleImg, selectedVehiclePx[0] - selectedVehicleWidth / 2, selectedVehiclePx[1] - selectedVehicleHeight / 2, @@ -339,7 +219,7 @@ export default class Tracker { } if (hoverVehicleImg) { - this.canvasContext.drawImage( + context.drawImage( hoverVehicleImg, hoverVehiclePx[0] - hoverVehicleWidth / 2, hoverVehiclePx[1] - hoverVehicleHeight / 2, @@ -347,14 +227,8 @@ export default class Tracker { hoverVehicleHeight, ); } - } - - /** - * Clean the canvas and the events the tracker. - * @private - */ - destroy() { - this.renderedTrajectories = []; - this.clear(); + return { + nbTrajectoriesRendered: nbRendered, + }; } } diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 4cc40ac4..1b85eaab 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -3,6 +3,7 @@ import { buffer, containsCoordinate } from 'ol/extent'; import { unByKey } from 'ol/Observable'; import qs from 'query-string'; +import throttle from 'lodash.throttle'; import Tracker from '../Tracker'; import { timeSteps } from '../trackerConfig'; import createFilters from '../utils/createTrackerFilters'; @@ -112,6 +113,13 @@ const TrackerLayerMixin = (Base) => super({ hitTolerance: 10, ...options }); this.onFeatureHover = this.onFeatureHover.bind(this); this.onFeatureClick = this.onFeatureClick.bind(this); + this.renderTrajectoriesInternal = + this.renderTrajectoriesInternal.bind(this); + + this.throttleRenderTajectories = throttle( + this.renderTrajectoriesInternal, + 30, + ); } /** @@ -120,14 +128,13 @@ const TrackerLayerMixin = (Base) => * @ignore */ defineProperties(options) { - const { style, speed } = { - ...options, - }; - // Tracker options use to build the tracker. + let { regexPublishedLineName, publishedLineName, tripNumber, operator } = + options; const { + style, + speed, pixelRatio, - interpolate, hoverVehicleId, selectedVehicleId, filter, @@ -136,16 +143,7 @@ const TrackerLayerMixin = (Base) => live, } = options; - let { regexPublishedLineName, publishedLineName, tripNumber, operator } = - options; - const initTrackerOptions = { - pixelRatio: pixelRatio || window.devicePixelRatio || 1, - interpolate, - hoverVehicleId, - selectedVehicleId, - filter, - sort, style, }; @@ -186,30 +184,16 @@ const TrackerLayerMixin = (Base) => * Function to filter which vehicles to display. */ filter: { - get: () => - this.tracker ? this.tracker.filter : this.initTrackerOptions.filter, - set: (newFilter) => { - if (this.tracker) { - this.tracker.filter = newFilter; - } else { - this.initTrackerOptions.filter = newFilter; - } - }, + value: filter, + writable: true, }, /** * Function to sort the vehicles to display. */ sort: { - get: () => - this.tracker ? this.tracker.sort : this.initTrackerOptions.sort, - set: (newSort) => { - if (this.tracker) { - this.tracker.sort = newSort; - } else { - this.initTrackerOptions.sort = newSort; - } - }, + value: sort, + writable: true, }, /** @@ -247,7 +231,8 @@ const TrackerLayerMixin = (Base) => * Keep track of which trajectories are stored. */ trajectories: { - get: () => (this.tracker && this.tracker.trajectories) || [], + value: {}, + writable: true, }, /** @@ -261,51 +246,24 @@ const TrackerLayerMixin = (Base) => * Id of the hovered vehicle. */ hoverVehicleId: { - get: () => - this.tracker - ? this.tracker.hoverVehicleId - : this.initTrackerOptions.hoverVehicleId, - set: (newHoverVehicleId) => { - if (this.tracker) { - this.tracker.hoverVehicleId = newHoverVehicleId; - } else { - this.initTrackerOptions.hoverVehicleId = newHoverVehicleId; - } - }, + value: hoverVehicleId, + writable: true, }, /** * Id of the selected vehicle. */ selectedVehicleId: { - get: () => - this.tracker - ? this.tracker.selectedVehicleId - : this.initTrackerOptions.selectedVehicleId, - set: (newSelectedVehicleId) => { - if (this.tracker) { - this.tracker.selectedVehicleId = newSelectedVehicleId; - } else { - this.initTrackerOptions.selectedVehicleId = newSelectedVehicleId; - } - }, + value: selectedVehicleId, + writable: true, }, /** - * Pixel ratio use for the rendering. Default to window.devicePixelRatio. + * Id of the selected vehicle. */ pixelRatio: { - get: () => - this.tracker - ? this.tracker.pixelRatio - : this.initTrackerOptions.pixelRatio, - set: (newPixelRatio) => { - if (this.tracker) { - this.tracker.pixelRatio = newPixelRatio; - } else { - this.initTrackerOptions.pixelRatio = newPixelRatio; - } - }, + value: pixelRatio || 1, + writable: true, }, /** @@ -388,22 +346,24 @@ const TrackerLayerMixin = (Base) => // When we use the delay style we want to display delayed train on top by default if (this.useDelayStyle && !this.sort) { this.sort = (traj1, traj2) => { - if (traj1.delay === null && traj2.delay !== null) { + const props1 = traj1.properties; + const props2 = traj2.properties; + if (props1.delay === null && props2.delay !== null) { return 1; } - if (traj2.delay === null && traj1.delay !== null) { + if (props2.delay === null && props1.delay !== null) { return -1; } // We put cancelled train inbetween green and yellow trains // >=180000ms corresponds to yellow train - if (traj1.cancelled && !traj2.cancelled) { - return traj2.delay < 180000 ? -1 : 1; + if (props1.cancelled && !props2.cancelled) { + return props2.delay < 180000 ? -1 : 1; } - if (traj2.cancelled && !traj1.cancelled) { - return traj1.delay < 180000 ? 1 : -1; + if (props2.cancelled && !props1.cancelled) { + return props1.delay < 180000 ? 1 : -1; } - return traj2.delay - traj1.delay; + return props2.delay - props1.delay; }; } @@ -453,7 +413,7 @@ const TrackerLayerMixin = (Base) => this.stop(); unByKey(this.visibilityRef); if (this.tracker) { - this.tracker.destroy(); + this.clear(); this.tracker = null; } super.terminate(); @@ -536,15 +496,35 @@ const TrackerLayerMixin = (Base) => if (!this.tracker) { return false; } + // console.log('icic'); const time = this.live ? Date.now() : this.time; - this.tracker.renderTrajectories( - this.trajectories, - { ...viewState, time }, - noInterpolate, + const trajectories = Object.values(this.trajectories); + + // console.time('sort'); + if (this.sort) { + trajectories.sort(this.sort); + } + // console.timeEnd('sort'); + window.trajectories = trajectories; + + // console.time('render'); + this.renderState = this.tracker.renderTrajectories( + trajectories, + { ...viewState, pixelRatio: this.pixelRatio, time }, + { + noInterpolate, + hoverVehicleId: this.hoverVehicleId, + selectedVehicleId: this.selectedVehicleId, + iconScale: this.iconScale, + delayDisplay: this.delayDisplay, + delayOutlineColor: this.delayOutlineColor, + useDelayStyle: this.useDelayStyle, + }, ); + // console.timeEnd('render'); return true; } @@ -566,6 +546,7 @@ const TrackerLayerMixin = (Base) => renderTrajectories(viewState, noInterpolate) { if (this.requestId) { cancelAnimationFrame(this.requestId); + this.requestId = null; } if (this.useRequestAnimationFrame) { @@ -573,7 +554,7 @@ const TrackerLayerMixin = (Base) => this.renderTrajectoriesInternal(viewState, noInterpolate); }); } else { - this.renderTrajectoriesInternal(viewState, noInterpolate); + this.throttleRenderTajectories(viewState, noInterpolate); } } diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 99f81459..a5af8c3e 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -237,33 +237,14 @@ const TralisLayerMixin = (TrackerLayer) => /** * Add a trajectory to the tracker. * @param {TralisTrajectory} trajectory The trajectory to add. - * @param {boolean} [addOnTop=false] If true, the trajectory is added on top of - * the trajectory object. This affects the draw order. If addOnTop is - * true, the trajectory is drawn first and appears on bottom. * @private */ - addTrajectory(trajectory, addOnTop) { - // console.time(`addTrajectory${traj.train_id}`); + addTrajectory(trajectory) { + if (this.filter && !this.filter(trajectory)) { + return; + } this.trajectories[trajectory.properties.train_id] = trajectory; - // const idx = this.trajectories.findIndex( - // (t) => t.train_id === traj.train_id, - // ); - // const { time_intervals: timeIntervals } = traj; - - // // Properties needed to display the vehicle. - // const trajectory = { ...traj, id: traj.train_id, timeIntervals }; - // if (addOnTop) { - // this.trajectories.unshift(trajectory); - // if (idx !== -1) { - // this.tracker.trajectories.splice(idx + 1, 1); - // } - // } else { - // this.trajectories.push(trajectory); - // if (idx !== -1) { - // this.tracker.trajectories.splice(idx, 1); - // } - // } - // console.timeEnd(`addTrajectory${traj.train_id}`); + this.renderTrajectories(); } removeTrajectory(id) { diff --git a/src/common/utils/createTrackerFilters.js b/src/common/utils/createTrackerFilters.js index 4ad553d2..96fa08b1 100644 --- a/src/common/utils/createTrackerFilters.js +++ b/src/common/utils/createTrackerFilters.js @@ -18,7 +18,10 @@ const createFilters = (line, route, operator, regexLine) => { const regexLineList = typeof regexLine === 'string' ? [regexLine] : regexLine; const lineFilter = (item) => { - const name = item.name || (item.line && item.line.name) || ''; + const name = + item.properties.name || + (item.properties.line && item.properties.line.name) || + ''; if (!name) { return false; } @@ -35,15 +38,12 @@ const createFilters = (line, route, operator, regexLine) => { l.replace(/\s+/g, '').toUpperCase(), ); const lineFilter = (item) => { - const name = ( - item.name || - (item.line && item.line.name) || - '' - ).toUpperCase(); - if (!name) { + const { line: linee, name } = item.properties; + const lineName = (name || (linee && linee.name) || '').toUpperCase(); + if (!lineName) { return false; } - return lineList.includes(name); + return lineList.includes(lineName); }; filterList.push(lineFilter); } @@ -52,7 +52,10 @@ const createFilters = (line, route, operator, regexLine) => { const routes = typeof route === 'string' ? route.split(',') : route; const routeList = routes.map((item) => parseInt(item, 10)); const routeFilter = (item) => { - const routeId = parseInt(item.routeIdentifier.split('.')[0], 10); + const routeId = parseInt( + item.properties.routeIdentifier.split('.')[0], + 10, + ); return routeList.includes(routeId); }; filterList.push(routeFilter); @@ -61,7 +64,9 @@ const createFilters = (line, route, operator, regexLine) => { if (operator) { const operatorList = typeof operator === 'string' ? [operator] : operator; const operatorFilter = (item) => - operatorList.some((op) => new RegExp(op, 'i').test(item.operator)); + operatorList.some((op) => + new RegExp(op, 'i').test(item.properties.operator), + ); filterList.push(operatorFilter); } diff --git a/src/common/utils/getVehiclePosition.js b/src/common/utils/getVehiclePosition.js new file mode 100644 index 00000000..30eeb8ae --- /dev/null +++ b/src/common/utils/getVehiclePosition.js @@ -0,0 +1,52 @@ +import GeomType from 'ol/geom/GeometryType'; + +/** + * Interpolate or not the vehicle position from a trajectory at a specific date. + * + * @param {number} now Current date to interpolate a position with. In ms. + * @param {TralisTrajectory} trajectory The trajectory to interpolate. +@returns + */ +const getVehiclePosition = (now, trajectory, noInterpolate) => { + const { + time_intervals: timeIntervals, + olGeometry: geometry, + coordinate, + } = trajectory.properties; + + let coord; + let rotation; + + if (noInterpolate && coordinate) { + coord = coordinate; + } else if (geometry.getType() === GeomType.POINT) { + coord = geometry.getCoordinates(); + } else if (geometry.getType() === GeomType.LINE_STRING) { + const intervals = timeIntervals || []; + // Search the time interval. + for (let j = 0; j < intervals.length - 1; j += 1) { + // Rotation only available in tralis layer. + const [start, startFrac] = intervals[j]; + const [end, endFrac] = intervals[j + 1]; + + if (start <= now && now <= end) { + // interpolate position inside the time interval. + const timeFrac = Math.min((now - start) / (end - start), 1); + const geomFrac = timeFrac * (endFrac - startFrac) + startFrac; + coord = geometry.getCoordinateAt(geomFrac); + [, , rotation] = intervals[j]; + break; + } + } + } else { + // eslint-disable-next-line no-console + console.error( + 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', + geometry, + ); + } + + return { coord, rotation }; +}; + +export default getVehiclePosition; diff --git a/src/mapbox/layers/TrackerLayer.js b/src/mapbox/layers/TrackerLayer.js index f82acf8f..1a26a8f3 100644 --- a/src/mapbox/layers/TrackerLayer.js +++ b/src/mapbox/layers/TrackerLayer.js @@ -261,7 +261,9 @@ class TrackerLayer extends mixin(Layer) { * @private */ // eslint-disable-next-line class-methods-use-this - onMoveEnd() {} + onMoveEnd() { + this.renderTrajectories(); + } /** * Update the cursor style when hovering a vehicle. diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 1c9d35de..c31c687a 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -37,9 +37,8 @@ class TrackerLayer extends mixin(Layer) { options.renderWhenInteracting || (() => // Render trajectories on each render frame when the number of trajectories is small. - this.tracker && - this.tracker.renderedTrajectories && - this.tracker.renderedTrajectories.length <= 200); + ((this.renderState && this.renderState.nbTrajectoriesRendered) || 0) < + 200); /** @ignore */ this.olLayer = diff --git a/yarn.lock b/yarn.lock index a16c8e46..ed10b137 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9412,6 +9412,11 @@ lodash.some@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" From 21d63e43be0ab31bbed440ce926745de64b5342e Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Mon, 28 Mar 2022 12:03:20 +0200 Subject: [PATCH 076/109] Small change --- src/common/Tracker.js | 2 ++ src/common/mixins/TrackerLayerMixin.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 754cf511..8fbc07f7 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -73,6 +73,8 @@ export default class Tracker { * Draw all the trajectories available to the canvas. * @param {ViewState} trajectories An array of trajectories. * @param {ViewState} viewState The view state of the map. + * @param {boolean} options.hoverVehicleId The id of the vehicle to highlight. + * @param {boolean} options.selectedVehicleId The id of the vehicle to select. * @param {boolean} options.noInterpolate If true trajectories are not interpolated but * drawn at the last known coordinate. Use this for performance optimization * during map navigation. diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 1b85eaab..6123378d 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -118,7 +118,7 @@ const TrackerLayerMixin = (Base) => this.throttleRenderTajectories = throttle( this.renderTrajectoriesInternal, - 30, + 50, ); } From d1fe893e58584fc59b7171e31aa2d1a275dfbb71 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 09:00:44 +0200 Subject: [PATCH 077/109] Fix pixelRatio, and add useDebounce and useThrottle experimental options --- package.json | 3 +- src/common/Tracker.js | 4 +-- src/common/mixins/TrackerLayerMixin.js | 38 ++++++++++++++++++++++++-- yarn.lock | 4 +-- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 42cff551..24bf491e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "@turf/helpers": "^6.5.0", "@turf/transform-rotate": "^6.5.0", "codesandbox": "2.2.3", - "lodash.throttle": "^4.1.1", + "lodash.debounce": "4.1.1", + "lodash.throttle": "4.0.8", "path": "^0.12.7", "prop-types": "15.8.1", "query-string": "7.1.0", diff --git a/src/common/Tracker.js b/src/common/Tracker.js index 8fbc07f7..b5ea9da2 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -25,8 +25,8 @@ export default class Tracker { * @type {Canvas} */ this.canvas = options.canvas || document.createElement('canvas'); - this.canvas.width = options.width * this.pixelRatio; - this.canvas.height = options.height * this.pixelRatio; + this.canvas.width = options.width * (options.pixelRatio || 1); + this.canvas.height = options.height * (options.pixelRatio || 1); this.canvas.setAttribute( 'style', [ diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 6123378d..230029f5 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -3,6 +3,7 @@ import { buffer, containsCoordinate } from 'ol/extent'; import { unByKey } from 'ol/Observable'; import qs from 'query-string'; +import debounce from 'lodash.debounce'; import throttle from 'lodash.throttle'; import Tracker from '../Tracker'; import { timeSteps } from '../trackerConfig'; @@ -22,6 +23,8 @@ const OPERATOR_FILTER = 'operator'; * @classproperty {number} pixelRatio - Pixel ratio use to render the trajectories. Default to window.devicePixelRatio. * @classproperty {boolean} live - If true, the layer will always use Date.now() to render trajectories. Default to true. * @classproperty {boolean} useRequestAnimationFrame - If true, encapsulates the renderTrajectories calls in a requestAnimationFrame. Experimental. + * @classproperty {boolean} useThrottle - If true, encapsulates the renderTrajectories calls in a throttle function. Experimental. + * @classproperty {boolean} useDebounce - If true, encapsulates the renderTrajectories calls in a debounce function. Experimental. * @classproperty {boolean} isTrackerLayer - Property for duck typing since `instanceof` is not working when the instance was created on different bundles. * @classproperty {function} sort - Sort the trajectories. * @classproperty {function} style - Style of a trajectory. @@ -116,9 +119,16 @@ const TrackerLayerMixin = (Base) => this.renderTrajectoriesInternal = this.renderTrajectoriesInternal.bind(this); - this.throttleRenderTajectories = throttle( + this.throttleRenderTrajectories = debounce( this.renderTrajectoriesInternal, 50, + { leading: true, trailing: true }, + ); + + this.debounceRenderTrajectories = debounce( + this.renderTrajectoriesInternal, + 50, + { leading: true, trailing: true, maxWait: 1000 }, ); } @@ -262,7 +272,7 @@ const TrackerLayerMixin = (Base) => * Id of the selected vehicle. */ pixelRatio: { - value: pixelRatio || 1, + value: pixelRatio || window.devicePixelRatio || 1, writable: true, }, @@ -282,6 +292,22 @@ const TrackerLayerMixin = (Base) => writable: true, }, + /** + * If true, encapsulates the renderTrajectories calls in a throttle function. + */ + useThrottle: { + default: false, + writable: true, + }, + + /** + * If true, encapsulates the renderTrajectories calls in a debounce function. + */ + useDebounce: { + default: false, + writable: true, + }, + /** * Filter properties used in combination with permalink parameters. */ @@ -509,7 +535,9 @@ const TrackerLayerMixin = (Base) => // console.timeEnd('sort'); window.trajectories = trajectories; + // console.log('render', trajectories.length); // console.time('render'); + // console.log('pixelRatio', this.pixelRatio); this.renderState = this.tracker.renderTrajectories( trajectories, { ...viewState, pixelRatio: this.pixelRatio, time }, @@ -553,8 +581,12 @@ const TrackerLayerMixin = (Base) => this.requestId = requestAnimationFrame(() => { this.renderTrajectoriesInternal(viewState, noInterpolate); }); - } else { + } else if (this.useThrottle) { this.throttleRenderTajectories(viewState, noInterpolate); + } else if (this.useDebounce) { + this.debounceRenderTajectories(viewState, noInterpolate); + } else { + this.renderTrajectoriesInternal(viewState, noInterpolate); } } diff --git a/yarn.lock b/yarn.lock index ed10b137..3e3d1d97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9347,7 +9347,7 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.debounce@^4.0.8: +lodash.debounce@4.1.1, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= @@ -9412,7 +9412,7 @@ lodash.some@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= -lodash.throttle@^4.1.1: +lodash.throttle@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= From 7d4f0daab518ff1d03702f6811931d44e4d4debf Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 11:47:50 +0200 Subject: [PATCH 078/109] Fix disappearance of vehicles --- src/common/mixins/TrackerLayerMixin.js | 17 ++++------ src/common/mixins/TralisLayerMixin.js | 2 +- src/common/utils/getVehiclePosition.js | 45 ++++++++++++++++++-------- src/doc/examples/ol-tracker.js | 1 + 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 230029f5..9a5a2e3a 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -119,7 +119,7 @@ const TrackerLayerMixin = (Base) => this.renderTrajectoriesInternal = this.renderTrajectoriesInternal.bind(this); - this.throttleRenderTrajectories = debounce( + this.throttleRenderTrajectories = throttle( this.renderTrajectoriesInternal, 50, { leading: true, trailing: true }, @@ -128,7 +128,7 @@ const TrackerLayerMixin = (Base) => this.debounceRenderTrajectories = debounce( this.renderTrajectoriesInternal, 50, - { leading: true, trailing: true, maxWait: 1000 }, + { leading: true, trailing: true, maxWait: 5000 }, ); } @@ -288,7 +288,7 @@ const TrackerLayerMixin = (Base) => * If true, encapsulates the renderTrajectories calls in a requestAnimationFrame. */ useRequestAnimationFrame: { - default: false, + value: options.useRequestAnimationFrame || false, writable: true, }, @@ -296,7 +296,7 @@ const TrackerLayerMixin = (Base) => * If true, encapsulates the renderTrajectories calls in a throttle function. */ useThrottle: { - default: false, + value: options.useThrottle || false, writable: true, }, @@ -304,7 +304,7 @@ const TrackerLayerMixin = (Base) => * If true, encapsulates the renderTrajectories calls in a debounce function. */ useDebounce: { - default: false, + value: options.useDebounce || false, writable: true, }, @@ -522,7 +522,6 @@ const TrackerLayerMixin = (Base) => if (!this.tracker) { return false; } - // console.log('icic'); const time = this.live ? Date.now() : this.time; @@ -535,9 +534,7 @@ const TrackerLayerMixin = (Base) => // console.timeEnd('sort'); window.trajectories = trajectories; - // console.log('render', trajectories.length); // console.time('render'); - // console.log('pixelRatio', this.pixelRatio); this.renderState = this.tracker.renderTrajectories( trajectories, { ...viewState, pixelRatio: this.pixelRatio, time }, @@ -582,9 +579,9 @@ const TrackerLayerMixin = (Base) => this.renderTrajectoriesInternal(viewState, noInterpolate); }); } else if (this.useThrottle) { - this.throttleRenderTajectories(viewState, noInterpolate); + this.throttleRenderTrajectories(viewState, noInterpolate); } else if (this.useDebounce) { - this.debounceRenderTajectories(viewState, noInterpolate); + this.debounceRenderTrajectories(viewState, noInterpolate); } else { this.renderTrajectoriesInternal(viewState, noInterpolate); } diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index a5af8c3e..531ecef4 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -302,9 +302,9 @@ const TralisLayerMixin = (TrackerLayer) => trajectory.properties.olGeometry = this.format.readGeometry(geometry); } + // TODO Make sure the timeOffset is useful. May be we can remove it. trajectory.properties.timeOffset = Date.now() - data.timestamp; this.addTrajectory(trajectory); - // console.timeEnd(`onTrajectoryMessage${data.content.properties.train_id}`); } /** diff --git a/src/common/utils/getVehiclePosition.js b/src/common/utils/getVehiclePosition.js index 30eeb8ae..5d4e6ee8 100644 --- a/src/common/utils/getVehiclePosition.js +++ b/src/common/utils/getVehiclePosition.js @@ -22,20 +22,39 @@ const getVehiclePosition = (now, trajectory, noInterpolate) => { } else if (geometry.getType() === GeomType.POINT) { coord = geometry.getCoordinates(); } else if (geometry.getType() === GeomType.LINE_STRING) { - const intervals = timeIntervals || []; - // Search the time interval. - for (let j = 0; j < intervals.length - 1; j += 1) { - // Rotation only available in tralis layer. - const [start, startFrac] = intervals[j]; - const [end, endFrac] = intervals[j + 1]; + const intervals = timeIntervals || [[]]; + const firstInterval = intervals[0]; + const lastInterval = intervals[intervals.length - 1]; - if (start <= now && now <= end) { - // interpolate position inside the time interval. - const timeFrac = Math.min((now - start) / (end - start), 1); - const geomFrac = timeFrac * (endFrac - startFrac) + startFrac; - coord = geometry.getCoordinateAt(geomFrac); - [, , rotation] = intervals[j]; - break; + // Between the last time interval of a trajectory event and the beginning + // of the new trajectory event, there is few seconds, can be 6 to 30 + // seconds (that's why the vehicle jumps sometimes). + // So we make the choice here to display the last (or the first) position + // of an trajectory event instead of removing them, if the current date is + // outside the time intervals we display the vehicle at the last (or first) position known. + if (now < firstInterval[0]) { + // Display first position known. + [[, , rotation]] = firstInterval; + coord = geometry.getFirstCoordinate(); + } else if (now > lastInterval[0]) { + // Display last position known. + [, , rotation] = lastInterval; + coord = geometry.getLastCoordinate(); + } else { + // Interpolate position using time intervals. + for (let j = 0; j < intervals.length - 1; j += 1) { + // Rotation only available in tralis layer. + const [start, startFrac] = intervals[j]; + const [end, endFrac] = intervals[j + 1]; + + if (start <= now && now <= end) { + // interpolate position inside the time interval. + const timeFrac = Math.min((now - start) / (end - start), 1); + const geomFrac = timeFrac * (endFrac - startFrac) + startFrac; + coord = geometry.getCoordinateAt(geomFrac); + [, , rotation] = intervals[j]; + break; + } } } } else { diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 0a66e13a..2824acda 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -20,6 +20,7 @@ export default () => { const tracker = new TralisLayer({ url: 'wss://tralis-tracker-api.geops.io/ws', apiKey: window.apiKey, + useDebounce: true, }); tracker.onClick(([feature]) => { From 72a83b712172b880a1177caf224cd69b70f12b12 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 14:30:22 +0200 Subject: [PATCH 079/109] Don't render when interacting --- src/common/mixins/TrackerLayerMixin.js | 27 +++++++++++++------------- src/doc/examples/ol-tracker.js | 2 +- src/ol/layers/TrackerLayer.js | 15 +++----------- src/ol/layers/TralisLayer.test.js | 2 +- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 9a5a2e3a..6e2e4a41 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -122,7 +122,7 @@ const TrackerLayerMixin = (Base) => this.throttleRenderTrajectories = throttle( this.renderTrajectoriesInternal, 50, - { leading: true, trailing: true }, + { leading: false, trailing: true }, ); this.debounceRenderTrajectories = debounce( @@ -293,10 +293,10 @@ const TrackerLayerMixin = (Base) => }, /** - * If true, encapsulates the renderTrajectories calls in a throttle function. + * If true, encapsulates the renderTrajectories calls in a throttle function. Default to true. */ useThrottle: { - value: options.useThrottle || false, + value: options.useThrottle || true, writable: true, }, @@ -372,24 +372,22 @@ const TrackerLayerMixin = (Base) => // When we use the delay style we want to display delayed train on top by default if (this.useDelayStyle && !this.sort) { this.sort = (traj1, traj2) => { - const props1 = traj1.properties; - const props2 = traj2.properties; - if (props1.delay === null && props2.delay !== null) { + if (traj1.delay === null && traj2.delay !== null) { return 1; } - if (props2.delay === null && props1.delay !== null) { + if (traj2.delay === null && traj1.delay !== null) { return -1; } // We put cancelled train inbetween green and yellow trains // >=180000ms corresponds to yellow train - if (props1.cancelled && !props2.cancelled) { - return props2.delay < 180000 ? -1 : 1; + if (traj1.cancelled && !traj2.cancelled) { + return traj2.delay < 180000 ? -1 : 1; } - if (props2.cancelled && !props1.cancelled) { - return props1.delay < 180000 ? 1 : -1; + if (traj2.cancelled && !traj1.cancelled) { + return traj1.delay < 180000 ? 1 : -1; } - return props2.delay - props1.delay; + return traj2.delay - traj1.delay; }; } @@ -548,6 +546,7 @@ const TrackerLayerMixin = (Base) => useDelayStyle: this.useDelayStyle, }, ); + this.isRendering = false; // console.timeEnd('render'); return true; @@ -578,10 +577,10 @@ const TrackerLayerMixin = (Base) => this.requestId = requestAnimationFrame(() => { this.renderTrajectoriesInternal(viewState, noInterpolate); }); - } else if (this.useThrottle) { - this.throttleRenderTrajectories(viewState, noInterpolate); } else if (this.useDebounce) { this.debounceRenderTrajectories(viewState, noInterpolate); + } else if (this.useThrottle) { + this.throttleRenderTrajectories(viewState, noInterpolate); } else { this.renderTrajectoriesInternal(viewState, noInterpolate); } diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 2824acda..2d9f14a7 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -20,7 +20,7 @@ export default () => { const tracker = new TralisLayer({ url: 'wss://tralis-tracker-api.geops.io/ws', apiKey: window.apiKey, - useDebounce: true, + useThrottle: true, }); tracker.onClick(([feature]) => { diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index c31c687a..5db7dcf0 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -79,15 +79,8 @@ class TrackerLayer extends mixin(Layer) { resolution: renderedResolution, rotation: renderedRotation, } = this.renderedViewState; - if ( - this.renderWhenInteracting && - this.renderWhenInteracting( - frameState.viewState, - this.renderedViewState, - ) - ) { - this.renderTrajectories(true); - } else if (renderedResolution / resolution >= 3) { + + if (renderedResolution / resolution >= 3) { // Avoid having really big points when zooming fast. this.tracker.clear(); } else { @@ -204,9 +197,7 @@ class TrackerLayer extends mixin(Layer) { let isRendered = false; const blockRendering = - !this.renderWhenInteracting(viewState, this.renderedViewState) && - (this.map.getView().getAnimating() || - this.map.getView().getInteracting()); + this.map.getView().getAnimating() || this.map.getView().getInteracting(); // Don't render the map when the map is animating or interacting. isRendered = blockRendering diff --git a/src/ol/layers/TralisLayer.test.js b/src/ol/layers/TralisLayer.test.js index be65052f..d1bd9858 100644 --- a/src/ol/layers/TralisLayer.test.js +++ b/src/ol/layers/TralisLayer.test.js @@ -73,7 +73,7 @@ describe('TrajservLayer', () => { expect(laye.sort).toBe(fn); }); - test('should set a default sort function if useDelayStyle is used.', () => { + test.only('should set a default sort function if useDelayStyle is used.', () => { const laye = new TralisLayer({ url: 'ws://localhost:1234', apiKey: 'apiKey', From d5079419a08beabbe52c59494b9e9a6ef714f8fa Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 14:44:47 +0200 Subject: [PATCH 080/109] Fix tests --- src/common/mixins/TrackerLayerMixin.js | 17 ++++++----- src/common/utils/createTrackerFilters.test.js | 30 +++++++++++-------- src/doc/examples/ol-tracker.js | 2 +- src/ol/layers/TrackerLayer.js | 12 -------- src/ol/layers/TralisLayer.test.js | 12 ++++---- 5 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 6e2e4a41..96e3d0b6 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -372,22 +372,25 @@ const TrackerLayerMixin = (Base) => // When we use the delay style we want to display delayed train on top by default if (this.useDelayStyle && !this.sort) { this.sort = (traj1, traj2) => { - if (traj1.delay === null && traj2.delay !== null) { + const props1 = traj1.properties; + const props2 = traj2.properties; + + if (props1.delay === null && props2.delay !== null) { return 1; } - if (traj2.delay === null && traj1.delay !== null) { + if (props2.delay === null && props1.delay !== null) { return -1; } // We put cancelled train inbetween green and yellow trains // >=180000ms corresponds to yellow train - if (traj1.cancelled && !traj2.cancelled) { - return traj2.delay < 180000 ? -1 : 1; + if (props1.cancelled && !props2.cancelled) { + return props2.delay < 180000 ? -1 : 1; } - if (traj2.cancelled && !traj1.cancelled) { - return traj1.delay < 180000 ? 1 : -1; + if (props2.cancelled && !props1.cancelled) { + return props1.delay < 180000 ? 1 : -1; } - return traj2.delay - traj1.delay; + return props2.delay - props1.delay; }; } diff --git a/src/common/utils/createTrackerFilters.test.js b/src/common/utils/createTrackerFilters.test.js index 61fb96aa..0f3e77d7 100644 --- a/src/common/utils/createTrackerFilters.test.js +++ b/src/common/utils/createTrackerFilters.test.js @@ -1,24 +1,30 @@ import createTrackerFilters from './createTrackerFilters'; const u1 = { - routeIdentifier: '001.000827.004:7', - operator: 'FoO', - line: { - name: 'U1', + properties: { + routeIdentifier: '001.000827.004:7', + operator: 'FoO', + line: { + name: 'U1', + }, }, }; const ireta = { - routeIdentifier: '0022.000827.004:7', - operator: 'BAR', - line: { - name: 'IRETA', + properties: { + routeIdentifier: '0022.000827.004:7', + operator: 'BAR', + line: { + name: 'IRETA', + }, }, }; const arb = { - routeIdentifier: '00333.000827.004:7', - operator: 'qux', - line: { - name: 'ARB', + properties: { + routeIdentifier: '00333.000827.004:7', + operator: 'qux', + line: { + name: 'ARB', + }, }, }; diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 2d9f14a7..35ce2429 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -20,7 +20,7 @@ export default () => { const tracker = new TralisLayer({ url: 'wss://tralis-tracker-api.geops.io/ws', apiKey: window.apiKey, - useThrottle: true, + useDelayStyle: true, }); tracker.onClick(([feature]) => { diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 5db7dcf0..3c5e9cf0 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -28,18 +28,6 @@ class TrackerLayer extends mixin(Layer) { ...options, }); - /** - * Boolean that defines if the layer is allow to renderTrajectories when the map is zooming, rotating or poanning true. - * It's useful to avoid rendering the map when the map is animating or interacting. - * @type {function} - */ - this.renderWhenInteracting = - options.renderWhenInteracting || - (() => - // Render trajectories on each render frame when the number of trajectories is small. - ((this.renderState && this.renderState.nbTrajectoriesRendered) || 0) < - 200); - /** @ignore */ this.olLayer = options.olLayer || diff --git a/src/ol/layers/TralisLayer.test.js b/src/ol/layers/TralisLayer.test.js index d1bd9858..3c56267e 100644 --- a/src/ol/layers/TralisLayer.test.js +++ b/src/ol/layers/TralisLayer.test.js @@ -82,12 +82,12 @@ describe('TrajservLayer', () => { expect(laye).toBeInstanceOf(TralisLayer); expect(laye.useDelayStyle).toBe(true); expect(laye.sort).toBeDefined(); - const red = { delay: 1000000 }; - const yellow = { delay: 180000 }; - const green2 = { delay: 178990 }; - const green = { delay: 0 }; - const gray = { delay: null }; - const cancelled = { cancelled: true, delay: 3000000 }; + const red = { properties: { delay: 1000000 } }; + const yellow = { properties: { delay: 180000 } }; + const green2 = { properties: { delay: 178990 } }; + const green = { properties: { delay: 0 } }; + const gray = { properties: { delay: null } }; + const cancelled = { properties: { cancelled: true, delay: 3000000 } }; const trajectories = [gray, green, yellow, red, green2, cancelled]; trajectories.sort(laye.sort); From 8abc3d1a321ee70433dbf091f1c9801eea58a218 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 14:45:32 +0200 Subject: [PATCH 081/109] Fix tests --- src/doc/examples/ol-tracker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 35ce2429..0a66e13a 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -20,7 +20,6 @@ export default () => { const tracker = new TralisLayer({ url: 'wss://tralis-tracker-api.geops.io/ws', apiKey: window.apiKey, - useDelayStyle: true, }); tracker.onClick(([feature]) => { From 3f6a2e7898996b4b20ef1cbdd9ea66a8964b0b15 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 14:47:31 +0200 Subject: [PATCH 082/109] Fix tests --- package.json | 4 ++-- yarn.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 24bf491e..71153f1e 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "@turf/helpers": "^6.5.0", "@turf/transform-rotate": "^6.5.0", "codesandbox": "2.2.3", - "lodash.debounce": "4.1.1", - "lodash.throttle": "4.0.8", + "lodash.debounce": "4.0.8", + "lodash.throttle": "4.1.1", "path": "^0.12.7", "prop-types": "15.8.1", "query-string": "7.1.0", diff --git a/yarn.lock b/yarn.lock index 3e3d1d97..0d67fcee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9347,7 +9347,7 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.debounce@4.1.1, lodash.debounce@^4.0.8: +lodash.debounce@4.0.8, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= From cb61c69881e48075bd59a0101731bf0be303168c Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 14:59:50 +0200 Subject: [PATCH 083/109] v1.7.8-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4edb0a1c..daaaa717 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.7", + "version": "1.7.8-beta.1", "main": "index.js", "module": "module.js", "dependencies": { From 56b7af4b55b781e3ffbe5a95e12ad1aee268f978 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 15:30:04 +0200 Subject: [PATCH 084/109] Clean doc --- src/common/mixins/TralisLayerMixin.js | 2 +- src/mapbox/layers/TralisLayer.js | 2 +- src/ol/layers/TralisLayer.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 531ecef4..a90193ec 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -211,7 +211,7 @@ const TralisLayerMixin = (TrackerLayer) => } /** - * Determine if the trajectory must be rendered or not. + * Determine if the trajectory is useless and should be removed from the list or not. * By default, this function exclude vehicles: * - that have their trajectory outside the current extent and * - that are not a train and zoom level is lower than layer's minZoomNonTrain property. diff --git a/src/mapbox/layers/TralisLayer.js b/src/mapbox/layers/TralisLayer.js index 08437571..8abe8053 100644 --- a/src/mapbox/layers/TralisLayer.js +++ b/src/mapbox/layers/TralisLayer.js @@ -20,7 +20,7 @@ import mixin from '../../common/mixins/TralisLayerMixin'; */ class TralisLayer extends mixin(TrackerLayer) { /** - * Determine if the trajectory must be removed or not added to the list + * Remove the trajectory form the list if necessary. * * @private */ diff --git a/src/ol/layers/TralisLayer.js b/src/ol/layers/TralisLayer.js index 91f64cfa..4f1924ee 100644 --- a/src/ol/layers/TralisLayer.js +++ b/src/ol/layers/TralisLayer.js @@ -25,7 +25,7 @@ const format = new GeoJSON(); */ class TralisLayer extends mixin(TrackerLayer) { /** - * Determine if the trajectory must be removed or not added to the list + * Remove the trajectory form the list if necessary. * * @private */ From deda20e9fbc89a731b53708a51bd9b51b5a73e7a Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 16:39:56 +0200 Subject: [PATCH 085/109] Use the tracker class --- src/common/Tracker.js | 41 ++++--- src/common/mixins/TralisLayerMixin.js | 9 +- src/common/tracker.worker.js | 170 +++----------------------- src/ol/layers/TrackerLayer.js | 4 +- 4 files changed, 41 insertions(+), 183 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index b5ea9da2..4abc68c0 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -25,26 +25,24 @@ export default class Tracker { * @type {Canvas} */ this.canvas = options.canvas || document.createElement('canvas'); + this.canvas.width = options.width * (options.pixelRatio || 1); this.canvas.height = options.height * (options.pixelRatio || 1); - this.canvas.setAttribute( - 'style', - [ - 'position: absolute', - 'top: 0', - 'bottom: 0', - `width: ${options.width}px`, - `height: ${options.height}px`, - 'pointer-events: none', - 'visibility: visible', - 'margin-top: inherit', // for scrolling behavior. - ].join(';'), - ); - /** - * 2d drawing context on the canvas. - * @type {CanvasRenderingContext2D} - */ - this.canvasContext = this.canvas.getContext('2d'); + if (this.canvas.setAttribute) { + this.canvas.setAttribute( + 'style', + [ + 'position: absolute', + 'top: 0', + 'bottom: 0', + `width: ${options.width}px`, + `height: ${options.height}px`, + 'pointer-events: none', + 'visibility: visible', + 'margin-top: inherit', // for scrolling behavior. + ].join(';'), + ); + } } /** @@ -118,8 +116,11 @@ export default class Tracker { -center[1], ); - canvas.style.width = `${canvas.width / pixelRatio}px`; - canvas.style.height = `${canvas.height / pixelRatio}px`; + // Offscreen canvas has not style attribute + if (canvas.style) { + canvas.style.width = `${canvas.width / pixelRatio}px`; + canvas.style.height = `${canvas.height / pixelRatio}px`; + } let hoverVehicleImg; let hoverVehiclePx; diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index d233e2b9..32b722a8 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -244,25 +244,24 @@ const TralisLayerMixin = (TrackerLayer) => return; } + this.trajectories[trajectory.properties.train_id] = trajectory; + this.renderTrajectories(); + if (this.worker) { this.worker.postMessage({ action: 'addTrajectory', trajectory, }); - } else { - this.trajectories[trajectory.properties.train_id] = trajectory; - this.renderTrajectories(); } } removeTrajectory(id) { + delete this.trajectories[id]; if (this.worker) { this.worker.postMessage({ action: 'removeTrajectory', trajectoryId: id, }); - } else { - delete this.trajectories[id]; } } diff --git a/src/common/tracker.worker.js b/src/common/tracker.worker.js index 2ce7f037..a08c6c2e 100644 --- a/src/common/tracker.worker.js +++ b/src/common/tracker.worker.js @@ -1,7 +1,7 @@ import stringify from 'json-stringify-safe'; -import { compose, apply, create } from 'ol/transform'; import GeoJSON from 'ol/format/GeoJSON'; -import { delayTrackerStyle, getVehiclePosition } from './utils'; +import { delayTrackerStyle } from './utils'; +import Tracker from './Tracker'; const debug = false; @@ -9,7 +9,12 @@ const trajectories = {}; let renderTimeout; let count = 0; const format = new GeoJSON(); -let canvas; +const tracker = new Tracker({ + canvas: new OffscreenCanvas(1, 1), + width: 1, + height: 1, + style: delayTrackerStyle, +}); const render = (evt) => { // eslint-disable-next-line no-console @@ -19,163 +24,16 @@ const render = (evt) => { count = 0; const { frameState, viewState, options } = evt.data; - const { - time = Date.now(), - size = [], - center, - resolution, - rotation = 0, - pixelRatio, - } = viewState; - const { - iconScale, - noInterpolate = false, - hoverVehicleId, - selectedVehicleId, - } = options; - - if (!canvas) { - canvas = new OffscreenCanvas(size[0] * pixelRatio, size[1] * pixelRatio); - } - - const context = canvas.getContext('2d'); - const [width, height] = size; - if (width && height && (canvas.width !== width || canvas.height !== height)) { - [canvas.width, canvas.height] = [width * pixelRatio, height * pixelRatio]; - } - - const coordinateToPixelTransform = compose( - create(), - size[0] / 2, - size[1] / 2, - 1 / resolution, - -1 / resolution, - -rotation, - -center[0], - -center[1], + const { nbTrajectoriesRendered } = tracker.renderTrajectories( + Object.values(trajectories), + viewState, + options, ); - // canvas.style.width = `${canvas.width / pixelRatio}px`; - // canvas.style.height = `${canvas.height / pixelRatio}px`; - - let hoverVehicleImg; - let hoverVehiclePx; - let hoverVehicleWidth; - let hoverVehicleHeight; - let selectedVehicleImg; - let selectedVehiclePx; - let selectedVehicleWidth; - let selectedVehicleHeight; - let nbRendered = 0; - - const keys = Object.keys(trajectories); - for (let i = (keys || []).length - 1; i >= 0; i -= 1) { - const trajectory = trajectories[keys[i]]; - - // We simplify the trajectory object - const { train_id: id, timeOffset } = trajectory.properties; - - // We set the rotation and the timeFraction of the trajectory (used by tralis). - // if rotation === null that seems there is no rotation available. - const { coord, rotation: rotationIcon } = getVehiclePosition( - time - (timeOffset || 0), - trajectory, - noInterpolate, - ); - - // We store the current vehicle position to the trajectory. - trajectories[keys[i]].properties.coordinate = coord; - trajectories[keys[i]].properties.rotation = rotationIcon; - - if (!coord) { - // eslint-disable-next-line no-continue - continue; - } - - let px = apply(coordinateToPixelTransform, [...coord]); - if (!px) { - // eslint-disable-next-line no-continue - continue; - } - - px = px.map((p) => p * pixelRatio); - - if ( - px[0] < 0 || - px[0] > canvas.width || - px[1] < 0 || - px[1] > canvas.height - ) { - // eslint-disable-next-line no-continue - continue; - } - - const vehicleImg = delayTrackerStyle(trajectory, viewState, options); - - if (!vehicleImg) { - // eslint-disable-next-line no-continue - continue; - } - - nbRendered += 1; - - let imgWidth = vehicleImg.width; - let imgHeight = vehicleImg.height; - - if (iconScale) { - imgHeight = Math.floor(imgHeight * iconScale); - imgWidth = Math.floor(imgWidth * iconScale); - } - - if (hoverVehicleId !== id && selectedVehicleId !== id) { - context.drawImage( - vehicleImg, - px[0] - imgWidth / 2, - px[1] - imgHeight / 2, - imgWidth, - imgHeight, - ); - } - if (hoverVehicleId === id) { - // Store the canvas to draw it at the end - hoverVehicleImg = vehicleImg; - hoverVehiclePx = px; - hoverVehicleWidth = imgWidth; - hoverVehicleHeight = imgHeight; - } - - if (selectedVehicleId === id) { - // Store the canvas to draw it at the end - selectedVehicleImg = vehicleImg; - selectedVehiclePx = px; - selectedVehicleWidth = imgWidth; - selectedVehicleHeight = imgHeight; - } - } - - if (selectedVehicleImg) { - context.drawImage( - selectedVehicleImg, - selectedVehiclePx[0] - selectedVehicleWidth / 2, - selectedVehiclePx[1] - selectedVehicleHeight / 2, - selectedVehicleWidth, - selectedVehicleHeight, - ); - } - - if (hoverVehicleImg) { - context.drawImage( - hoverVehicleImg, - hoverVehiclePx[0] - hoverVehicleWidth / 2, - hoverVehiclePx[1] - hoverVehicleHeight / 2, - hoverVehicleWidth, - hoverVehicleHeight, - ); - } if (debug) console.timeEnd('render'); if (debug) console.log('NUMBER OF STYLES CREATED', count); - const imageData = canvas.transferToImageBitmap(); + const imageData = tracker.canvas.transferToImageBitmap(); const state = { ...frameState }; delete state.layerStatesArray; delete state.viewState.projection; @@ -186,7 +44,7 @@ const render = (evt) => { action: 'rendered', imageData, // transform: rendererTransform, - nbRenderedTrajectories: nbRendered, + nbRenderedTrajectories: nbTrajectoriesRendered, frameState: JSON.parse(stringify(state)), }, [imageData], diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 977928a4..bf61292d 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -266,7 +266,7 @@ class TrackerLayer extends mixin(Layer) { const blockRendering = this.map.getView().getAnimating() || this.map.getView().getInteracting(); - if (this.worker && this.mainThreadFrameState) { + if (!blockRendering && this.worker && this.mainThreadFrameState) { const frameState = { ...this.mainThreadFrameState }; delete frameState.layerStatesArray; delete frameState.viewState.projection; @@ -284,7 +284,7 @@ class TrackerLayer extends mixin(Layer) { useDelayStyle: this.useDelayStyle, }, }); - } else { + } else if (!this.worker) { // Don't render the map when the map is animating or interacting. isRendered = blockRendering ? false From 292e70c05b892e00a5b618794dd693896781b50e Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 8 Apr 2022 17:20:50 +0200 Subject: [PATCH 086/109] Remove useless canvas stuff --- src/common/Tracker.js | 52 ++++---------------------- src/common/mixins/TrackerLayerMixin.js | 11 +++--- src/ol/layers/TrackerLayer.js | 4 +- 3 files changed, 15 insertions(+), 52 deletions(-) diff --git a/src/common/Tracker.js b/src/common/Tracker.js index b5ea9da2..9857747d 100644 --- a/src/common/Tracker.js +++ b/src/common/Tracker.js @@ -25,48 +25,6 @@ export default class Tracker { * @type {Canvas} */ this.canvas = options.canvas || document.createElement('canvas'); - this.canvas.width = options.width * (options.pixelRatio || 1); - this.canvas.height = options.height * (options.pixelRatio || 1); - this.canvas.setAttribute( - 'style', - [ - 'position: absolute', - 'top: 0', - 'bottom: 0', - `width: ${options.width}px`, - `height: ${options.height}px`, - 'pointer-events: none', - 'visibility: visible', - 'margin-top: inherit', // for scrolling behavior. - ].join(';'), - ); - /** - * 2d drawing context on the canvas. - * @type {CanvasRenderingContext2D} - */ - this.canvasContext = this.canvas.getContext('2d'); - } - - /** - * Set visibility of the canvas. - * @param {boolean} visible The visibility of the layer - */ - setVisible(visible) { - if (this.canvas) { - this.canvas.style.visibility = visible ? 'visible' : 'hidden'; - } - } - - /** - * Clear the canvas. - * @private - */ - clear() { - if (this.canvas) { - this.canvas - .getContext('2d') - .clearRect(0, 0, this.canvas.width, this.canvas.height); - } } /** @@ -94,10 +52,11 @@ export default class Tracker { hoverVehicleId, selectedVehicleId, } = options; - this.clear(); const { canvas } = this; const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + const [width, height] = size; if ( width && @@ -118,8 +77,11 @@ export default class Tracker { -center[1], ); - canvas.style.width = `${canvas.width / pixelRatio}px`; - canvas.style.height = `${canvas.height / pixelRatio}px`; + // Offscreen canvas has not style attribute + if (canvas.style) { + canvas.style.width = `${canvas.width / pixelRatio}px`; + canvas.style.height = `${canvas.height / pixelRatio}px`; + } let hoverVehicleImg; let hoverVehiclePx; diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 96e3d0b6..0dded5d6 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -456,7 +456,6 @@ const TrackerLayerMixin = (Base) => */ start() { this.stop(); - this.tracker.setVisible(true); this.renderTrajectories(); this.startUpdateTime(); @@ -490,8 +489,9 @@ const TrackerLayerMixin = (Base) => stop() { this.stopUpdateTime(); if (this.tracker) { - this.tracker.setVisible(false); - this.tracker.clear(); + const { canvas } = this.tracker; + const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); } } @@ -535,7 +535,7 @@ const TrackerLayerMixin = (Base) => // console.timeEnd('sort'); window.trajectories = trajectories; - // console.time('render'); + console.time('render'); this.renderState = this.tracker.renderTrajectories( trajectories, { ...viewState, pixelRatio: this.pixelRatio, time }, @@ -549,9 +549,8 @@ const TrackerLayerMixin = (Base) => useDelayStyle: this.useDelayStyle, }, ); - this.isRendering = false; - // console.timeEnd('render'); + console.timeEnd('render'); return true; } diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index 3c5e9cf0..870e07ea 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -70,7 +70,9 @@ class TrackerLayer extends mixin(Layer) { if (renderedResolution / resolution >= 3) { // Avoid having really big points when zooming fast. - this.tracker.clear(); + const { canvas } = this.tracker; + const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); } else { const pixelCenterRendered = this.map.getPixelFromCoordinate(renderedCenter); From 0d681441b4a6356e00ae7d2691ddd8f0db76db42 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Apr 2022 11:12:12 +0200 Subject: [PATCH 087/109] Improve rendering debounce and throttle time --- src/common/mixins/TrackerLayerMixin.js | 25 ++++++++++++++++++++----- src/common/mixins/TralisLayerMixin.js | 4 ++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 0dded5d6..8f21001a 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -535,7 +535,7 @@ const TrackerLayerMixin = (Base) => // console.timeEnd('sort'); window.trajectories = trajectories; - console.time('render'); + // console.time('render'); this.renderState = this.tracker.renderTrajectories( trajectories, { ...viewState, pixelRatio: this.pixelRatio, time }, @@ -550,7 +550,7 @@ const TrackerLayerMixin = (Base) => }, ); - console.timeEnd('render'); + // console.timeEnd('render'); return true; } @@ -575,13 +575,13 @@ const TrackerLayerMixin = (Base) => this.requestId = null; } - if (this.useRequestAnimationFrame) { + if (!noInterpolate && this.useRequestAnimationFrame) { this.requestId = requestAnimationFrame(() => { this.renderTrajectoriesInternal(viewState, noInterpolate); }); - } else if (this.useDebounce) { + } else if (!noInterpolate && this.useDebounce) { this.debounceRenderTrajectories(viewState, noInterpolate); - } else if (this.useThrottle) { + } else if (!noInterpolate && this.useThrottle) { this.throttleRenderTrajectories(viewState, noInterpolate); } else { this.renderTrajectoriesInternal(viewState, noInterpolate); @@ -687,6 +687,21 @@ const TrackerLayerMixin = (Base) => const roundedZoom = Math.round(zoom); const timeStep = timeSteps[roundedZoom] || 25; const nextTick = Math.max(25, timeStep / this.speed); + + // TODO: see if this should go elsewhere. + if (this.useThrottle) { + this.throttleRenderTrajectories = throttle( + this.renderTrajectoriesInternal, + Math.min(nextTick, 500), + { leading: true, trailing: true }, + ); + } else if (this.useDebounce) { + this.debounceRenderTrajectories = debounce( + this.renderTrajectoriesInternal, + Math.min(nextTick, 500), + { leading: true, trailing: true, maxWait: 5000 }, + ); + } return nextTick; } diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index a90193ec..1bb48b31 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -338,7 +338,7 @@ const TralisLayerMixin = (TrackerLayer) => if (this.hoverVehicleId !== id) { /** @ignore */ this.hoverVehicleId = id; - this.renderTrajectories(); + this.renderTrajectories(true); } super.onFeatureHover(features, layer, coordinate); } @@ -360,7 +360,7 @@ const TralisLayerMixin = (TrackerLayer) => /** @ignore */ this.selectedVehicleId = id; this.selectedVehicle = feature; - this.renderTrajectories(); + this.renderTrajectories(true); } super.onFeatureClick(features, layer, coordinate); } From 878d7b1d9d44c26a948186690594653ac25032db Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Apr 2022 11:13:28 +0200 Subject: [PATCH 088/109] v1.7.8-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index daaaa717..e4258609 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.1", + "version": "1.7.8-beta.2", "main": "index.js", "module": "module.js", "dependencies": { From 5010871d1f33dde6e0e192090bceb0a58a0b77af Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Apr 2022 11:25:16 +0200 Subject: [PATCH 089/109] Fix call of style --- src/common/mixins/TrackerLayerMixin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index 8f21001a..aaff6859 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -415,7 +415,7 @@ const TrackerLayerMixin = (Base) => super.init(map); this.tracker = new Tracker({ - style: (trajectory, viewState) => this.style(trajectory, viewState), + style: (...args) => this.style(...args), ...this.initTrackerOptions, ...options, }); From 2632be47c42d21cf537cb90f63e94992008838ed Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 19 Apr 2022 11:26:04 +0200 Subject: [PATCH 090/109] v1.7.8-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4258609..63dd78de 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.2", + "version": "1.7.8-beta.3", "main": "index.js", "module": "module.js", "dependencies": { From dd4a4e7b1cdfd8b78cb292dada52997563f1b0cb Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 14:06:48 +0200 Subject: [PATCH 091/109] Fix parse json --- src/api/tralis/TralisAPI.js | 18 +++++++++++++++++- src/api/tralis/WebSocketConnector.js | 25 ++++++++++++++++++------- src/common/mixins/TralisLayerMixin.js | 1 + src/common/utils/getVehiclePosition.js | 2 +- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index a4a22f6b..08bd6bc0 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -77,7 +77,7 @@ class TralisAPI { } const { apiKey } = opt; - let { url, projection, bbox } = opt; + let { url, projection, bbox, buffer } = opt; const conn = new WebSocketConnector(); if (apiKey) { @@ -114,6 +114,17 @@ class TralisAPI { } }, }, + buffer: { + get: () => buffer, + set: (newBuffer) => { + if (JSON.stringify(newBuffer) !== JSON.stringify(buffer)) { + buffer = newBuffer; + if (this.conn) { + this.conn.send(`BUFFER ${buffer.join(' ')}`); + } + } + }, + }, /** * The websocket helper class to connect the websocket. * @@ -178,10 +189,15 @@ class TralisAPI { if (this.projection) { this.conn.send(`PROJECTION ${this.projection}`); } + if (this.bbox) { this.conn.send(`BBOX ${this.bbox.join(' ')}`); } + if (this.buffer) { + this.conn.send(`BUFFER ${this.buffer.join(' ')}`); + } + /** * Keep websocket alive */ diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 574cec26..4d88d5d5 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -172,16 +172,27 @@ class WebSocketConnector { this.unlisten(params, cb); const onMessage = (evt) => { - const data = JSON.parse(evt.data); + let data = {}; + try { + data = JSON.parse(evt.data); + } catch (err) { + // eslint-disable-next-line no-console + console.error('WebSocket: unable to parse JSON data', err, evt.data); + } let source = params.channel; source += params.args ? ` ${params.args}` : ''; - if ( - data.source === source && - (!params.id || params.id === data.client_reference) - ) { - cb(data); - } + // Buffer channel message return a list of other channels to propagate to proper callbacks. + const contents = Array.isArray(data.content) ? data.content : [data]; + contents.forEach((content) => { + // Because of backend optimization, the last content is null. + if ( + content?.source === source && + (!params.id || params.id === data.client_reference) + ) { + cb(content); + } + }); }; if (this.websocket) { diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 1bb48b31..20a158bc 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -168,6 +168,7 @@ const TralisLayerMixin = (TrackerLayer) => } this.api.bbox = bbox; + this.api.buffer = [100, 10]; } setMode(mode) { diff --git a/src/common/utils/getVehiclePosition.js b/src/common/utils/getVehiclePosition.js index 5d4e6ee8..5078aa4c 100644 --- a/src/common/utils/getVehiclePosition.js +++ b/src/common/utils/getVehiclePosition.js @@ -34,7 +34,7 @@ const getVehiclePosition = (now, trajectory, noInterpolate) => { // outside the time intervals we display the vehicle at the last (or first) position known. if (now < firstInterval[0]) { // Display first position known. - [[, , rotation]] = firstInterval; + [, , rotation] = firstInterval; coord = geometry.getFirstCoordinate(); } else if (now > lastInterval[0]) { // Display last position known. From bc19f009fcef5e90dcc91f9f4751ec3f91aeaaa0 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 14:36:18 +0200 Subject: [PATCH 092/109] Use travic_v2 everywhere --- src/api/tralis/TralisAPI.js | 2 +- src/common/mixins/TrackerLayerMixin.js | 10 +++++++--- src/common/mixins/TralisLayerMixin.js | 1 - src/doc/examples/ol-mapbox-style-layer.js | 2 +- src/doc/examples/ol-old-tracker.js | 2 +- src/doc/examples/ol-query.js | 2 +- src/doc/examples/ol-routing.js | 2 +- src/doc/examples/ol-stop-finder.js | 2 +- src/doc/examples/ol-tracker.js | 2 +- 9 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index 08bd6bc0..33be4884 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -77,7 +77,7 @@ class TralisAPI { } const { apiKey } = opt; - let { url, projection, bbox, buffer } = opt; + let { url, projection, bbox, buffer = [100, 200] } = opt; const conn = new WebSocketConnector(); if (apiKey) { diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index aaff6859..dfc6bf03 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -687,21 +687,25 @@ const TrackerLayerMixin = (Base) => const roundedZoom = Math.round(zoom); const timeStep = timeSteps[roundedZoom] || 25; const nextTick = Math.max(25, timeStep / this.speed); - + const nextThrottleTick = Math.min(nextTick, 500); // TODO: see if this should go elsewhere. if (this.useThrottle) { this.throttleRenderTrajectories = throttle( this.renderTrajectoriesInternal, - Math.min(nextTick, 500), + nextThrottleTick, { leading: true, trailing: true }, ); } else if (this.useDebounce) { this.debounceRenderTrajectories = debounce( this.renderTrajectoriesInternal, - Math.min(nextTick, 500), + nextThrottleTick, { leading: true, trailing: true, maxWait: 5000 }, ); } + if (this.api?.buffer) { + const [, size] = this.api.buffer; + this.api.buffer = [nextThrottleTick, size]; + } return nextTick; } diff --git a/src/common/mixins/TralisLayerMixin.js b/src/common/mixins/TralisLayerMixin.js index 20a158bc..1bb48b31 100644 --- a/src/common/mixins/TralisLayerMixin.js +++ b/src/common/mixins/TralisLayerMixin.js @@ -168,7 +168,6 @@ const TralisLayerMixin = (TrackerLayer) => } this.api.bbox = bbox; - this.api.buffer = [100, 10]; } setMode(mode) { diff --git a/src/doc/examples/ol-mapbox-style-layer.js b/src/doc/examples/ol-mapbox-style-layer.js index 8240a93e..f6f3803a 100644 --- a/src/doc/examples/ol-mapbox-style-layer.js +++ b/src/doc/examples/ol-mapbox-style-layer.js @@ -12,7 +12,7 @@ export default () => { }); const mapboxLayer = new MapboxLayer({ - url: 'https://maps.geops.io/styles/base_bright_v2/style.json', + url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); diff --git a/src/doc/examples/ol-old-tracker.js b/src/doc/examples/ol-old-tracker.js index 462f2015..a0a33506 100644 --- a/src/doc/examples/ol-old-tracker.js +++ b/src/doc/examples/ol-old-tracker.js @@ -14,7 +14,7 @@ export default () => { }); const layer = new MapboxLayer({ - url: 'https://maps.geops.io/styles/base_bright_v2/style.json', + url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); diff --git a/src/doc/examples/ol-query.js b/src/doc/examples/ol-query.js index 3d4200f3..03ed7a89 100644 --- a/src/doc/examples/ol-query.js +++ b/src/doc/examples/ol-query.js @@ -38,7 +38,7 @@ export default () => { }); const mapboxLayer = new MapboxLayer({ - url: 'https://maps.geops.io/styles/base_bright_v2/style.json', + url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); diff --git a/src/doc/examples/ol-routing.js b/src/doc/examples/ol-routing.js index f342bfda..c492e7dc 100644 --- a/src/doc/examples/ol-routing.js +++ b/src/doc/examples/ol-routing.js @@ -10,7 +10,7 @@ export default () => { }); const mapboxLayer = new MapboxLayer({ - url: `https://maps.geops.io/styles/base_bright_v2/style.json?key=${window.apiKey}`, + url: `https://maps.geops.io/styles/travic_v2/style.json?key=${window.apiKey}`, }); const map = new Map({ diff --git a/src/doc/examples/ol-stop-finder.js b/src/doc/examples/ol-stop-finder.js index 1dc67fba..87b277a8 100644 --- a/src/doc/examples/ol-stop-finder.js +++ b/src/doc/examples/ol-stop-finder.js @@ -8,7 +8,7 @@ export default () => { }); const mapboxLayer = new MapboxLayer({ - url: 'https://maps.geops.io/styles/base_bright_v2/style.json', + url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); diff --git a/src/doc/examples/ol-tracker.js b/src/doc/examples/ol-tracker.js index 0a66e13a..6f0506b3 100644 --- a/src/doc/examples/ol-tracker.js +++ b/src/doc/examples/ol-tracker.js @@ -14,7 +14,7 @@ export default () => { }); const layer = new MapboxLayer({ - url: 'https://maps.geops.io/styles/base_bright_v2/style.json', + url: 'https://maps.geops.io/styles/travic_v2/style.json', apiKey: window.apiKey, }); const tracker = new TralisLayer({ From 0e9f06a74be824103aa502a28fa6c133074c1b62 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 14:37:22 +0200 Subject: [PATCH 093/109] v1.7.8-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63dd78de..6ffcc5f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.3", + "version": "1.7.8-beta.4", "main": "index.js", "module": "module.js", "dependencies": { From 9fe3b695bbadfb80162bcfff206f7d62517d95e5 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 14:38:56 +0200 Subject: [PATCH 094/109] Use 100 as buffer size --- src/api/tralis/TralisAPI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index 33be4884..0a45754f 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -77,7 +77,7 @@ class TralisAPI { } const { apiKey } = opt; - let { url, projection, bbox, buffer = [100, 200] } = opt; + let { url, projection, bbox, buffer = [100, 100] } = opt; const conn = new WebSocketConnector(); if (apiKey) { From 73ca1c7c09e878fd13e6e50629b104731b117281 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 14:39:31 +0200 Subject: [PATCH 095/109] v1.7.8-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ffcc5f0..ee33a6a0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.4", + "version": "1.7.8-beta.5", "main": "index.js", "module": "module.js", "dependencies": { From 1998b7ce34a8185c2dbed94fc8c202c2d2a3bc27 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 15:20:04 +0200 Subject: [PATCH 096/109] Manage buffer as specific case --- src/api/tralis/WebSocketConnector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tralis/WebSocketConnector.js b/src/api/tralis/WebSocketConnector.js index 4d88d5d5..9496485c 100644 --- a/src/api/tralis/WebSocketConnector.js +++ b/src/api/tralis/WebSocketConnector.js @@ -183,7 +183,7 @@ class WebSocketConnector { source += params.args ? ` ${params.args}` : ''; // Buffer channel message return a list of other channels to propagate to proper callbacks. - const contents = Array.isArray(data.content) ? data.content : [data]; + const contents = data.source === 'buffer' ? data.content : [data]; contents.forEach((content) => { // Because of backend optimization, the last content is null. if ( From 26d11f7da46fed7fbaf01263261a67bf1767e73f Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 20 Apr 2022 15:20:38 +0200 Subject: [PATCH 097/109] v1.7.8-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee33a6a0..90968fc3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.5", + "version": "1.7.8-beta.6", "main": "index.js", "module": "module.js", "dependencies": { From a210853a8b9e8e9683febcc9d3960bd7e7afe438 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 21 Apr 2022 09:51:06 +0200 Subject: [PATCH 098/109] Fix clear of tracker --- src/common/mixins/TrackerLayerMixin.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index dfc6bf03..940aa39c 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -440,7 +440,9 @@ const TrackerLayerMixin = (Base) => this.stop(); unByKey(this.visibilityRef); if (this.tracker) { - this.clear(); + const { canvas } = this.tracker; + const context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); this.tracker = null; } super.terminate(); From 1136582c88cbcda7d9c8be34a1b030ae1f6cd548 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 21 Apr 2022 09:53:03 +0200 Subject: [PATCH 099/109] v1.7.8-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90968fc3..6b583bc7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.6", + "version": "1.7.8-beta.7", "main": "index.js", "module": "module.js", "dependencies": { From 5f8e02c7876fd9bad06ac72b3aaa2f56b8d1f166 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 22 Apr 2022 15:08:56 +0200 Subject: [PATCH 100/109] v1.7.8-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afb17664..f7d44bfc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "mobility-toolbox-js", "license": "MIT", "description": "Toolbox for JavaScript applications in the domains of mobility and logistics.", - "version": "1.7.8-beta.7", + "version": "1.7.8-beta.8", "main": "index.js", "module": "module.js", "dependencies": { From cd5b137ee6f68e5d8403fe553a9f3a345c975855 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 6 May 2022 16:35:55 +0200 Subject: [PATCH 101/109] Make example map bigger --- src/doc/App.js | 2 +- src/doc/components/Example.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/App.js b/src/doc/App.js index 6adb1ac1..a6e14f85 100644 --- a/src/doc/App.js +++ b/src/doc/App.js @@ -85,7 +85,7 @@ function App() { + } diff --git a/src/doc/components/Example.js b/src/doc/components/Example.js index c0af3624..22346cfd 100644 --- a/src/doc/components/Example.js +++ b/src/doc/components/Example.js @@ -12,7 +12,7 @@ const useStyles = makeStyles((theme) => ({ padding: 12, }, htmlContainer: { - height: 500, + height: 900, }, noPointer: { // Remove pointer events for mobile devices on load From be3d5eb0530df00d4b78167d2fc7dba249481145 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Fri, 6 May 2022 16:51:27 +0200 Subject: [PATCH 102/109] Remove useless code --- src/api/tralis/TralisAPI.js | 2 - src/common/interpolate.js | 67 ------------- src/common/mixins/TrackerLayerMixin.js | 3 - src/common/trackerStyleConfig.js | 134 ------------------------- src/ol/layers/TrackerLayer.js | 4 +- 5 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 src/common/interpolate.js delete mode 100644 src/common/trackerStyleConfig.js diff --git a/src/api/tralis/TralisAPI.js b/src/api/tralis/TralisAPI.js index 1bda1337..0a45754f 100644 --- a/src/api/tralis/TralisAPI.js +++ b/src/api/tralis/TralisAPI.js @@ -65,8 +65,6 @@ class TralisAPI { /** @ignore */ this.prefix = options.prefix || ''; - this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd || false; - /** @ignore */ this.onOpen = this.onOpen.bind(this); } diff --git a/src/common/interpolate.js b/src/common/interpolate.js deleted file mode 100644 index 9706b586..00000000 --- a/src/common/interpolate.js +++ /dev/null @@ -1,67 +0,0 @@ -import GeomType from 'ol/geom/GeometryType'; - -/** - * Interpolate a position along a geometry at a specific date. - * - * @param {number} now Current date to interpolate a position with. In ms. - * @param {ol/geom/LineString~LineString} geometry The geoemtry used to interpolate a position. - * @param {Array>} timeIntervals The time intervals used to interpolate a position, ex: [[dateInMs, fraction, rotation]...]. - * @returns - */ -const interpolate = (now, geometry, timeIntervals) => { - let coord; - let start; - let end; - let startFrac; - let endFrac; - let timeFrac; - let rotation; - - // Search th time interval. - for (let j = 0; j < timeIntervals.length - 1; j += 1) { - // Rotation only available in tralis layer. - [start, startFrac, rotation] = timeIntervals[j]; - [end, endFrac] = timeIntervals[j + 1]; - - if (start <= now && now <= end) { - break; - } else { - start = null; - end = null; - } - } - // The geometry can also be a Point - if (geometry.getType() === GeomType.POINT) { - coord = geometry.getCoordinates(); - } else if (geometry.getType() === GeomType.LINE_STRING) { - if (start && end) { - // interpolate position inside the time interval. - timeFrac = interpolate ? Math.min((now - start) / (end - start), 1) : 0; - - const geomFrac = interpolate - ? timeFrac * (endFrac - startFrac) + startFrac - : 0; - - coord = geometry.getCoordinateAt(geomFrac); - - // It happens that the now date was some ms before the first timeIntervals we have. - } else if (now < timeIntervals[0][0]) { - [[, , rotation]] = timeIntervals; - timeFrac = 0; - coord = geometry.getFirstCoordinate(); - } else if (now > timeIntervals[timeIntervals.length - 1][0]) { - [, , rotation] = timeIntervals[timeIntervals.length - 1]; - timeFrac = 1; - coord = geometry.getLastCoordinate(); - } - } else { - // eslint-disable-next-line no-console - console.error( - 'This geometry type is not supported. Only Point or LineString are. Current geometry: ', - geometry, - ); - } - return { coord, rotation, timeFrac }; -}; - -export default interpolate; diff --git a/src/common/mixins/TrackerLayerMixin.js b/src/common/mixins/TrackerLayerMixin.js index ba786dc2..940aa39c 100644 --- a/src/common/mixins/TrackerLayerMixin.js +++ b/src/common/mixins/TrackerLayerMixin.js @@ -507,9 +507,6 @@ const TrackerLayerMixin = (Base) => } } - /** - * - /** * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method. * diff --git a/src/common/trackerStyleConfig.js b/src/common/trackerStyleConfig.js deleted file mode 100644 index 1424305a..00000000 --- a/src/common/trackerStyleConfig.js +++ /dev/null @@ -1,134 +0,0 @@ -const cacheDelayBg = {}; - -// Draw circle delay background -export const getDelayBgCanvas = (origin, radius, color) => { - const key = `${origin}, ${radius}, ${color}`; - if (!cacheDelayBg[key]) { - // console.log('cacheDelayBg'); - const canvas = new OffscreenCanvas(origin * 2, origin * 2); - const ctx = canvas.getContext('2d'); - ctx.beginPath(); - ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); - ctx.fillStyle = color; - ctx.filter = 'blur(1px)'; - ctx.fill(); - cacheDelayBg[key] = canvas; - } - return cacheDelayBg[key]; -}; - -// Draw delay text -const cacheDelayText = {}; -export const getDelayTextCanvas = ( - width, - text, - fontSize, - font, - delayColor, - delayOutlineColor, - pixelRatio, -) => { - const key = `${width}, ${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`; - if (!cacheDelayText[key]) { - const canvas = new OffscreenCanvas(width, fontSize + 8); - const ctx = canvas.getContext('2d'); - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - ctx.font = font; - ctx.fillStyle = delayColor; - ctx.strokeStyle = delayOutlineColor; - ctx.lineWidth = 1.5 * pixelRatio; - const delayText = text; - ctx.strokeText(delayText, 0, fontSize); - ctx.fillText(delayText, 0, fontSize); - cacheDelayText[key] = canvas; - } - return cacheDelayText[key]; -}; - -// Draw colored circle with black border -const cacheCircle = {}; -export const getCircleCanvas = ( - origin, - radius, - color, - hasStroke, - hasDash, - pixelRatio, -) => { - const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`; - if (!cacheCircle[key]) { - // console.log('cacheDelayBg'); - const canvas = new OffscreenCanvas(origin * 2, origin * 2); - const ctx = canvas.getContext('2d'); - ctx.fillStyle = color; - - if (hasStroke) { - ctx.lineWidth = 1 * pixelRatio; - ctx.strokeStyle = '#000000'; - } - - ctx.beginPath(); - ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false); - ctx.fill(); - - if (hasDash) { - ctx.setLineDash([5, 3]); - } - - if (hasStroke) { - ctx.stroke(); - } - - cacheCircle[key] = canvas; - } - return cacheCircle[key]; -}; - -// Draw text in the circle -const cacheText = {}; -export const getTextCanvas = ( - text, - origin, - textSize, - fillColor, - strokeColor, - hasStroke, - pixelRatio, -) => { - const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`; - if (!cacheText[key]) { - const canvas = new OffscreenCanvas(origin * 2, origin * 2); - const ctx = canvas.getContext('2d'); - - // Draw a stroke to the text only if a provider provides realtime but we don't use it. - if (hasStroke) { - ctx.save(); - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.font = `bold ${textSize + 2}px Arial`; - ctx.strokeStyle = strokeColor; - ctx.strokeText(text, origin, origin); - ctx.restore(); - } - - // Draw a text - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.fillStyle = fillColor; - ctx.font = `bold ${textSize}px Arial`; - ctx.strokeStyle = strokeColor; - ctx.strokeText(text, origin, origin); - ctx.fillText(text, origin, origin); - - cacheText[key] = canvas; - } - return cacheText[key]; -}; - -export default { - getDelayBgCanvas, - getDelayTextCanvas, - getCircleCanvas, - getTextCanvas, -}; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js index ae0c4679..8b8cc9d3 100644 --- a/src/ol/layers/TrackerLayer.js +++ b/src/ol/layers/TrackerLayer.js @@ -30,8 +30,6 @@ const updateContainerTransform = (layer) => { rotation - renderedRotation, 0, 0, - layer.canvas.width, - layer.canvas.height, ); } }; @@ -66,6 +64,7 @@ class TrackerLayer extends mixin(Layer) { // Worker messaging and actions this.worker.onmessage = (message) => { if (message.data.action === 'requestRender') { + console.log('icci'); // Worker requested a new render frame that.map.render(); } else if (that.canvas && message.data.action === 'rendered') { @@ -94,7 +93,6 @@ class TrackerLayer extends mixin(Layer) { that.canvas.style.transform = ``; that.canvas.style.width = `${that.canvas.width / that.pixelRatio}px`; that.canvas.style.height = `${that.canvas.height / that.pixelRatio}px`; - // this.canvas.style.transform = message.data.transform; that.renderedViewState = message.data.frameState.viewState; updateContainerTransform(that); that.canvas.getContext('2d').drawImage(imageData, 0, 0); From b513e31a61ed3218c3ffb96b8dec059f54e5be2f Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 16 Nov 2022 11:48:16 +0100 Subject: [PATCH 103/109] chore: ake offscreen canvas work --- doc/public/static/examples/ol-tracker.js | 1 + doc/src/components/Esdoc/index.json | 2491 +++++++--------------- package.json | 2 +- src/common/mixins/RealtimeLayerMixin.ts | 11 +- src/common/styles/realtimeSimpleStyle.ts | 11 +- src/common/tracker.worker.ts | 64 +- src/common/tracker2.worker.js | 95 - src/common/utils/createCanvas.ts | 2 +- src/common/utils/getVehiclePosition.ts | 19 +- src/common/utils/realtimeConfig.ts | 12 +- src/ol/layers/RealtimeLayer.ts | 25 +- src/types/common.d.ts | 10 +- tsconfig.json | 5 +- 13 files changed, 840 insertions(+), 1908 deletions(-) delete mode 100644 src/common/tracker2.worker.js diff --git a/doc/public/static/examples/ol-tracker.js b/doc/public/static/examples/ol-tracker.js index 2d31b11d..6fc680b4 100644 --- a/doc/public/static/examples/ol-tracker.js +++ b/doc/public/static/examples/ol-tracker.js @@ -29,6 +29,7 @@ export default () => { const tracker = new RealtimeLayer({ url: 'wss://api.geops.io/tracker-ws/v1/', apiKey: window.apiKey, + // allowRenderWhenAnimating: true, }); tracker.attachToMap(map); diff --git a/doc/src/components/Esdoc/index.json b/doc/src/components/Esdoc/index.json index 867406a9..d392e2c4 100644 --- a/doc/src/components/Esdoc/index.json +++ b/doc/src/components/Esdoc/index.json @@ -2311,7 +2311,7 @@ "__docId__": 118, "kind": "file", "name": "build/api/RoutingAPI.js", - "content": "import HttpAPI from '../common/api/HttpAPI';\n/**\n * Access to the [geOps Routing service](https://developer.geops.io/apis/routing).\n *\n * @example\n * import { RoutingAPI } from 'mobility-toolbox-js';\n *\n * const api = new RoutingAPI({\n * apiKey: [yourApiKey]\n * });\n *\n */\nclass RoutingAPI extends HttpAPI {\n /**\n * Constructor\n *\n * @param {RoutingAPIOptions} options Options.\n * @param {string} [options.url='https://api.geops.io/routing/v1/'] Service url.\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).\n */\n constructor(options = {}) {\n super(Object.assign({ url: 'https://api.geops.io/routing/v1/' }, options));\n }\n /**\n * Route.\n *\n * @param {RoutingParameters} params Request parameters. See [Routing service documentation](https://developer.geops.io/apis/routing/).\n * @param {RequestInit} config Options for the fetch request.\n * @return {Promise} An GeoJSON feature collection with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n route(params, config) {\n return this.fetch('', params, config);\n }\n}\nexport default RoutingAPI;\n", + "content": "import HttpAPI from '../common/api/HttpAPI';\n/**\n * Access to the [geOps Routing service](https://developer.geops.io/apis/routing).\n *\n * @example\n * import { RoutingAPI } from 'mobility-toolbox-js';\n *\n * const api = new RoutingAPI({\n * apiKey: [yourApiKey]\n * });\n *\n */\nclass RoutingAPI extends HttpAPI {\n /**\n * Constructor\n *\n * @param {RoutingAPIOptions} options Options.\n * @param {string} [options.url='https://api.geops.io/routing/v1/'] Service url.\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).\n */\n constructor(options = {}) {\n super({ url: 'https://api.geops.io/routing/v1/', ...options });\n }\n /**\n * Route.\n *\n * @param {RoutingParameters} params Request parameters. See [Routing service documentation](https://developer.geops.io/apis/routing/).\n * @param {RequestInit} config Options for the fetch request.\n * @return {Promise} An GeoJSON feature collection with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n route(params, config) {\n return this.fetch('', params, config);\n }\n}\nexport default RoutingAPI;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/api/RoutingAPI.js", "access": "public", @@ -2433,7 +2433,7 @@ "__docId__": 122, "kind": "file", "name": "build/api/StopsAPI.js", - "content": "import HttpAPI from '../common/api/HttpAPI';\n/**\n * Access to the [Stops service](https://developer.geops.io/apis/5dcbd702a256d90001cf1361/).\n *\n * @example\n * import { StopsAPI } from 'mobility-toolbox-js/api';\n *\n * const api = new StopsAPI({\n * url: 'https://api.geops.io/stops/v1/',\n * apiKey: [yourApiKey]\n * });\n *\n */\nclass StopsAPI extends HttpAPI {\n /**\n * Constructor\n *\n * @param {StopsAPIOptions} options Options.\n * @param {string} [options.url='https://api.geops.io/stops/v1/'] Service url.\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).\n */\n constructor(options = {}) {\n super(Object.assign({ url: 'https://api.geops.io/stops/v1/' }, options));\n }\n /**\n * Search fo stops.\n *\n * @param {StopsParameters} params Request parameters. See [Stops service documentation](https://developer.geops.io/apis/stops).\n * @param {RequestInit} config Options for the fetch request.\n * @return {Promise} An GeoJSON feature collection with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n search(params, config) {\n return this.fetch('', params, config);\n }\n}\nexport default StopsAPI;\n", + "content": "import HttpAPI from '../common/api/HttpAPI';\n/**\n * Access to the [Stops service](https://developer.geops.io/apis/5dcbd702a256d90001cf1361/).\n *\n * @example\n * import { StopsAPI } from 'mobility-toolbox-js/api';\n *\n * const api = new StopsAPI({\n * url: 'https://api.geops.io/stops/v1/',\n * apiKey: [yourApiKey]\n * });\n *\n */\nclass StopsAPI extends HttpAPI {\n /**\n * Constructor\n *\n * @param {StopsAPIOptions} options Options.\n * @param {string} [options.url='https://api.geops.io/stops/v1/'] Service url.\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).\n */\n constructor(options = {}) {\n super({ url: 'https://api.geops.io/stops/v1/', ...options });\n }\n /**\n * Search fo stops.\n *\n * @param {StopsParameters} params Request parameters. See [Stops service documentation](https://developer.geops.io/apis/stops).\n * @param {RequestInit} config Options for the fetch request.\n * @return {Promise} An GeoJSON feature collection with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n search(params, config) {\n return this.fetch('', params, config);\n }\n}\nexport default StopsAPI;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/api/StopsAPI.js", "access": "public", @@ -3227,7 +3227,7 @@ "__docId__": 141, "kind": "file", "name": "build/common/api/HttpAPI.js", - "content": "import BaseObject from 'ol/Object';\nimport getUrlWithParams from '../utils/getUrlWithParams';\n/**\n * Common class to access to a geOps api using http.\n *\n * @example\n * import { API } from 'mobility-toolbox-js/api';\n *\n * const api = new HttpApi({\n * url: [yourUrl],\n * apiKey: [yourApiKey]\n * });\n *\n * @classproperty {string} url Url of the service.\n * @classproperty {string} apiKey Api key to access the service.\n */\nclass HttpAPI extends BaseObject {\n constructor(options) {\n super();\n /** @ignore */\n this.url = options.url;\n /** @ignore */\n this.apiKey = options.apiKey;\n }\n /**\n * Append the apiKey before sending the request.\n * @ignore\n */\n fetch(path, params, config) {\n if (!this.url) {\n // eslint-disable-next-line no-console\n return Promise.reject(new Error(`No url defined for request to ${this.url}/${path}`));\n }\n if (!this.url && !this.apiKey && !/key=/.test(this.url)) {\n // eslint-disable-next-line no-console\n return Promise.reject(new Error(`No apiKey defined for request to ${this.url}`));\n }\n // Clean requets parameters, removing undefined and null values.\n const searchParams = params || {};\n const url = getUrlWithParams(`${this.url}${path || ''}`, Object.assign({ key: this.apiKey }, searchParams));\n // We use toString because of TYpeScript bug that only accept a string in fetch method.\n return fetch(url.toString(), config).then((response) => {\n try {\n return response.json().then((data) => {\n if (data.error) {\n throw new Error(data.error);\n }\n return data;\n });\n }\n catch (err) {\n return Promise.reject(new Error(err));\n }\n });\n }\n}\nexport default HttpAPI;\n", + "content": "import BaseObject from 'ol/Object';\nimport getUrlWithParams from '../utils/getUrlWithParams';\n/**\n * Common class to access to a geOps api using http.\n *\n * @example\n * import { API } from 'mobility-toolbox-js/api';\n *\n * const api = new HttpApi({\n * url: [yourUrl],\n * apiKey: [yourApiKey]\n * });\n *\n * @classproperty {string} url Url of the service.\n * @classproperty {string} apiKey Api key to access the service.\n */\nclass HttpAPI extends BaseObject {\n constructor(options) {\n super();\n /** @ignore */\n this.url = options.url;\n /** @ignore */\n this.apiKey = options.apiKey;\n }\n /**\n * Append the apiKey before sending the request.\n * @ignore\n */\n fetch(path, params, config) {\n if (!this.url) {\n // eslint-disable-next-line no-console\n return Promise.reject(new Error(`No url defined for request to ${this.url}/${path}`));\n }\n if (!this.url && !this.apiKey && !/key=/.test(this.url)) {\n // eslint-disable-next-line no-console\n return Promise.reject(new Error(`No apiKey defined for request to ${this.url}`));\n }\n // Clean requets parameters, removing undefined and null values.\n const searchParams = params || {};\n const url = getUrlWithParams(`${this.url}${path || ''}`, {\n key: this.apiKey,\n ...searchParams,\n });\n // We use toString because of TYpeScript bug that only accept a string in fetch method.\n return fetch(url.toString(), config).then((response) => {\n try {\n return response.json().then((data) => {\n if (data.error) {\n throw new Error(data.error);\n }\n return data;\n });\n }\n catch (err) {\n return Promise.reject(new Error(err));\n }\n });\n }\n}\nexport default HttpAPI;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/api/HttpAPI.js", "access": "public", @@ -3356,7 +3356,7 @@ "__docId__": 147, "kind": "file", "name": "build/common/api/WebSocketAPI.js", - "content": "/**\n * Class used to facilitate connection to a WebSocketAPI and\n * also to manage properly messages send to the WebSocketAPI.\n * This class must not contain any specific implementation.\n */\nclass WebSocketAPI {\n constructor() {\n this.defineProperties();\n }\n defineProperties() {\n Object.defineProperties(this, {\n closed: {\n get: () => !!(!this.websocket ||\n this.websocket.readyState === this.websocket.CLOSED),\n },\n closing: {\n get: () => !!(this.websocket &&\n this.websocket.readyState === this.websocket.CLOSING),\n },\n connecting: {\n get: () => !!(this.websocket &&\n this.websocket.readyState === this.websocket.CONNECTING),\n },\n open: {\n get: () => !!(this.websocket && this.websocket.readyState === this.websocket.OPEN),\n },\n /**\n * Array of message to send on open.\n * @type {Array}\n * @private\n */\n messagesOnOpen: {\n value: [],\n writable: true,\n },\n /**\n * Array of subscriptions.\n * @type {Array}\n * @private\n */\n subscriptions: {\n value: [],\n writable: true,\n },\n /**\n * List of channels subscribed.\n * @type {WebSocketSubscribed}\n * @private\n */\n subscribed: {\n value: {},\n writable: true,\n },\n });\n }\n /**\n * Get the websocket request string.\n *\n * @param {string} method Request mehtod {GET, SUB}.\n * @param {WebSocketParameters} params Request parameters.\n * @param {string} params.channel Channel name\n * @param {string} [params.args] Request arguments\n * @param {Number|string} [params.id] Request identifier\n * @return {string} request string\n * @private\n */\n static getRequestString(method, params = {}) {\n let reqStr = `${method} ${params.channel}`;\n reqStr += params.args ? ` ${params.args}` : '';\n reqStr += params.id ? ` ${params.id}` : '';\n return reqStr.trim();\n }\n /**\n * (Re)connect the websocket.\n *\n * @param {string} url Websocket url.\n * @param {function} onOpen Callback called when the websocket connection is opened and before subscriptions of previous subscriptions.\n * @private\n */\n connect(url, onOpen = () => { }) {\n if (this.websocket && !this.closed) {\n if (!this.closing && this.websocket.url !== url) {\n this.websocket.close();\n }\n else if (this.connecting) {\n return;\n }\n }\n /** @ignore */\n this.websocket = new WebSocket(url);\n if (!this.open) {\n this.websocket.addEventListener('open', () => {\n onOpen();\n this.subscribePreviousSubscriptions();\n });\n }\n else {\n onOpen();\n this.subscribePreviousSubscriptions();\n }\n }\n /**\n * Close the websocket definitively.\n *\n * @private\n */\n close() {\n if (this.websocket && (this.open || this.connecting)) {\n this.websocket.onclose = () => {\n // We set the ws to undefined here to wait that the ws is properly closed.\n this.websocket = undefined;\n };\n this.websocket.close();\n this.messagesOnOpen = [];\n }\n }\n /**\n * Sends a message to the websocket.\n *\n * @param {message} message Message to send.\n * @private\n */\n send(message) {\n if (!this.websocket) {\n return;\n }\n const send = () => {\n var _a;\n (_a = this.websocket) === null || _a === void 0 ? void 0 : _a.send(message);\n };\n if (!this.open) {\n // This 'if' avoid sending 2 identical BBOX message on open,\n if (!this.messagesOnOpen.includes(message)) {\n this.messagesOnOpen.push(message);\n this.websocket.addEventListener('open', () => {\n this.messagesOnOpen = [];\n send();\n });\n this.websocket.addEventListener('close', () => {\n this.messagesOnOpen = [];\n });\n }\n }\n else if (!this.messagesOnOpen.includes(message)) {\n send();\n }\n }\n addEvents(onMessage, onError) {\n if (this.websocket) {\n this.websocket.addEventListener('message', onMessage);\n if (onError) {\n this.websocket.addEventListener('error', onError);\n this.websocket.addEventListener('close', onError);\n }\n }\n }\n removeEvents(onMessage, onError) {\n if (this.websocket) {\n this.websocket.removeEventListener('message', onMessage);\n if (onError) {\n this.websocket.removeEventListener('error', onError);\n this.websocket.removeEventListener('close', onError);\n }\n }\n }\n /**\n * Listen to websocket messages.\n *\n * @param {WebSocketParameters} params Parameters for the websocket get request\n * @param {function} cb callback on listen\n * @param {function} errorCb Callback on error\n * @return {{onMessage: function, errorCb: function}} Object with onMessage and error callbacks\n * @private\n */\n listen(params, cb, errorCb) {\n // Remove the previous identical callback\n this.unlisten(params, cb);\n // We wrap the message callback to be sure we only propagate the message if it is for the right channel.\n const onMessage = (evt) => {\n let data;\n try {\n data = JSON.parse(evt.data);\n }\n catch (err) {\n // eslint-disable-next-line no-console\n console.error('WebSocket: unable to parse JSON data', err, evt.data);\n return;\n }\n let source = params.channel;\n source += params.args ? ` ${params.args}` : '';\n // Buffer channel message return a list of other channels to propagate to proper callbacks.\n let contents;\n if (data.source === 'buffer') {\n contents = data\n .content;\n }\n else {\n contents = [data];\n }\n contents.forEach((content) => {\n // Because of backend optimization, the last content is null.\n if ((content === null || content === void 0 ? void 0 : content.source) === source &&\n (!params.id || params.id === data.client_reference)) {\n cb(content);\n }\n });\n };\n this.addEvents(onMessage, errorCb);\n return { onMessageCb: onMessage, onErrorCb: errorCb };\n }\n /**\n * Unlisten websocket messages.\n *\n * @param {Object} params Parameters for the websocket get request.\n * @param {function} cb Callback used when listen.\n * @private\n */\n unlisten(params, cb) {\n [...(this.subscriptions || []), ...(this.requests || [])]\n .filter((s) => s.params.channel === params.channel && (!cb || s.cb === cb))\n .forEach(({ onMessageCb, onErrorCb }) => {\n this.removeEvents(onMessageCb, onErrorCb);\n });\n }\n /**\n * Sends a get request to the websocket.\n * The callback is called only once, when the response is received or when the call returns an error.\n *\n * @param {Object} params Parameters for the websocket get request\n * @param {function} onMessage callback on message event\n * @param {function} onError Callback on error and close event\n * @private\n */\n get(params, cb, errorCb) {\n const requestString = WebSocketAPI.getRequestString('GET', params);\n this.send(requestString);\n // We wrap the callbacks to make sure they are called only once.\n const once = (callback) => \n // @ts-ignore: Spread error\n (...args) => {\n // @ts-ignore: Spread error\n callback(...args);\n const index = this.requests.findIndex((request) => requestString === request.requestString && cb === request.cb);\n const { onMessageCb, onErrorCb } = this.requests[index];\n this.removeEvents(onMessageCb, onErrorCb);\n this.requests.splice(index, 1);\n };\n const { onMessageCb, onErrorCb } = this.listen(params, once(cb), errorCb && once(errorCb));\n // Store requests and callbacks to be able to remove them.\n if (!this.requests) {\n this.requests = [];\n }\n const index = this.requests.findIndex((request) => requestString === request.requestString && cb === request.cb);\n const newReq = {\n params,\n requestString,\n cb,\n errorCb,\n onMessageCb,\n onErrorCb,\n };\n if (index > -1) {\n this.requests[index] = newReq;\n }\n else {\n this.requests.push(newReq);\n }\n }\n /**\n * Subscribe to a given channel.\n *\n * @param {Object} params Parameters for the websocket get request\n * @param {function} cb callback on listen\n * @param {function} errorCb Callback on error\n * @param {boolean} quiet if false, no GET or SUB requests are send, only the callback is registered.\n * @private\n */\n subscribe(params, cb, errorCb, quiet = false) {\n const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb);\n const reqStr = WebSocketAPI.getRequestString('', params);\n const index = this.subscriptions.findIndex((subcr) => params.channel === subcr.params.channel && cb === subcr.cb);\n const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet };\n if (index > -1) {\n this.subscriptions[index] = newSubscr;\n }\n else {\n this.subscriptions.push(newSubscr);\n }\n if (!this.subscribed[reqStr]) {\n if (!newSubscr.quiet) {\n this.send(`GET ${reqStr}`);\n this.send(`SUB ${reqStr}`);\n }\n this.subscribed[reqStr] = true;\n }\n }\n /**\n * Unsubscribe from a channel.\n * @param {string} source source to unsubscribe from\n * @param {function} cb Callback function to unsubscribe. If null all subscriptions for the channel will be unsubscribed.\n * @private\n */\n unsubscribe(source, cb) {\n const toRemove = this.subscriptions.filter((s) => s.params.channel === source && (!cb || s.cb === cb));\n toRemove.forEach(({ onMessageCb, onErrorCb }) => {\n this.removeEvents(onMessageCb, onErrorCb);\n });\n this.subscriptions = this.subscriptions.filter((s) => s.params.channel !== source || (cb && s.cb !== cb));\n // If there is no more subscriptions to this channel, and the removed subscriptions didn't register quietly,\n // we DEL it.\n if (source &&\n this.subscribed[source] &&\n !this.subscriptions.find((s) => s.params.channel === source) &&\n toRemove.find((subscr) => !subscr.quiet)) {\n this.send(`DEL ${source}`);\n this.subscribed[source] = false;\n }\n }\n /**\n * After an auto reconnection we need to re-subscribe to the channels.\n */\n subscribePreviousSubscriptions() {\n // Before to subscribe previous subscriptions we make sure they\n // are all defined as unsubscribed, because this code is asynchrone\n // and a subscription could have been added in between.\n Object.keys(this.subscribed).forEach((key) => {\n this.subscribed[key] = false;\n });\n // Subscribe all previous subscriptions.\n [...this.subscriptions].forEach((s) => {\n this.subscribe(s.params, s.cb, s.errorCb, s.quiet);\n });\n }\n}\nexport default WebSocketAPI;\n", + "content": "/**\n * Class used to facilitate connection to a WebSocketAPI and\n * also to manage properly messages send to the WebSocketAPI.\n * This class must not contain any specific implementation.\n */\nclass WebSocketAPI {\n constructor() {\n this.defineProperties();\n }\n defineProperties() {\n Object.defineProperties(this, {\n closed: {\n get: () => !!(!this.websocket ||\n this.websocket.readyState === this.websocket.CLOSED),\n },\n closing: {\n get: () => !!(this.websocket &&\n this.websocket.readyState === this.websocket.CLOSING),\n },\n connecting: {\n get: () => !!(this.websocket &&\n this.websocket.readyState === this.websocket.CONNECTING),\n },\n open: {\n get: () => !!(this.websocket && this.websocket.readyState === this.websocket.OPEN),\n },\n /**\n * Array of message to send on open.\n * @type {Array}\n * @private\n */\n messagesOnOpen: {\n value: [],\n writable: true,\n },\n /**\n * Array of subscriptions.\n * @type {Array}\n * @private\n */\n subscriptions: {\n value: [],\n writable: true,\n },\n /**\n * List of channels subscribed.\n * @type {WebSocketSubscribed}\n * @private\n */\n subscribed: {\n value: {},\n writable: true,\n },\n });\n }\n /**\n * Get the websocket request string.\n *\n * @param {string} method Request mehtod {GET, SUB}.\n * @param {WebSocketParameters} params Request parameters.\n * @param {string} params.channel Channel name\n * @param {string} [params.args] Request arguments\n * @param {Number|string} [params.id] Request identifier\n * @return {string} request string\n * @private\n */\n static getRequestString(method, params = {}) {\n let reqStr = `${method} ${params.channel}`;\n reqStr += params.args ? ` ${params.args}` : '';\n reqStr += params.id ? ` ${params.id}` : '';\n return reqStr.trim();\n }\n /**\n * (Re)connect the websocket.\n *\n * @param {string} url Websocket url.\n * @param {function} onOpen Callback called when the websocket connection is opened and before subscriptions of previous subscriptions.\n * @private\n */\n connect(url, onOpen = () => { }) {\n if (this.websocket && !this.closed) {\n if (!this.closing && this.websocket.url !== url) {\n this.websocket.close();\n }\n else if (this.connecting) {\n return;\n }\n }\n /** @ignore */\n this.websocket = new WebSocket(url);\n if (!this.open) {\n this.websocket.addEventListener('open', () => {\n onOpen();\n this.subscribePreviousSubscriptions();\n });\n }\n else {\n onOpen();\n this.subscribePreviousSubscriptions();\n }\n }\n /**\n * Close the websocket definitively.\n *\n * @private\n */\n close() {\n if (this.websocket && (this.open || this.connecting)) {\n this.websocket.onclose = () => { };\n this.websocket.close();\n this.messagesOnOpen = [];\n }\n }\n /**\n * Sends a message to the websocket.\n *\n * @param {message} message Message to send.\n * @private\n */\n send(message) {\n if (!this.websocket) {\n return;\n }\n const send = () => {\n this.websocket?.send(message);\n };\n if (!this.open) {\n // This 'if' avoid sending 2 identical BBOX message on open,\n if (!this.messagesOnOpen.includes(message)) {\n this.messagesOnOpen.push(message);\n this.websocket.addEventListener('open', () => {\n this.messagesOnOpen = [];\n send();\n });\n this.websocket.addEventListener('close', () => {\n this.messagesOnOpen = [];\n });\n }\n }\n else if (!this.messagesOnOpen.includes(message)) {\n send();\n }\n }\n addEvents(onMessage, onError) {\n if (this.websocket) {\n this.websocket.addEventListener('message', onMessage);\n if (onError) {\n this.websocket.addEventListener('error', onError);\n this.websocket.addEventListener('close', onError);\n }\n }\n }\n removeEvents(onMessage, onError) {\n if (this.websocket) {\n this.websocket.removeEventListener('message', onMessage);\n if (onError) {\n this.websocket.removeEventListener('error', onError);\n this.websocket.removeEventListener('close', onError);\n }\n }\n }\n /**\n * Listen to websocket messages.\n *\n * @param {WebSocketParameters} params Parameters for the websocket get request\n * @param {function} cb callback on listen\n * @param {function} errorCb Callback on error\n * @return {{onMessage: function, errorCb: function}} Object with onMessage and error callbacks\n * @private\n */\n listen(params, cb, errorCb) {\n // Remove the previous identical callback\n this.unlisten(params, cb);\n // We wrap the message callback to be sure we only propagate the message if it is for the right channel.\n const onMessage = (evt) => {\n let data;\n try {\n data = JSON.parse(evt.data);\n }\n catch (err) {\n // eslint-disable-next-line no-console\n console.error('WebSocket: unable to parse JSON data', err, evt.data);\n return;\n }\n let source = params.channel;\n source += params.args ? ` ${params.args}` : '';\n // Buffer channel message return a list of other channels to propagate to proper callbacks.\n let contents;\n if (data.source === 'buffer') {\n contents = data\n .content;\n }\n else {\n contents = [data];\n }\n contents.forEach((content) => {\n // Because of backend optimization, the last content is null.\n if (content?.source === source &&\n (!params.id || params.id === data.client_reference)) {\n cb(content);\n }\n });\n };\n this.addEvents(onMessage, errorCb);\n return { onMessageCb: onMessage, onErrorCb: errorCb };\n }\n /**\n * Unlisten websocket messages.\n *\n * @param {Object} params Parameters for the websocket get request.\n * @param {function} cb Callback used when listen.\n * @private\n */\n unlisten(params, cb) {\n [...(this.subscriptions || []), ...(this.requests || [])]\n .filter((s) => s.params.channel === params.channel && (!cb || s.cb === cb))\n .forEach(({ onMessageCb, onErrorCb }) => {\n this.removeEvents(onMessageCb, onErrorCb);\n });\n }\n /**\n * Sends a get request to the websocket.\n * The callback is called only once, when the response is received or when the call returns an error.\n *\n * @param {Object} params Parameters for the websocket get request\n * @param {function} onMessage callback on message event\n * @param {function} onError Callback on error and close event\n * @private\n */\n get(params, cb, errorCb) {\n const requestString = WebSocketAPI.getRequestString('GET', params);\n this.send(requestString);\n // We wrap the callbacks to make sure they are called only once.\n const once = (callback) => \n // @ts-ignore: Spread error\n (...args) => {\n // @ts-ignore: Spread error\n callback(...args);\n const index = this.requests.findIndex((request) => requestString === request.requestString && cb === request.cb);\n const { onMessageCb, onErrorCb } = this.requests[index];\n this.removeEvents(onMessageCb, onErrorCb);\n this.requests.splice(index, 1);\n };\n const { onMessageCb, onErrorCb } = this.listen(params, once(cb), errorCb && once(errorCb));\n // Store requests and callbacks to be able to remove them.\n if (!this.requests) {\n this.requests = [];\n }\n const index = this.requests.findIndex((request) => requestString === request.requestString && cb === request.cb);\n const newReq = {\n params,\n requestString,\n cb,\n errorCb,\n onMessageCb,\n onErrorCb,\n };\n if (index > -1) {\n this.requests[index] = newReq;\n }\n else {\n this.requests.push(newReq);\n }\n }\n /**\n * Subscribe to a given channel.\n *\n * @param {Object} params Parameters for the websocket get request\n * @param {function} cb callback on listen\n * @param {function} errorCb Callback on error\n * @param {boolean} quiet if false, no GET or SUB requests are send, only the callback is registered.\n * @private\n */\n subscribe(params, cb, errorCb, quiet = false) {\n const { onMessageCb, onErrorCb } = this.listen(params, cb, errorCb);\n const reqStr = WebSocketAPI.getRequestString('', params);\n const index = this.subscriptions.findIndex((subcr) => params.channel === subcr.params.channel && cb === subcr.cb);\n const newSubscr = { params, cb, errorCb, onMessageCb, onErrorCb, quiet };\n if (index > -1) {\n this.subscriptions[index] = newSubscr;\n }\n else {\n this.subscriptions.push(newSubscr);\n }\n if (!this.subscribed[reqStr]) {\n if (!newSubscr.quiet) {\n this.send(`GET ${reqStr}`);\n this.send(`SUB ${reqStr}`);\n }\n this.subscribed[reqStr] = true;\n }\n }\n /**\n * Unsubscribe from a channel.\n * @param {string} source source to unsubscribe from\n * @param {function} cb Callback function to unsubscribe. If null all subscriptions for the channel will be unsubscribed.\n * @private\n */\n unsubscribe(source, cb) {\n const toRemove = this.subscriptions.filter((s) => s.params.channel === source && (!cb || s.cb === cb));\n toRemove.forEach(({ onMessageCb, onErrorCb }) => {\n this.removeEvents(onMessageCb, onErrorCb);\n });\n this.subscriptions = this.subscriptions.filter((s) => s.params.channel !== source || (cb && s.cb !== cb));\n // If there is no more subscriptions to this channel, and the removed subscriptions didn't register quietly,\n // we DEL it.\n if (source &&\n this.subscribed[source] &&\n !this.subscriptions.find((s) => s.params.channel === source) &&\n toRemove.find((subscr) => !subscr.quiet)) {\n this.send(`DEL ${source}`);\n this.subscribed[source] = false;\n }\n }\n /**\n * After an auto reconnection we need to re-subscribe to the channels.\n */\n subscribePreviousSubscriptions() {\n // Before to subscribe previous subscriptions we make sure they\n // are all defined as unsubscribed, because this code is asynchrone\n // and a subscription could have been added in between.\n Object.keys(this.subscribed).forEach((key) => {\n this.subscribed[key] = false;\n });\n // Subscribe all previous subscriptions.\n [...this.subscriptions].forEach((s) => {\n this.subscribe(s.params, s.cb, s.errorCb, s.quiet);\n });\n }\n}\nexport default WebSocketAPI;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/api/WebSocketAPI.js", "access": "public", @@ -3551,7 +3551,7 @@ "return": null }, { - "__docId__": 156, + "__docId__": 155, "kind": "member", "name": "messagesOnOpen", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3559,7 +3559,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#messagesOnOpen", "access": "public", "description": null, - "lineNumber": 114, + "lineNumber": 111, "undocument": true, "type": { "types": [ @@ -3568,7 +3568,7 @@ } }, { - "__docId__": 157, + "__docId__": 156, "kind": "method", "name": "send", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3578,7 +3578,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#send", "access": "private", "description": "Sends a message to the websocket.", - "lineNumber": 123, + "lineNumber": 120, "params": [ { "nullable": null, @@ -3594,7 +3594,7 @@ "return": null }, { - "__docId__": 160, + "__docId__": 159, "kind": "method", "name": "addEvents", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3604,7 +3604,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#addEvents", "access": "public", "description": null, - "lineNumber": 148, + "lineNumber": 144, "undocument": true, "params": [ { @@ -3623,7 +3623,7 @@ "return": null }, { - "__docId__": 161, + "__docId__": 160, "kind": "method", "name": "removeEvents", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3633,7 +3633,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#removeEvents", "access": "public", "description": null, - "lineNumber": 157, + "lineNumber": 153, "undocument": true, "params": [ { @@ -3652,7 +3652,7 @@ "return": null }, { - "__docId__": 162, + "__docId__": 161, "kind": "method", "name": "listen", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3662,7 +3662,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#listen", "access": "private", "description": "Listen to websocket messages.", - "lineNumber": 175, + "lineNumber": 171, "params": [ { "nullable": null, @@ -3705,7 +3705,7 @@ } }, { - "__docId__": 163, + "__docId__": 162, "kind": "method", "name": "unlisten", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3715,7 +3715,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#unlisten", "access": "private", "description": "Unlisten websocket messages.", - "lineNumber": 218, + "lineNumber": 214, "params": [ { "nullable": null, @@ -3741,7 +3741,7 @@ "return": null }, { - "__docId__": 164, + "__docId__": 163, "kind": "method", "name": "get", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3751,7 +3751,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#get", "access": "private", "description": "Sends a get request to the websocket.\nThe callback is called only once, when the response is received or when the call returns an error.", - "lineNumber": 234, + "lineNumber": 230, "params": [ { "nullable": null, @@ -3787,7 +3787,7 @@ "return": null }, { - "__docId__": 165, + "__docId__": 164, "kind": "member", "name": "requests", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3795,7 +3795,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#requests", "access": "public", "description": null, - "lineNumber": 251, + "lineNumber": 247, "undocument": true, "type": { "types": [ @@ -3804,7 +3804,7 @@ } }, { - "__docId__": 166, + "__docId__": 165, "kind": "method", "name": "subscribe", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3814,7 +3814,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#subscribe", "access": "private", "description": "Subscribe to a given channel.", - "lineNumber": 278, + "lineNumber": 274, "params": [ { "nullable": null, @@ -3860,7 +3860,7 @@ "return": null }, { - "__docId__": 167, + "__docId__": 166, "kind": "method", "name": "unsubscribe", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3870,7 +3870,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#unsubscribe", "access": "private", "description": "Unsubscribe from a channel.", - "lineNumber": 303, + "lineNumber": 299, "params": [ { "nullable": null, @@ -3896,7 +3896,7 @@ "return": null }, { - "__docId__": 168, + "__docId__": 167, "kind": "member", "name": "subscriptions", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3904,7 +3904,7 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#subscriptions", "access": "public", "description": null, - "lineNumber": 308, + "lineNumber": 304, "undocument": true, "type": { "types": [ @@ -3913,7 +3913,7 @@ } }, { - "__docId__": 169, + "__docId__": 168, "kind": "method", "name": "subscribePreviousSubscriptions", "memberof": "build/common/api/WebSocketAPI.js~WebSocketAPI", @@ -3923,15 +3923,15 @@ "longname": "build/common/api/WebSocketAPI.js~WebSocketAPI#subscribePreviousSubscriptions", "access": "public", "description": "After an auto reconnection we need to re-subscribe to the channels.", - "lineNumber": 322, + "lineNumber": 318, "params": [], "return": null }, { - "__docId__": 170, + "__docId__": 169, "kind": "file", "name": "build/common/controls/ControlCommon.js", - "content": "import BaseObject from 'ol/Object';\n/**\n * A class representing a control to display on map.\n *\n * @example\n * const control = new Control();\n *\n * @classproperty {ol/Map~Map|mapboxgl.Map} map - The map which the control refers to.\n * @classproperty {boolean} active - Active the control.\n * @classproperty {HTMLElement} element - The HTML element used to render the control.\n * @classproperty {HTMLElement} target - The HTML element where to render the element property. Default is the map's element. Read only.\n */\nclass ControlCommon extends BaseObject {\n /**\n * Constructor\n *\n * @param {Object} [options] Control options.\n * @param {boolean} [options.active = true] Whether the control is active or not.\n * @param {HTMLElement} [options.element] The HTML element used to render the control.\n * @param {HTMLElement} [options.target] The HTML element where to render the element property. Default is the map's element.\n * @param {function} [options.render] Render function called whenever the control needs to be rerendered.\n */\n constructor(options = {}) {\n super(options);\n this.defineProperties(options);\n const { active } = Object.assign({ active: options.active !== false }, options);\n /**\n * @ignore\n */\n this.active = active;\n }\n /**\n * Define control's properties.\n *\n * @private\n * @ignore\n */\n defineProperties(options) {\n const { target, element, render } = Object.assign({}, options);\n Object.defineProperties(this, {\n active: {\n get: () => this.get('active'),\n set: (newActive) => {\n this.set('active', newActive);\n if (newActive) {\n this.activate();\n }\n else {\n this.deactivate();\n }\n this.render();\n },\n },\n map: {\n get: () => this.get('map'),\n set: (map) => {\n // Remove previous node.\n if (this.map && this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n // Clean listeners\n this.deactivate();\n this.set('map', map);\n if (this.map) {\n // Add new node\n const targett = this.target ||\n (this.map.getTargetElement &&\n this.map.getTargetElement()) ||\n (this.map.getContainer &&\n this.map.getContainer());\n if (!this.element) {\n this.createDefaultElement();\n }\n if (this.element) {\n targett.appendChild(this.element);\n }\n // Add listeners\n if (this.active) {\n this.activate();\n }\n }\n this.render();\n },\n },\n target: {\n value: target,\n },\n element: {\n value: element,\n writable: true,\n },\n render: {\n /** @ignore */\n value: render || this.render,\n writable: true,\n },\n });\n }\n /**\n * Attach the control to the map. Add events, html element ...\n */\n attachToMap(map) {\n this.map = map;\n }\n /**\n * Detach the control From the map. Remove events, html element ..\n */\n detachFromMap() {\n this.map = undefined;\n }\n /**\n * Add listeners then renders the control.\n * To be defined in inherited classes.\n */\n activate() {\n this.deactivate();\n }\n /**\n * Remove listeners added by activate() function then renders the control.\n * To be defined in inherited classes.\n */\n // eslint-disable-next-line class-methods-use-this\n deactivate() {\n // eslint-disable-next-line no-console\n console.error('The function deactivate() must be implemented in subclasses');\n }\n /**\n * The default render function. It renders content in the HTML element.\n * To be defined in inherited classes.\n *\n * @private\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n render(options) {\n // eslint-disable-next-line no-console\n console.error('The function render() must be implemented in subclasses');\n }\n /**\n * The default element to display if this.element is not defined.\n * To be defined in inherited classes.\n *\n * @private\n */\n // eslint-disable-next-line class-methods-use-this\n createDefaultElement() {\n // eslint-disable-next-line no-console\n console.error('The function createDefaultElement() must be implemented in subclasses');\n }\n}\nexport default ControlCommon;\n", + "content": "import BaseObject from 'ol/Object';\n/**\n * A class representing a control to display on map.\n *\n * @example\n * const control = new Control();\n *\n * @classproperty {ol/Map~Map|mapboxgl.Map} map - The map which the control refers to.\n * @classproperty {boolean} active - Active the control.\n * @classproperty {HTMLElement} element - The HTML element used to render the control.\n * @classproperty {HTMLElement} target - The HTML element where to render the element property. Default is the map's element. Read only.\n */\nclass ControlCommon extends BaseObject {\n /**\n * Constructor\n *\n * @param {Object} [options] Control options.\n * @param {boolean} [options.active = true] Whether the control is active or not.\n * @param {HTMLElement} [options.element] The HTML element used to render the control.\n * @param {HTMLElement} [options.target] The HTML element where to render the element property. Default is the map's element.\n * @param {function} [options.render] Render function called whenever the control needs to be rerendered.\n */\n constructor(options = {}) {\n super(options);\n this.defineProperties(options);\n const { active } = {\n active: options.active !== false,\n ...options,\n };\n /**\n * @ignore\n */\n this.active = active;\n }\n /**\n * Define control's properties.\n *\n * @private\n * @ignore\n */\n defineProperties(options) {\n const { target, element, render } = {\n ...options,\n };\n Object.defineProperties(this, {\n active: {\n get: () => this.get('active'),\n set: (newActive) => {\n this.set('active', newActive);\n if (newActive) {\n this.activate();\n }\n else {\n this.deactivate();\n }\n this.render();\n },\n },\n map: {\n get: () => this.get('map'),\n set: (map) => {\n // Remove previous node.\n if (this.map && this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n // Clean listeners\n this.deactivate();\n this.set('map', map);\n if (this.map) {\n // Add new node\n const targett = this.target ||\n (this.map.getTargetElement &&\n this.map.getTargetElement()) ||\n (this.map.getContainer &&\n this.map.getContainer());\n if (!this.element) {\n this.createDefaultElement();\n }\n if (this.element) {\n targett.appendChild(this.element);\n }\n // Add listeners\n if (this.active) {\n this.activate();\n }\n }\n this.render();\n },\n },\n target: {\n value: target,\n },\n element: {\n value: element,\n writable: true,\n },\n render: {\n /** @ignore */\n value: render || this.render,\n writable: true,\n },\n });\n }\n /**\n * Attach the control to the map. Add events, html element ...\n */\n attachToMap(map) {\n this.map = map;\n }\n /**\n * Detach the control From the map. Remove events, html element ..\n */\n detachFromMap() {\n this.map = undefined;\n }\n /**\n * Add listeners then renders the control.\n * To be defined in inherited classes.\n */\n activate() {\n this.deactivate();\n }\n /**\n * Remove listeners added by activate() function then renders the control.\n * To be defined in inherited classes.\n */\n // eslint-disable-next-line class-methods-use-this\n deactivate() {\n // eslint-disable-next-line no-console\n console.error('The function deactivate() must be implemented in subclasses');\n }\n /**\n * The default render function. It renders content in the HTML element.\n * To be defined in inherited classes.\n *\n * @private\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n render(options) {\n // eslint-disable-next-line no-console\n console.error('The function render() must be implemented in subclasses');\n }\n /**\n * The default element to display if this.element is not defined.\n * To be defined in inherited classes.\n *\n * @private\n */\n // eslint-disable-next-line class-methods-use-this\n createDefaultElement() {\n // eslint-disable-next-line no-console\n console.error('The function createDefaultElement() must be implemented in subclasses');\n }\n}\nexport default ControlCommon;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/controls/ControlCommon.js", "access": "public", @@ -3939,7 +3939,7 @@ "lineNumber": 1 }, { - "__docId__": 171, + "__docId__": 170, "kind": "class", "name": "ControlCommon", "memberof": "build/common/controls/ControlCommon.js", @@ -3978,7 +3978,7 @@ ] }, { - "__docId__": 172, + "__docId__": 171, "kind": "constructor", "name": "constructor", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4045,7 +4045,7 @@ ] }, { - "__docId__": 173, + "__docId__": 172, "kind": "member", "name": "active", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4053,7 +4053,7 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#active", "access": "public", "description": "", - "lineNumber": 30, + "lineNumber": 33, "ignore": true, "type": { "types": [ @@ -4062,7 +4062,7 @@ } }, { - "__docId__": 174, + "__docId__": 173, "kind": "method", "name": "defineProperties", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4072,7 +4072,7 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#defineProperties", "access": "private", "description": "Define control's properties.", - "lineNumber": 38, + "lineNumber": 41, "ignore": true, "params": [ { @@ -4085,7 +4085,7 @@ "return": null }, { - "__docId__": 175, + "__docId__": 174, "kind": "method", "name": "attachToMap", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4095,7 +4095,7 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#attachToMap", "access": "public", "description": "Attach the control to the map. Add events, html element ...", - "lineNumber": 102, + "lineNumber": 107, "params": [ { "name": "map", @@ -4107,7 +4107,7 @@ "return": null }, { - "__docId__": 176, + "__docId__": 175, "kind": "member", "name": "map", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4115,7 +4115,7 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#map", "access": "public", "description": null, - "lineNumber": 103, + "lineNumber": 108, "undocument": true, "type": { "types": [ @@ -4124,7 +4124,7 @@ } }, { - "__docId__": 177, + "__docId__": 176, "kind": "method", "name": "detachFromMap", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4134,12 +4134,12 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#detachFromMap", "access": "public", "description": "Detach the control From the map. Remove events, html element ..", - "lineNumber": 108, + "lineNumber": 113, "params": [], "return": null }, { - "__docId__": 179, + "__docId__": 178, "kind": "method", "name": "activate", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4149,12 +4149,12 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#activate", "access": "public", "description": "Add listeners then renders the control.\nTo be defined in inherited classes.", - "lineNumber": 115, + "lineNumber": 120, "params": [], "return": null }, { - "__docId__": 180, + "__docId__": 179, "kind": "method", "name": "deactivate", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4164,12 +4164,12 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#deactivate", "access": "public", "description": "Remove listeners added by activate() function then renders the control.\nTo be defined in inherited classes.", - "lineNumber": 123, + "lineNumber": 128, "params": [], "return": null }, { - "__docId__": 181, + "__docId__": 180, "kind": "method", "name": "render", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4179,7 +4179,7 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#render", "access": "private", "description": "The default render function. It renders content in the HTML element.\nTo be defined in inherited classes.", - "lineNumber": 134, + "lineNumber": 139, "params": [ { "name": "options", @@ -4191,7 +4191,7 @@ "return": null }, { - "__docId__": 182, + "__docId__": 181, "kind": "method", "name": "createDefaultElement", "memberof": "build/common/controls/ControlCommon.js~ControlCommon", @@ -4201,12 +4201,12 @@ "longname": "build/common/controls/ControlCommon.js~ControlCommon#createDefaultElement", "access": "private", "description": "The default element to display if this.element is not defined.\nTo be defined in inherited classes.", - "lineNumber": 145, + "lineNumber": 150, "params": [], "return": null }, { - "__docId__": 183, + "__docId__": 182, "kind": "file", "name": "build/common/controls/CopyrightControlCommon.js", "content": "/* eslint-disable max-classes-per-file */\nimport ControlCommon from './ControlCommon';\n/**\n * A class representing a copyright control to display on map.\n * This class only draw an html element, with an empty string in it.\n * Use subclasses to use it in an ol or mapbox map.\n */\nclass CopyrightControlCommon extends ControlCommon {\n render() {\n if (!this.element) {\n return;\n }\n this.element.innerHTML = this.active\n ? this.getCopyrights().join(' | ')\n : '';\n }\n createDefaultElement() {\n this.element = document.createElement('div');\n this.element.id = 'mbt-copyright';\n Object.assign(this.element.style, {\n position: 'absolute',\n bottom: 0,\n right: 0,\n fontSize: '.8rem',\n padding: '0 10px',\n });\n }\n getCopyrights() {\n // eslint-disable-next-line no-console\n console.error('The getCopyrights() function must be implemented in subclasses.', this);\n return [];\n }\n}\nexport default CopyrightControlCommon;\n", @@ -4217,7 +4217,7 @@ "lineNumber": 1 }, { - "__docId__": 184, + "__docId__": 183, "kind": "class", "name": "CopyrightControlCommon", "memberof": "build/common/controls/CopyrightControlCommon.js", @@ -4235,7 +4235,7 @@ ] }, { - "__docId__": 185, + "__docId__": 184, "kind": "method", "name": "render", "memberof": "build/common/controls/CopyrightControlCommon.js~CopyrightControlCommon", @@ -4251,7 +4251,7 @@ "return": null }, { - "__docId__": 186, + "__docId__": 185, "kind": "method", "name": "createDefaultElement", "memberof": "build/common/controls/CopyrightControlCommon.js~CopyrightControlCommon", @@ -4267,7 +4267,7 @@ "return": null }, { - "__docId__": 187, + "__docId__": 186, "kind": "member", "name": "element", "memberof": "build/common/controls/CopyrightControlCommon.js~CopyrightControlCommon", @@ -4284,7 +4284,7 @@ } }, { - "__docId__": 188, + "__docId__": 187, "kind": "method", "name": "getCopyrights", "memberof": "build/common/controls/CopyrightControlCommon.js~CopyrightControlCommon", @@ -4304,10 +4304,10 @@ } }, { - "__docId__": 189, + "__docId__": 188, "kind": "file", "name": "build/common/controls/StopFinderControlCommon.js", - "content": "import { StopsAPI } from '../../api';\nimport ControlCommon from './ControlCommon';\n/**\n * A class representing a stop finder control to display on map.\n * This class only draw the html elements.\n * The geographic logic must be implemented by subclasses.\n */\nclass StopFinderControlCommon extends ControlCommon {\n /**\n * Constructor.\n *\n * @param {Object} options Map options\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/). See StopsAPI.\n * @param {string} [options.url='https://api.geops.io/tracker/v1'] Stops service url. See StopsAPI.\n * @param {string} [options.placeholder='Search for a stop...'] Input field placeholder.\n * @param {StopsSearchParams} [options.apiParams={ limit: 20 }] Request parameters. See [Stops service documentation](https://developer.geops.io/apis/5dcbd702a256d90001cf1361/).\n */\n constructor(options) {\n super(options);\n const { apiParams, apiKey, url, placeholder } = options || {};\n this.apiParams = Object.assign({ limit: 20 }, (apiParams || {}));\n this.placeholder = placeholder || 'Search for a stop...';\n const apiOptions = { apiKey };\n if (url) {\n apiOptions.url = url;\n }\n this.api = new StopsAPI(apiOptions);\n this.abortController = new AbortController();\n }\n deactivate() { }\n render(featureCollection) {\n const suggestions = (featureCollection === null || featureCollection === void 0 ? void 0 : featureCollection.features) || [];\n if (!this.suggestionsElt) {\n return;\n }\n this.suggestionsElt.style.display = suggestions.length ? 'block' : 'none';\n this.suggestionsElt.innerHTML = '';\n suggestions.forEach((suggestion) => {\n var _a, _b;\n const suggElt = document.createElement('div');\n suggElt.innerHTML = (_a = suggestion === null || suggestion === void 0 ? void 0 : suggestion.properties) === null || _a === void 0 ? void 0 : _a.name;\n suggElt.onclick = () => {\n // @ts-ignore\n this.onSuggestionClick(suggestion);\n };\n Object.assign(suggElt.style, {\n padding: '5px 12px',\n });\n (_b = this.suggestionsElt) === null || _b === void 0 ? void 0 : _b.appendChild(suggElt);\n });\n }\n createDefaultElement() {\n /**\n * Define a default element.\n */\n this.element = document.createElement('div');\n this.element.id = 'mbt-search';\n Object.assign(this.element.style, {\n position: 'absolute',\n top: 0,\n left: '50px',\n margin: '10px',\n display: 'flex',\n flexDirection: 'column',\n width: '320px',\n });\n // Create input element\n this.inputElt = document.createElement('input');\n this.inputElt.type = 'text';\n this.inputElt.placeholder = this.placeholder;\n this.inputElt.autocomplete = 'off';\n this.inputElt.onkeyup = (evt) => {\n var _a;\n (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();\n this.abortController = new AbortController();\n // @ts-ignore\n this.search(evt.target.value, this.abortController);\n };\n Object.assign(this.inputElt.style, {\n padding: '10px 30px 10px 10px',\n });\n this.element.appendChild(this.inputElt);\n // Create suggestions list element\n this.suggestionsElt = document.createElement('div');\n Object.assign(this.suggestionsElt.style, {\n backgroundColor: 'white',\n overflowY: 'auto',\n cursor: 'pointer',\n });\n this.element.appendChild(this.suggestionsElt);\n this.clearElt = document.createElement('div');\n Object.assign(this.clearElt.style, {\n display: 'none',\n position: 'absolute',\n right: '0',\n padding: '0 10px',\n fontSize: '200%',\n cursor: 'pointer',\n });\n this.clearElt.innerHTML = '×';\n this.clearElt.onclick = () => this.clear();\n this.element.appendChild(this.clearElt);\n }\n /**\n * Launch a search.\n *\n * @param {String} query The query to search for.\n * @param {AbortController} abortController Abort controller used to cancel the request.\n * @return {Promise>} An array of GeoJSON features with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n search(q, abortController) {\n if (q !== undefined || q !== null) {\n this.apiParams.q = q;\n }\n if (this.clearElt) {\n this.clearElt.style.display = 'block';\n }\n return this.api\n .search(this.apiParams, abortController && { signal: abortController.signal })\n .then((data) => {\n this.render(data);\n })\n .catch(() => {\n this.render();\n });\n }\n /**\n * To be defined in inherited class\n */\n // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars\n onSuggestionClick(suggestion) { }\n /**\n * Clear the search field and close the control.\n */\n clear() {\n if (!this.suggestionsElt || !this.inputElt || !this.clearElt) {\n return;\n }\n this.inputElt.value = '';\n this.suggestionsElt.innerHTML = '';\n this.clearElt.style.display = 'none';\n }\n}\nexport default StopFinderControlCommon;\n", + "content": "import { StopsAPI } from '../../api';\nimport ControlCommon from './ControlCommon';\n/**\n * A class representing a stop finder control to display on map.\n * This class only draw the html elements.\n * The geographic logic must be implemented by subclasses.\n */\nclass StopFinderControlCommon extends ControlCommon {\n /**\n * Constructor.\n *\n * @param {Object} options Map options\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/). See StopsAPI.\n * @param {string} [options.url='https://api.geops.io/tracker/v1'] Stops service url. See StopsAPI.\n * @param {string} [options.placeholder='Search for a stop...'] Input field placeholder.\n * @param {StopsSearchParams} [options.apiParams={ limit: 20 }] Request parameters. See [Stops service documentation](https://developer.geops.io/apis/5dcbd702a256d90001cf1361/).\n */\n constructor(options) {\n super(options);\n const { apiParams, apiKey, url, placeholder } = options || {};\n this.apiParams = { limit: 20, ...(apiParams || {}) };\n this.placeholder = placeholder || 'Search for a stop...';\n const apiOptions = { apiKey };\n if (url) {\n apiOptions.url = url;\n }\n this.api = new StopsAPI(apiOptions);\n this.abortController = new AbortController();\n }\n deactivate() { }\n render(featureCollection) {\n const suggestions = featureCollection?.features || [];\n if (!this.suggestionsElt) {\n return;\n }\n this.suggestionsElt.style.display = suggestions.length ? 'block' : 'none';\n this.suggestionsElt.innerHTML = '';\n suggestions.forEach((suggestion) => {\n const suggElt = document.createElement('div');\n suggElt.innerHTML = suggestion?.properties?.name;\n suggElt.onclick = () => {\n // @ts-ignore\n this.onSuggestionClick(suggestion);\n };\n Object.assign(suggElt.style, {\n padding: '5px 12px',\n });\n this.suggestionsElt?.appendChild(suggElt);\n });\n }\n createDefaultElement() {\n /**\n * Define a default element.\n */\n this.element = document.createElement('div');\n this.element.id = 'mbt-search';\n Object.assign(this.element.style, {\n position: 'absolute',\n top: 0,\n left: '50px',\n margin: '10px',\n display: 'flex',\n flexDirection: 'column',\n width: '320px',\n });\n // Create input element\n this.inputElt = document.createElement('input');\n this.inputElt.type = 'text';\n this.inputElt.placeholder = this.placeholder;\n this.inputElt.autocomplete = 'off';\n this.inputElt.onkeyup = (evt) => {\n this.abortController?.abort();\n this.abortController = new AbortController();\n // @ts-ignore\n this.search(evt.target.value, this.abortController);\n };\n Object.assign(this.inputElt.style, {\n padding: '10px 30px 10px 10px',\n });\n this.element.appendChild(this.inputElt);\n // Create suggestions list element\n this.suggestionsElt = document.createElement('div');\n Object.assign(this.suggestionsElt.style, {\n backgroundColor: 'white',\n overflowY: 'auto',\n cursor: 'pointer',\n });\n this.element.appendChild(this.suggestionsElt);\n this.clearElt = document.createElement('div');\n Object.assign(this.clearElt.style, {\n display: 'none',\n position: 'absolute',\n right: '0',\n padding: '0 10px',\n fontSize: '200%',\n cursor: 'pointer',\n });\n this.clearElt.innerHTML = '×';\n this.clearElt.onclick = () => this.clear();\n this.element.appendChild(this.clearElt);\n }\n /**\n * Launch a search.\n *\n * @param {String} query The query to search for.\n * @param {AbortController} abortController Abort controller used to cancel the request.\n * @return {Promise>} An array of GeoJSON features with coordinates in [EPSG:4326](http://epsg.io/4326).\n */\n search(q, abortController) {\n if (q !== undefined || q !== null) {\n this.apiParams.q = q;\n }\n if (this.clearElt) {\n this.clearElt.style.display = 'block';\n }\n return this.api\n .search(this.apiParams, abortController && { signal: abortController.signal })\n .then((data) => {\n this.render(data);\n })\n .catch(() => {\n this.render();\n });\n }\n /**\n * To be defined in inherited class\n */\n // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars\n onSuggestionClick(suggestion) { }\n /**\n * Clear the search field and close the control.\n */\n clear() {\n if (!this.suggestionsElt || !this.inputElt || !this.clearElt) {\n return;\n }\n this.inputElt.value = '';\n this.suggestionsElt.innerHTML = '';\n this.clearElt.style.display = 'none';\n }\n}\nexport default StopFinderControlCommon;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/controls/StopFinderControlCommon.js", "access": "public", @@ -4315,7 +4315,7 @@ "lineNumber": 1 }, { - "__docId__": 190, + "__docId__": 189, "kind": "class", "name": "StopFinderControlCommon", "memberof": "build/common/controls/StopFinderControlCommon.js", @@ -4333,7 +4333,7 @@ ] }, { - "__docId__": 191, + "__docId__": 190, "kind": "constructor", "name": "constructor", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4404,7 +4404,7 @@ ] }, { - "__docId__": 192, + "__docId__": 191, "kind": "member", "name": "apiParams", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4416,12 +4416,12 @@ "undocument": true, "type": { "types": [ - "*" + "{\"limit\": number, ...undefined: Object}" ] } }, { - "__docId__": 193, + "__docId__": 192, "kind": "member", "name": "placeholder", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4438,7 +4438,7 @@ } }, { - "__docId__": 194, + "__docId__": 193, "kind": "member", "name": "api", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4455,7 +4455,7 @@ } }, { - "__docId__": 195, + "__docId__": 194, "kind": "member", "name": "abortController", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4472,7 +4472,7 @@ } }, { - "__docId__": 196, + "__docId__": 195, "kind": "method", "name": "deactivate", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4488,7 +4488,7 @@ "return": null }, { - "__docId__": 197, + "__docId__": 196, "kind": "method", "name": "render", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4511,7 +4511,7 @@ "return": null }, { - "__docId__": 198, + "__docId__": 197, "kind": "method", "name": "createDefaultElement", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4521,13 +4521,13 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#createDefaultElement", "access": "public", "description": null, - "lineNumber": 52, + "lineNumber": 51, "undocument": true, "params": [], "return": null }, { - "__docId__": 199, + "__docId__": 198, "kind": "member", "name": "element", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4535,7 +4535,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#element", "access": "public", "description": "Define a default element.", - "lineNumber": 56, + "lineNumber": 55, "type": { "types": [ "*" @@ -4543,7 +4543,7 @@ } }, { - "__docId__": 200, + "__docId__": 199, "kind": "member", "name": "inputElt", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4551,7 +4551,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#inputElt", "access": "public", "description": null, - "lineNumber": 68, + "lineNumber": 67, "undocument": true, "type": { "types": [ @@ -4560,7 +4560,7 @@ } }, { - "__docId__": 202, + "__docId__": 201, "kind": "member", "name": "suggestionsElt", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4568,7 +4568,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#suggestionsElt", "access": "public", "description": null, - "lineNumber": 84, + "lineNumber": 82, "undocument": true, "type": { "types": [ @@ -4577,7 +4577,7 @@ } }, { - "__docId__": 203, + "__docId__": 202, "kind": "member", "name": "clearElt", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4585,7 +4585,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#clearElt", "access": "public", "description": null, - "lineNumber": 91, + "lineNumber": 89, "undocument": true, "type": { "types": [ @@ -4594,7 +4594,7 @@ } }, { - "__docId__": 204, + "__docId__": 203, "kind": "method", "name": "search", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4604,7 +4604,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#search", "access": "public", "description": "Launch a search.", - "lineNumber": 111, + "lineNumber": 109, "params": [ { "nullable": null, @@ -4637,7 +4637,7 @@ } }, { - "__docId__": 205, + "__docId__": 204, "kind": "method", "name": "onSuggestionClick", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4647,7 +4647,7 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#onSuggestionClick", "access": "public", "description": "To be defined in inherited class", - "lineNumber": 131, + "lineNumber": 129, "params": [ { "name": "suggestion", @@ -4659,7 +4659,7 @@ "return": null }, { - "__docId__": 206, + "__docId__": 205, "kind": "method", "name": "clear", "memberof": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon", @@ -4669,12 +4669,12 @@ "longname": "build/common/controls/StopFinderControlCommon.js~StopFinderControlCommon#clear", "access": "public", "description": "Clear the search field and close the control.", - "lineNumber": 135, + "lineNumber": 133, "params": [], "return": null }, { - "__docId__": 207, + "__docId__": 206, "kind": "file", "name": "build/common/index.js", "content": "export * from './utils';\nexport * from './styles';\n", @@ -4685,10 +4685,10 @@ "lineNumber": 1 }, { - "__docId__": 208, + "__docId__": 207, "kind": "file", "name": "build/common/layers/LayerCommon.js", - "content": "import BaseObject from 'ol/Object';\nimport { v4 as uuid } from 'uuid';\nimport BaseEvent from 'ol/events/Event';\nimport getLayersAsFlatArray from '../utils/getLayersAsFlatArray';\n/**\n * A class representing a layer to display on map.\n *\n * @example\n * const layer = new Layer({\n * name: 'My Layer',\n * });\n *\n * @classproperty {string} key - Identifier of the layer. Must be unique.\n * @classproperty {string} name - Name of the layer\n * @classproperty {string[]} copyrights - Array of copyrights.\n * @classproperty {Layer[]} children - List of children layers.\n * @classproperty {boolean} visible - Define if the layer is currently display on the map.\n * @classproperty {boolean} disabled - Define if the layer is currently display on the map but can't be seen (extent, zoom ,data restrictions).\n * @classproperty {number} hitTolerance - Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.\n * @classproperty {Object} properties - Custom properties.\n * @classproperty {ol/Map~Map|mapboxgl.Map} map - The map where the layer is displayed.\n */\nexport default class Layer extends BaseObject {\n /**\n * Constructor\n *\n * @param {Object} options\n * @param {string} [options.key=uuid()] Identifier of the layer. Muste be unique. Default use a generated uuid.\n * @param {string} [options.name] Name of the layer.\n * @param {string[]} [options.copyrights] Array of copyrights.\n * @param {Array} [options.children=[]] Sublayers, all child layers will have a parent property associated to this layer.\n * @param {boolean} [options.visible=true] Define if the layer is currently display on the map.\n * @param {boolean} [options.disabled=false] Define if the layer is currently display on the map but can't be seen (extent, zoom ,data restrictions).\n * @param {number} [options.hitTolerance=5] Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.\n * @param {Object} [options.properties={}] Application-specific layer properties.\n */\n constructor(options = {}) {\n super();\n this.properties = {};\n this.options = {};\n this.defineProperties(options);\n if (options.properties) {\n this.setProperties(options.properties);\n }\n this.options = options;\n this.visible = options.visible === undefined ? true : !!options.visible;\n this.group = options.group;\n this.copyrights = options.copyrights;\n this.children = options.children;\n // Listen for group visiblity change\n // if a layer from a group is newly visible we hide the others.\n /* @ts-ignore */\n this.on(`change:visible:group`, (evt) => {\n // We hide layer of the same group\n if (this.group === evt.target.group &&\n this !== evt.target &&\n this.visible) {\n this.visible = false;\n // Propagate event to parent\n }\n else if (this.children) {\n this.children.forEach((child) => child.dispatchEvent(evt));\n }\n });\n }\n /**\n * Define layer's properties that needs custom get and set.\n *\n * @ignore\n */\n defineProperties(options = {}) {\n const { name, key, properties, hitTolerance } = Object.assign({}, options);\n const uid = uuid();\n Object.defineProperties(this, {\n /* Layer's information properties */\n name: {\n value: name,\n },\n key: {\n value: key || name || uid,\n },\n group: {\n get: () => this.get('group'),\n set: (newGroup) => {\n this.set('group', newGroup);\n },\n },\n copyrights: {\n get: () => this.get('copyrights'),\n set: (newCopyrights) => {\n const arrValue = newCopyrights && !Array.isArray(newCopyrights)\n ? [newCopyrights]\n : newCopyrights;\n this.set('copyrights', arrValue || []);\n },\n },\n // options is used for clone function.\n options: {\n value: options,\n },\n map: {\n writable: true,\n },\n /* Layer's state properties */\n visible: {\n get: () => this.get('visible'),\n set: (newVisible) => {\n if (newVisible === this.visible) {\n return;\n }\n this.set('visible', newVisible);\n if (this.visible) {\n // We make the parent visible\n if (this.parent) {\n this.parent.visible = true;\n }\n // If children doesn't contain any visible layers, we display all children.\n if (this.children &&\n !this.children.some((child) => child.visible)) {\n this.children.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.visible = true;\n });\n }\n // Warn the same group that a new layer is visible\n if (this.parent && this.group) {\n // We search for the higher parent then it will dispatch to all the tree.\n let higherParent = this.parent;\n while (higherParent.parent) {\n higherParent = higherParent.parent;\n }\n const evt = new BaseEvent(`change:visible:group`);\n evt.target = this;\n higherParent.dispatchEvent(evt);\n }\n }\n else if (!this.visible) {\n // We hide all the children\n if (this.children) {\n this.children.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.visible = false;\n });\n }\n // If the parent has no more visible child we also hide it.\n if (this.parent &&\n this.parent.visible &&\n this.parent.children &&\n !this.parent.children.find((child) => child.visible)) {\n this.parent.visible = false;\n }\n }\n },\n },\n disabled: {\n get: () => this.get('disabled'),\n set: (newValue) => {\n this.set('disabled', newValue);\n },\n },\n /* Layer's hierarchy properties */\n parent: {\n value: null,\n writable: true,\n },\n children: {\n get: () => this.get('children') || [],\n set: (newValue) => {\n (this.children || []).forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.parent = undefined;\n });\n if (Array.isArray(newValue)) {\n newValue.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.parent = this;\n });\n }\n this.set('children', newValue || []);\n },\n },\n /* Layer's query properties */\n hitTolerance: {\n value: hitTolerance || 5,\n writable: true,\n },\n /* Custom app specific properties */\n properties: {\n value: Object.assign({}, (properties || {})),\n },\n });\n }\n /**\n * Initialize the layer with the map passed in parameters.\n *\n * @param {ol/Map~Map|mapboxgl.Map} map A map.\n */\n attachToMap(map) {\n this.detachFromMap();\n /** @ignore */\n this.map = map;\n if (this.children) {\n this.children.forEach((child) => {\n child.attachToMap(map);\n });\n }\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n // eslint-disable-next-line class-methods-use-this\n detachFromMap() {\n /** @ignore */\n this.map = undefined;\n }\n /**\n * Request feature information for a given coordinate.\n * This function must be implemented by inheriting layers.\n *\n * @param {ol/coordinate~Coordinate} coordinate Coordinate.\n * @param {Object} options Some options. See child classes to see which are supported.\n * @return {Promise} An empty response.\n */\n // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars\n getFeatureInfoAtCoordinate(coordinate, \n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n options) {\n // eslint-disable-next-line no-console\n console.error('getFeatureInfoAtCoordinate must be implemented by inheriting layers', this.key);\n // This layer returns no feature info.\n // The function is implemented by inheriting layers.\n return Promise.resolve({\n layer: this,\n features: [],\n coordinate,\n });\n }\n /**\n * Return the an array containing all the descendants of the layer in a flat array. Including the current layer.\n */\n flat() {\n return getLayersAsFlatArray(this);\n }\n}\n", + "content": "import BaseObject from 'ol/Object';\nimport { v4 as uuid } from 'uuid';\nimport BaseEvent from 'ol/events/Event';\nimport getLayersAsFlatArray from '../utils/getLayersAsFlatArray';\n/**\n * A class representing a layer to display on map.\n *\n * @example\n * const layer = new Layer({\n * name: 'My Layer',\n * });\n *\n * @classproperty {string} key - Identifier of the layer. Must be unique.\n * @classproperty {string} name - Name of the layer\n * @classproperty {string[]} copyrights - Array of copyrights.\n * @classproperty {Layer[]} children - List of children layers.\n * @classproperty {boolean} visible - Define if the layer is currently display on the map.\n * @classproperty {boolean} disabled - Define if the layer is currently display on the map but can't be seen (extent, zoom ,data restrictions).\n * @classproperty {number} hitTolerance - Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.\n * @classproperty {Object} properties - Custom properties.\n * @classproperty {ol/Map~Map|mapboxgl.Map} map - The map where the layer is displayed.\n */\nexport default class Layer extends BaseObject {\n /**\n * Constructor\n *\n * @param {Object} options\n * @param {string} [options.key=uuid()] Identifier of the layer. Muste be unique. Default use a generated uuid.\n * @param {string} [options.name] Name of the layer.\n * @param {string[]} [options.copyrights] Array of copyrights.\n * @param {Array} [options.children=[]] Sublayers, all child layers will have a parent property associated to this layer.\n * @param {boolean} [options.visible=true] Define if the layer is currently display on the map.\n * @param {boolean} [options.disabled=false] Define if the layer is currently display on the map but can't be seen (extent, zoom ,data restrictions).\n * @param {number} [options.hitTolerance=5] Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.\n * @param {Object} [options.properties={}] Application-specific layer properties.\n */\n constructor(options = {}) {\n super();\n this.properties = {};\n this.options = {};\n this.defineProperties(options);\n if (options.properties) {\n this.setProperties(options.properties);\n }\n this.options = options;\n this.visible = options.visible === undefined ? true : !!options.visible;\n this.group = options.group;\n this.copyrights = options.copyrights;\n this.children = options.children;\n // Listen for group visiblity change\n // if a layer from a group is newly visible we hide the others.\n /* @ts-ignore */\n this.on(`change:visible:group`, (evt) => {\n // We hide layer of the same group\n if (this.group === evt.target.group &&\n this !== evt.target &&\n this.visible) {\n this.visible = false;\n // Propagate event to parent\n }\n else if (this.children) {\n this.children.forEach((child) => child.dispatchEvent(evt));\n }\n });\n }\n /**\n * Define layer's properties that needs custom get and set.\n *\n * @ignore\n */\n defineProperties(options = {}) {\n const { name, key, properties, hitTolerance } = {\n ...options,\n };\n const uid = uuid();\n Object.defineProperties(this, {\n /* Layer's information properties */\n name: {\n value: name,\n },\n key: {\n value: key || name || uid,\n },\n group: {\n get: () => this.get('group'),\n set: (newGroup) => {\n this.set('group', newGroup);\n },\n },\n copyrights: {\n get: () => this.get('copyrights'),\n set: (newCopyrights) => {\n const arrValue = newCopyrights && !Array.isArray(newCopyrights)\n ? [newCopyrights]\n : newCopyrights;\n this.set('copyrights', arrValue || []);\n },\n },\n // options is used for clone function.\n options: {\n value: options,\n },\n map: {\n writable: true,\n },\n /* Layer's state properties */\n visible: {\n get: () => this.get('visible'),\n set: (newVisible) => {\n if (newVisible === this.visible) {\n return;\n }\n this.set('visible', newVisible);\n if (this.visible) {\n // We make the parent visible\n if (this.parent) {\n this.parent.visible = true;\n }\n // If children doesn't contain any visible layers, we display all children.\n if (this.children &&\n !this.children.some((child) => child.visible)) {\n this.children.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.visible = true;\n });\n }\n // Warn the same group that a new layer is visible\n if (this.parent && this.group) {\n // We search for the higher parent then it will dispatch to all the tree.\n let higherParent = this.parent;\n while (higherParent.parent) {\n higherParent = higherParent.parent;\n }\n const evt = new BaseEvent(`change:visible:group`);\n evt.target = this;\n higherParent.dispatchEvent(evt);\n }\n }\n else if (!this.visible) {\n // We hide all the children\n if (this.children) {\n this.children.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.visible = false;\n });\n }\n // If the parent has no more visible child we also hide it.\n if (this.parent &&\n this.parent.visible &&\n this.parent.children &&\n !this.parent.children.find((child) => child.visible)) {\n this.parent.visible = false;\n }\n }\n },\n },\n disabled: {\n get: () => this.get('disabled'),\n set: (newValue) => {\n this.set('disabled', newValue);\n },\n },\n /* Layer's hierarchy properties */\n parent: {\n value: null,\n writable: true,\n },\n children: {\n get: () => this.get('children') || [],\n set: (newValue) => {\n (this.children || []).forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.parent = undefined;\n });\n if (Array.isArray(newValue)) {\n newValue.forEach((child) => {\n // eslint-disable-next-line no-param-reassign\n child.parent = this;\n });\n }\n this.set('children', newValue || []);\n },\n },\n /* Layer's query properties */\n hitTolerance: {\n value: hitTolerance || 5,\n writable: true,\n },\n /* Custom app specific properties */\n properties: {\n value: { ...(properties || {}) },\n },\n });\n }\n /**\n * Initialize the layer with the map passed in parameters.\n *\n * @param {ol/Map~Map|mapboxgl.Map} map A map.\n */\n attachToMap(map) {\n this.detachFromMap();\n /** @ignore */\n this.map = map;\n if (this.children) {\n this.children.forEach((child) => {\n child.attachToMap(map);\n });\n }\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n // eslint-disable-next-line class-methods-use-this\n detachFromMap() {\n /** @ignore */\n this.map = undefined;\n }\n /**\n * Request feature information for a given coordinate.\n * This function must be implemented by inheriting layers.\n *\n * @param {ol/coordinate~Coordinate} coordinate Coordinate.\n * @param {Object} options Some options. See child classes to see which are supported.\n * @return {Promise} An empty response.\n */\n // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars\n getFeatureInfoAtCoordinate(coordinate, \n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n options) {\n // eslint-disable-next-line no-console\n console.error('getFeatureInfoAtCoordinate must be implemented by inheriting layers', this.key);\n // This layer returns no feature info.\n // The function is implemented by inheriting layers.\n return Promise.resolve({\n layer: this,\n features: [],\n coordinate,\n });\n }\n /**\n * Return the an array containing all the descendants of the layer in a flat array. Including the current layer.\n */\n flat() {\n return getLayersAsFlatArray(this);\n }\n}\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/layers/LayerCommon.js", "access": "public", @@ -4696,7 +4696,7 @@ "lineNumber": 1 }, { - "__docId__": 209, + "__docId__": 208, "kind": "class", "name": "Layer", "memberof": "build/common/layers/LayerCommon.js", @@ -4755,7 +4755,7 @@ ] }, { - "__docId__": 210, + "__docId__": 209, "kind": "constructor", "name": "constructor", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4872,7 +4872,7 @@ ] }, { - "__docId__": 211, + "__docId__": 210, "kind": "member", "name": "properties", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4889,7 +4889,7 @@ } }, { - "__docId__": 212, + "__docId__": 211, "kind": "member", "name": "options", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4906,7 +4906,7 @@ } }, { - "__docId__": 214, + "__docId__": 213, "kind": "member", "name": "visible", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4923,7 +4923,7 @@ } }, { - "__docId__": 215, + "__docId__": 214, "kind": "member", "name": "group", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4940,7 +4940,7 @@ } }, { - "__docId__": 216, + "__docId__": 215, "kind": "member", "name": "copyrights", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4957,7 +4957,7 @@ } }, { - "__docId__": 217, + "__docId__": 216, "kind": "member", "name": "children", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -4974,7 +4974,7 @@ } }, { - "__docId__": 219, + "__docId__": 218, "kind": "method", "name": "defineProperties", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5000,7 +5000,7 @@ "return": null }, { - "__docId__": 220, + "__docId__": 219, "kind": "method", "name": "attachToMap", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5010,7 +5010,7 @@ "longname": "build/common/layers/LayerCommon.js~Layer#attachToMap", "access": "public", "description": "Initialize the layer with the map passed in parameters.", - "lineNumber": 198, + "lineNumber": 200, "params": [ { "nullable": null, @@ -5027,7 +5027,7 @@ "return": null }, { - "__docId__": 221, + "__docId__": 220, "kind": "member", "name": "map", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5035,7 +5035,7 @@ "longname": "build/common/layers/LayerCommon.js~Layer#map", "access": "public", "description": null, - "lineNumber": 201, + "lineNumber": 203, "ignore": true, "type": { "types": [ @@ -5044,7 +5044,7 @@ } }, { - "__docId__": 222, + "__docId__": 221, "kind": "method", "name": "detachFromMap", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5054,12 +5054,12 @@ "longname": "build/common/layers/LayerCommon.js~Layer#detachFromMap", "access": "public", "description": "Terminate what was initialized in init function. Remove layer, events...", - "lineNumber": 212, + "lineNumber": 214, "params": [], "return": null }, { - "__docId__": 224, + "__docId__": 223, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5069,7 +5069,7 @@ "longname": "build/common/layers/LayerCommon.js~Layer#getFeatureInfoAtCoordinate", "access": "public", "description": "Request feature information for a given coordinate.\nThis function must be implemented by inheriting layers.", - "lineNumber": 225, + "lineNumber": 227, "params": [ { "nullable": null, @@ -5102,7 +5102,7 @@ } }, { - "__docId__": 225, + "__docId__": 224, "kind": "method", "name": "flat", "memberof": "build/common/layers/LayerCommon.js~Layer", @@ -5112,7 +5112,7 @@ "longname": "build/common/layers/LayerCommon.js~Layer#flat", "access": "public", "description": "Return the an array containing all the descendants of the layer in a flat array. Including the current layer.", - "lineNumber": 241, + "lineNumber": 243, "params": [], "return": { "types": [ @@ -5121,10 +5121,10 @@ } }, { - "__docId__": 226, + "__docId__": 225, "kind": "file", "name": "build/common/mixins/RealtimeLayerMixin.js", - "content": "/* eslint-disable no-empty-function,@typescript-eslint/no-empty-function */\n/* eslint-disable no-useless-constructor,@typescript-eslint/no-useless-constructor */\n/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */\n/* eslint-disable class-methods-use-this */\n/* eslint-disable max-classes-per-file */\nimport { buffer, containsCoordinate, intersects } from 'ol/extent';\nimport { unByKey } from 'ol/Observable';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport debounce from 'lodash.debounce';\nimport throttle from 'lodash.throttle';\nimport { fromLonLat } from 'ol/proj';\nimport realtimeDefaultStyle from '../styles/realtimeDefaultStyle';\nimport { RealtimeAPI, RealtimeModes } from '../../api';\nimport renderTrajectories from '../utils/renderTrajectories';\nimport * as realtimeConfig from '../utils/realtimeConfig';\n/**\n * RealtimeLayerInterface.\n */\nexport class RealtimeLayerInterface {\n /**\n * Start the clock.\n */\n start() { }\n /**\n * Stop the clock.\n */\n stop() { }\n /**\n * Set the Realtime api's bbox.\n *\n * @param {Array} extent Extent to request, [minX, minY, maxX, maxY, zoom].\n * @param {number} zoom Zoom level to request. Must be an integer.\n */\n setBbox(extent, zoom) { }\n /**\n * Set the Realtime api's mode.\n *\n * @param {RealtimeMode} mode Realtime mode\n */\n setMode(mode) { }\n /**\n * Render the trajectories\n */\n renderTrajectories() { }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @param {RealtimeMode} mode The mode to request. If not defined, the layer´s mode propetrty will be used.\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id, mode) { }\n}\n/**\n * Mixin for RealtimeLayerInterface.\n *\n * @param {Class} Base A class to extend with {RealtimeLayerInterface} functionnalities.\n * @return {Class} A class that implements {RealtimeLayerInterface} class and extends Base;\n * @private\n */\nfunction RealtimeLayerMixin(Base) {\n // @ts-ignore\n return class Mixin extends Base {\n constructor(options) {\n super(Object.assign({ hitTolerance: 10 }, options));\n this.debug = options.debug || false;\n this.mode = options.mode || RealtimeModes.TOPOGRAPHIC;\n this.api = options.api || new RealtimeAPI(options);\n this.tenant = options.tenant || ''; // sbb,sbh or sbm\n this.minZoomInterpolation = options.minZoomInterpolation || 8; // Min zoom level from which trains positions are not interpolated.\n this.format = new GeoJSON();\n // MOTs by zoom\n const allMots = [\n 'tram',\n 'subway',\n 'rail',\n 'bus',\n 'ferry',\n 'cablecar',\n 'gondola',\n 'funicular',\n 'coach',\n ];\n const onlyRail = ['rail'];\n const withoutCable = ['tram', 'subway', 'rail', 'bus'];\n // Server will block non train before zoom 9\n this.motsByZoom = options.motsByZoom || [\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n withoutCable,\n withoutCable,\n allMots,\n allMots,\n allMots,\n allMots,\n ];\n this.getMotsByZoom = (zoom) => {\n return ((options.getMotsByZoom &&\n options.getMotsByZoom(zoom, this.motsByZoom)) ||\n this.motsByZoom[zoom] ||\n this.motsByZoom[this.motsByZoom.length - 1]);\n };\n // Generalization levels by zoom\n this.generalizationLevelByZoom = options.generalizationLevelByZoom || [\n 5, 5, 5, 5, 5, 5, 5, 5, 10, 30, 30, 100, 100, 100,\n ];\n this.getGeneralizationLevelByZoom = (zoom) => {\n return ((options.getGeneralizationLevelByZoom &&\n options.getGeneralizationLevelByZoom(zoom, this.generalizationLevelByZoom)) ||\n this.generalizationLevelByZoom[zoom]);\n };\n // Render time interval by zoom\n this.renderTimeIntervalByZoom = options.renderTimeIntervalByZoom || [\n 100000, 50000, 40000, 30000, 20000, 15000, 10000, 5000, 2000, 1000, 400,\n 300, 250, 180, 90, 60, 50, 50, 50, 50, 50,\n ];\n this.getRenderTimeIntervalByZoom = (zoom) => {\n return ((options.getRenderTimeIntervalByZoom &&\n options.getRenderTimeIntervalByZoom(zoom, this.renderTimeIntervalByZoom)) ||\n this.renderTimeIntervalByZoom[zoom]);\n };\n // This property will call api.setBbox on each movend event\n this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd !== false;\n // Define throttling and debounce render function\n this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, 50, { leading: false, trailing: true });\n this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, 50, { leading: true, trailing: true, maxWait: 5000 });\n // Bind callbacks\n this.onFeatureHover = this.onFeatureHover.bind(this);\n this.onFeatureClick = this.onFeatureClick.bind(this);\n this.renderTrajectoriesInternal =\n this.renderTrajectoriesInternal.bind(this);\n this.onTrajectoryMessage = this.onTrajectoryMessage.bind(this);\n this.onDeleteTrajectoryMessage =\n this.onDeleteTrajectoryMessage.bind(this);\n this.onDocumentVisibilityChange =\n this.onDocumentVisibilityChange.bind(this);\n }\n /**\n * Define layer's properties.\n *\n * @ignore\n */\n defineProperties(options) {\n const { style, speed, pixelRatio, hoverVehicleId, selectedVehicleId, filter, sort, time, live, canvas, styleOptions, } = options;\n let currCanvas = canvas;\n let currSpeed = speed || 1;\n let currTime = time || new Date();\n let currStyle = style || realtimeDefaultStyle;\n super.defineProperties(options);\n Object.defineProperties(this, {\n isTrackerLayer: { value: true },\n canvas: {\n get: () => {\n if (!currCanvas) {\n currCanvas = document.createElement('canvas');\n }\n return currCanvas;\n },\n set: (cnvas) => {\n currCanvas = cnvas;\n },\n },\n /**\n * Style function used to render a vehicle.\n */\n style: {\n get: () => currStyle,\n set: (newStyle) => {\n currStyle = newStyle;\n // @ts-ignore function without parameters is defined in subclasses\n this.renderTrajectories();\n },\n },\n /**\n * Custom options to pass as last parameter of the style function.\n */\n styleOptions: {\n value: Object.assign(Object.assign({}, realtimeConfig), (styleOptions || {})),\n },\n /**\n * Speed of the wheel of time.\n * If live property is true. The speed is ignored.\n */\n speed: {\n get: () => currSpeed,\n set: (newSpeed) => {\n currSpeed = newSpeed;\n this.start();\n },\n },\n /**\n * Function to filter which vehicles to display.\n */\n filter: {\n value: filter,\n writable: true,\n },\n /**\n * Function to sort the vehicles to display.\n */\n sort: {\n value: sort,\n writable: true,\n },\n /**\n * If true. The layer will always use Date.now() on the next tick to render the trajectories.\n * When true, setting the time property has no effect.\n */\n live: {\n value: live === false ? live : true,\n writable: true,\n },\n /**\n * Time used to display the trajectories. Can be a Date or a number in ms representing a Date.\n * If live property is true. The setter does nothing.\n */\n time: {\n get: () => currTime,\n set: (newTime) => {\n currTime = newTime && newTime.getTime ? newTime : new Date(newTime);\n // @ts-ignore function without parameters is defined in subclasses\n this.renderTrajectories();\n },\n },\n /**\n * Keep track of which trajectories are stored.\n */\n trajectories: {\n value: {},\n writable: true,\n },\n /**\n * Id of the hovered vehicle.\n */\n hoverVehicleId: {\n value: hoverVehicleId,\n writable: true,\n },\n /**\n * Id of the selected vehicle.\n */\n selectedVehicleId: {\n value: selectedVehicleId,\n writable: true,\n },\n /**\n * Id of the selected vehicle.\n */\n pixelRatio: {\n value: pixelRatio ||\n (typeof window !== 'undefined' ? window.devicePixelRatio : 1),\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a requestAnimationFrame.\n */\n useRequestAnimationFrame: {\n value: options.useRequestAnimationFrame || false,\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a throttle function. Default to true.\n */\n useThrottle: {\n value: options.useThrottle !== false,\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a debounce function.\n */\n useDebounce: {\n value: options.useDebounce || false,\n writable: true,\n },\n /**\n * Debug properties.\n */\n // Not used anymore, but could be useful for debugging.\n // showVehicleTraj: {\n // value:\n // options.showVehicleTraj !== undefined\n // ? options.showVehicleTraj\n // : true,\n // writable: true,\n // },\n });\n }\n attachToMap(map) {\n super.attachToMap(map);\n // If the layer is visible we start the rendering clock\n if (this.visible) {\n this.start();\n }\n // On change of visibility we start/stop the rendering clock\n this.visibilityRef = this.on('change:visible', (evt) => {\n if (evt.target.visible) {\n this.start();\n }\n else {\n this.stop();\n }\n });\n // To avoid browser hanging when the tab is not visible for a certain amount of time,\n // We stop the rendering and the websocket when hide and start again when show.\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n }\n detachFromMap() {\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n this.stop();\n unByKey(this.visibilityRef);\n if (this.canvas) {\n const context = this.canvas.getContext('2d');\n context === null || context === void 0 ? void 0 : context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n super.detachFromMap();\n }\n }\n start() {\n this.stop();\n // Before starting to update trajectories, we remove trajectories that have\n // a time_intervals in the past, it will\n // avoid phantom train that are at the end of their route because we never\n // received the deleted_vehicle event because we have changed the browser tab.\n this.purgeOutOfDateTrajectories();\n // @ts-ignore function without parameters must be define in subclasses\n this.renderTrajectories();\n this.startUpdateTime();\n this.api.open();\n this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n if (this.isUpdateBboxOnMoveEnd) {\n // Update the bbox on each move end\n // @ts-ignore function without parameters defined by subclasses\n this.setBbox();\n }\n }\n /**\n * Start the clock.\n * @private\n */\n startUpdateTime() {\n this.stopUpdateTime();\n this.updateTimeDelay = this.getRefreshTimeInMs() || 0;\n this.updateTimeInterval = window.setInterval(() => {\n // When live=true, we update the time with new Date();\n if (this.live) {\n this.time = new Date();\n }\n else if (this.time && this.updateTimeDelay && this.speed) {\n this.time = new Date(this.time.getTime() + this.updateTimeDelay * this.speed);\n }\n }, this.updateTimeDelay);\n }\n stop() {\n this.api.unsubscribeTrajectory(this.onTrajectoryMessage);\n this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);\n this.api.close();\n }\n /**\n * Stop the clock.\n * @private\n */\n stopUpdateTime() {\n if (this.updateTimeInterval) {\n clearInterval(this.updateTimeInterval);\n this.updateTimeInterval = undefined;\n }\n }\n /**\n * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method.\n *\n * @param {object} viewState The view state of the map.\n * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.\n * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.\n * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.\n * @param {number} [viewState.rotation = 0] Rotation of the map to render.\n * @param {number} viewState.resolution Resolution of the map to render.\n * @param {boolean} noInterpolate If true trajectories are not interpolated but\n * drawn at the last known coordinate. Use this for performance optimization\n * during map navigation.\n * @private\n */\n renderTrajectoriesInternal(viewState, noInterpolate = false) {\n var _a;\n if (!this.map || !this.trajectories) {\n return false;\n }\n const time = this.live ? Date.now() : (_a = this.time) === null || _a === void 0 ? void 0 : _a.getTime();\n const trajectories = Object.values(this.trajectories);\n // console.time('sort');\n if (this.sort) {\n // @ts-ignore\n trajectories.sort(this.sort);\n }\n // console.timeEnd('sort');\n if (!this.canvas || !this.style) {\n return true;\n }\n // console.time('render');\n 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\n ? true\n : noInterpolate, hoverVehicleId: this.hoverVehicleId, selectedVehicleId: this.selectedVehicleId }, this.styleOptions));\n // console.timeEnd('render');\n return true;\n }\n /**\n * Render the trajectories requesting an animation frame and cancelling the previous one.\n * This function must be overrided by children to provide the correct parameters.\n *\n * @param {object} viewState The view state of the map.\n * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.\n * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.\n * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.\n * @param {number} [viewState.rotation = 0] Rotation of the map to render.\n * @param {number} viewState.resolution Resolution of the map to render.\n * @param {boolean} noInterpolate If true trajectories are not interpolated but\n * drawn at the last known coordinate. Use this for performance optimization\n * during map navigation.\n * @private\n */\n renderTrajectories(viewState, noInterpolate) {\n if (this.requestId) {\n cancelAnimationFrame(this.requestId);\n this.requestId = undefined;\n }\n if (!viewState) {\n return;\n }\n if (!noInterpolate && this.useRequestAnimationFrame) {\n this.requestId = requestAnimationFrame(() => {\n this.renderTrajectoriesInternal(viewState, noInterpolate);\n });\n }\n else if (!noInterpolate && this.useDebounce) {\n this.debounceRenderTrajectories(viewState, noInterpolate);\n }\n else if (!noInterpolate && this.useThrottle) {\n this.throttleRenderTrajectories(viewState, noInterpolate);\n }\n else {\n this.renderTrajectoriesInternal(viewState, noInterpolate);\n }\n }\n setBbox(extent, zoom) {\n // Clean trajectories before sending the new bbox\n // Purge trajectories:\n // - which are outside the extent\n // - when it's bus and zoom level is too low for them\n if (this.trajectories && extent && zoom) {\n const keys = Object.keys(this.trajectories);\n for (let i = keys.length - 1; i >= 0; i -= 1) {\n this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom);\n }\n }\n if (!extent) {\n return;\n }\n const bbox = [...extent];\n if (this.isUpdateBboxOnMoveEnd && zoom) {\n bbox.push(zoom);\n if (this.tenant) {\n bbox.push(`tenant=${this.tenant}`);\n }\n /* @ignore */\n this.generalizationLevel = this.getGeneralizationLevelByZoom(zoom);\n if (this.generalizationLevel) {\n bbox.push(`gen=${this.generalizationLevel}`);\n }\n /* @ignore */\n this.mots = this.getMotsByZoom(zoom);\n if (this.mots) {\n bbox.push(`mots=${this.mots}`);\n }\n }\n this.api.bbox = bbox;\n }\n setMode(mode) {\n if (this.mode === mode) {\n return;\n }\n this.mode = mode;\n this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n }\n /**\n * Get the duration before the next update depending on zoom level.\n *\n * @private\n * @param {number} zoom\n */\n getRefreshTimeInMs(zoom = 0) {\n var _a;\n const roundedZoom = zoom !== undefined ? Math.round(zoom) : -1;\n const timeStep = this.getRenderTimeIntervalByZoom(roundedZoom) || 25;\n const nextTick = Math.max(25, timeStep / (this.speed || 1));\n const nextThrottleTick = Math.min(nextTick, 500);\n // TODO: see if this should go elsewhere.\n if (this.useThrottle) {\n this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true });\n }\n else if (this.useDebounce) {\n this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true, maxWait: 5000 });\n }\n if ((_a = this.api) === null || _a === void 0 ? void 0 : _a.buffer) {\n const [, size] = this.api.buffer;\n this.api.buffer = [nextThrottleTick, size];\n }\n return nextTick;\n }\n /**\n * Get vehicle.\n * @param {function} filterFc A function use to filter results.\n * @return {Array} Array of vehicle.\n */\n getVehicle(filterFc) {\n return ((this.trajectories &&\n // @ts-ignore\n Object.values(this.trajectories).filter(filterFc)) ||\n []);\n }\n /**\n * Request feature information for a given coordinate.\n *\n * @param {ol/coordinate~Coordinate} coordinate Coordinate.\n * @param {Object} options Options See child classes to see which options are supported.\n * @param {number} [options.resolution=1] The resolution of the map.\n * @param {number} [options.nb=Infinity] The max number of vehicles to return.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate, options) {\n const { resolution, nb } = options;\n const ext = buffer([...coordinate, ...coordinate], this.hitTolerance * resolution);\n let trajectories = Object.values(this.trajectories || {});\n if (this.sort) {\n // @ts-ignore\n trajectories = trajectories.sort(this.sort);\n }\n const vehicles = [];\n for (let i = 0; i < trajectories.length; i += 1) {\n if (trajectories[i].properties.coordinate &&\n containsCoordinate(ext, trajectories[i].properties.coordinate)) {\n vehicles.push(trajectories[i]);\n }\n if (vehicles.length === nb) {\n break;\n }\n }\n return Promise.resolve({\n layer: this,\n features: vehicles.map((vehicle) => this.format.readFeature(vehicle)),\n coordinate,\n });\n }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id) {\n // When a vehicle is selected, we request the complete stop sequence and the complete full trajectory.\n // Then we combine them in one response and send them to inherited layers.\n const promises = [\n this.api.getStopSequence(id),\n this.api.getFullTrajectory(id, this.mode, this.generalizationLevel),\n ];\n return Promise.all(promises).then(([stopSequence, fullTrajectory]) => {\n const response = {\n stopSequence,\n fullTrajectory,\n };\n return response;\n });\n }\n /**\n * Remove all trajectories that are in the past.\n */\n purgeOutOfDateTrajectories() {\n Object.entries(this.trajectories || {}).forEach(([key, trajectory]) => {\n var _a;\n const timeIntervals = (_a = trajectory === null || trajectory === void 0 ? void 0 : trajectory.properties) === null || _a === void 0 ? void 0 : _a.time_intervals;\n if (this.time && timeIntervals.length) {\n const lastTimeInterval = timeIntervals[timeIntervals.length - 1][0];\n if (lastTimeInterval < this.time) {\n this.removeTrajectory(key);\n }\n }\n });\n }\n /**\n * Determine if the trajectory is useless and should be removed from the list or not.\n * By default, this function exclude vehicles:\n * - that have their trajectory outside the current extent and\n * - that aren't in the MOT list.\n *\n * @param {RealtimeTrajectory} trajectory\n * @param {Array} extent\n * @param {number} zoom\n * @return {boolean} if the trajectory must be displayed or not.\n * @ignore\n */\n purgeTrajectory(trajectory, extent, zoom) {\n const { type, bounds } = trajectory.properties;\n if (!intersects(extent, bounds) ||\n (this.mots && !this.mots.includes(type))) {\n this.removeTrajectory(trajectory);\n return true;\n }\n return false;\n }\n /**\n * Add a trajectory.\n * @param {RealtimeTrajectory} trajectory The trajectory to add.\n * @private\n */\n addTrajectory(trajectory) {\n if (this.filter && !this.filter(trajectory)) {\n return;\n }\n if (!this.trajectories) {\n this.trajectories = {};\n }\n this.trajectories[trajectory.properties.train_id] = trajectory;\n // @ts-ignore the parameter are set by subclasses\n this.renderTrajectories();\n }\n removeTrajectory(trajectoryOrId) {\n var _a;\n let id;\n if (typeof trajectoryOrId !== 'string') {\n id = (_a = trajectoryOrId === null || trajectoryOrId === void 0 ? void 0 : trajectoryOrId.properties) === null || _a === void 0 ? void 0 : _a.train_id;\n }\n else {\n id = trajectoryOrId;\n }\n if (this.trajectories) {\n delete this.trajectories[id];\n }\n }\n /**\n * On zoomend we adjust the time interval of the update of vehicles positions.\n *\n * @param evt Event that triggered the function.\n * @private\n */\n onZoomEnd() {\n this.startUpdateTime();\n }\n onDocumentVisibilityChange() {\n if (!this.visible) {\n return;\n }\n if (document.hidden) {\n this.stop();\n // Since we don't receive deleted_vehicles event when docuement\n // is hidden. We have to clean all the trajectories for a fresh\n // start when the document is visible again.\n this.trajectories = {};\n }\n else {\n this.start();\n }\n }\n /**\n * Callback on websocket's trajectory channel events.\n * It adds a trajectory to the list.\n *\n * @private\n */\n onTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n const trajectory = data.content;\n const { geometry, properties: { train_id: id, time_since_update: timeSinceUpdate, raw_coordinates: rawCoordinates, }, } = trajectory;\n // ignore old events [SBAHNM-97]\n if (timeSinceUpdate < 0) {\n return;\n }\n // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);\n // @ts-ignore default value for extentand zoom are provided by subclasses\n if (this.purgeTrajectory(trajectory)) {\n return;\n }\n if (this.debug &&\n this.mode === RealtimeModes.TOPOGRAPHIC &&\n rawCoordinates) {\n trajectory.properties.olGeometry = {\n type: 'Point',\n coordinates: fromLonLat(rawCoordinates, this.map.getView().getProjection()),\n };\n }\n else {\n trajectory.properties.olGeometry = this.format.readGeometry(geometry);\n }\n // TODO Make sure the timeOffset is useful. May be we can remove it.\n trajectory.properties.timeOffset = Date.now() - data.timestamp;\n this.addTrajectory(trajectory);\n }\n /**\n * Callback on websocket's deleted_vehicles channel events.\n * It removes the trajectory from the list.\n *\n * @private\n * @override\n */\n onDeleteTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n this.removeTrajectory(data.content);\n }\n /**\n * Callback when user moves the mouse/pointer over the map.\n * It sets the layer's hoverVehicleId property with the current hovered vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.hoverVehicleId !== id) {\n /** @ignore */\n this.hoverVehicleId = id;\n // @ts-ignore\n this.renderTrajectories(true);\n }\n }\n /**\n * Callback when user clicks on the map.\n * It sets the layer's selectedVehicleId property with the current selected vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureClick(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.selectedVehicleId !== id) {\n /** @ignore */\n this.selectedVehicleId = id;\n this.selectedVehicle = feature;\n // @ts-ignore parameters are provided by subclasses\n this.renderTrajectories(true);\n }\n }\n };\n}\nexport default RealtimeLayerMixin;\n", + "content": "/* eslint-disable no-empty-function,@typescript-eslint/no-empty-function */\n/* eslint-disable no-useless-constructor,@typescript-eslint/no-useless-constructor */\n/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */\n/* eslint-disable class-methods-use-this */\n/* eslint-disable max-classes-per-file */\nimport { buffer, containsCoordinate, intersects } from 'ol/extent';\nimport { unByKey } from 'ol/Observable';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport debounce from 'lodash.debounce';\nimport throttle from 'lodash.throttle';\nimport { fromLonLat } from 'ol/proj';\nimport realtimeDefaultStyle from '../styles/realtimeDefaultStyle';\nimport { RealtimeAPI, RealtimeModes } from '../../api';\nimport renderTrajectories from '../utils/renderTrajectories';\nimport * as realtimeConfig from '../utils/realtimeConfig';\n/**\n * RealtimeLayerInterface.\n */\nexport class RealtimeLayerInterface {\n /**\n * Start the clock.\n */\n start() { }\n /**\n * Stop the clock.\n */\n stop() { }\n /**\n * Set the Realtime api's bbox.\n *\n * @param {Array} extent Extent to request, [minX, minY, maxX, maxY, zoom].\n * @param {number} zoom Zoom level to request. Must be an integer.\n */\n setBbox(extent, zoom) { }\n /**\n * Render the trajectories\n */\n renderTrajectories() { }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @param {RealtimeMode} mode The mode to request. If not defined, the layer´s mode propetrty will be used.\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id, mode) { }\n}\n/**\n * Mixin for RealtimeLayerInterface.\n *\n * @param {Class} Base A class to extend with {RealtimeLayerInterface} functionnalities.\n * @return {Class} A class that implements {RealtimeLayerInterface} class and extends Base;\n * @private\n */\nfunction RealtimeLayerMixin(Base) {\n // @ts-ignore\n return class Mixin extends Base {\n constructor(options) {\n super({\n hitTolerance: 10,\n ...options,\n });\n this.debug = options.debug || false;\n this.mode = options.mode || RealtimeModes.TOPOGRAPHIC;\n this.api = options.api || new RealtimeAPI(options);\n this.tenant = options.tenant || ''; // sbb,sbh or sbm\n this.minZoomInterpolation = options.minZoomInterpolation || 8; // Min zoom level from which trains positions are not interpolated.\n this.format = new GeoJSON();\n // MOTs by zoom\n const allMots = [\n 'tram',\n 'subway',\n 'rail',\n 'bus',\n 'ferry',\n 'cablecar',\n 'gondola',\n 'funicular',\n 'coach',\n ];\n const onlyRail = ['rail'];\n const withoutCable = ['tram', 'subway', 'rail', 'bus'];\n // Server will block non train before zoom 9\n this.motsByZoom = options.motsByZoom || [\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n onlyRail,\n withoutCable,\n withoutCable,\n allMots,\n allMots,\n allMots,\n allMots,\n ];\n this.getMotsByZoom = (zoom) => {\n return ((options.getMotsByZoom &&\n options.getMotsByZoom(zoom, this.motsByZoom)) ||\n this.motsByZoom[zoom] ||\n this.motsByZoom[this.motsByZoom.length - 1]);\n };\n // Generalization levels by zoom\n this.generalizationLevelByZoom = options.generalizationLevelByZoom || [\n 5, 5, 5, 5, 5, 5, 5, 5, 10, 30, 30, 100, 100, 100,\n ];\n this.getGeneralizationLevelByZoom = (zoom) => {\n return ((options.getGeneralizationLevelByZoom &&\n options.getGeneralizationLevelByZoom(zoom, this.generalizationLevelByZoom)) ||\n this.generalizationLevelByZoom[zoom]);\n };\n // Render time interval by zoom\n this.renderTimeIntervalByZoom = options.renderTimeIntervalByZoom || [\n 100000, 50000, 40000, 30000, 20000, 15000, 10000, 5000, 2000, 1000, 400,\n 300, 250, 180, 90, 60, 50, 50, 50, 50, 50,\n ];\n this.getRenderTimeIntervalByZoom = (zoom) => {\n return ((options.getRenderTimeIntervalByZoom &&\n options.getRenderTimeIntervalByZoom(zoom, this.renderTimeIntervalByZoom)) ||\n this.renderTimeIntervalByZoom[zoom]);\n };\n // This property will call api.setBbox on each movend event\n this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd !== false;\n // Define throttling and debounce render function\n this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, 50, { leading: false, trailing: true });\n this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, 50, { leading: true, trailing: true, maxWait: 5000 });\n // Bind callbacks\n this.onFeatureHover = this.onFeatureHover.bind(this);\n this.onFeatureClick = this.onFeatureClick.bind(this);\n this.renderTrajectoriesInternal =\n this.renderTrajectoriesInternal.bind(this);\n this.onTrajectoryMessage = this.onTrajectoryMessage.bind(this);\n this.onDeleteTrajectoryMessage =\n this.onDeleteTrajectoryMessage.bind(this);\n this.onDocumentVisibilityChange =\n this.onDocumentVisibilityChange.bind(this);\n }\n /**\n * Define layer's properties.\n *\n * @ignore\n */\n defineProperties(options) {\n const { style, speed, pixelRatio, hoverVehicleId, selectedVehicleId, filter, sort, time, live, canvas, styleOptions, mode, } = options;\n let currCanvas = canvas;\n let currSpeed = speed || 1;\n let currTime = time || new Date();\n let currMode = mode || RealtimeModes.TOPOGRAPHIC;\n let currStyle = style || realtimeDefaultStyle;\n super.defineProperties(options);\n Object.defineProperties(this, {\n isTrackerLayer: { value: true },\n canvas: {\n get: () => {\n if (!currCanvas) {\n currCanvas = document.createElement('canvas');\n }\n return currCanvas;\n },\n set: (cnvas) => {\n currCanvas = cnvas;\n },\n },\n /**\n * Style function used to render a vehicle.\n */\n mode: {\n get: () => currMode,\n set: (newMode) => {\n if (newMode === currMode) {\n return;\n }\n currMode = newMode;\n if (this.api?.wsApi?.open) {\n this.stop();\n this.start();\n }\n },\n },\n /**\n * Style function used to render a vehicle.\n */\n style: {\n get: () => currStyle,\n set: (newStyle) => {\n currStyle = newStyle;\n // @ts-ignore function without parameters is defined in subclasses\n this.renderTrajectories();\n },\n },\n /**\n * Custom options to pass as last parameter of the style function.\n */\n styleOptions: {\n value: { ...realtimeConfig, ...(styleOptions || {}) },\n },\n /**\n * Speed of the wheel of time.\n * If live property is true. The speed is ignored.\n */\n speed: {\n get: () => currSpeed,\n set: (newSpeed) => {\n currSpeed = newSpeed;\n this.start();\n },\n },\n /**\n * Function to filter which vehicles to display.\n */\n filter: {\n value: filter,\n writable: true,\n },\n /**\n * Function to sort the vehicles to display.\n */\n sort: {\n value: sort,\n writable: true,\n },\n /**\n * If true. The layer will always use Date.now() on the next tick to render the trajectories.\n * When true, setting the time property has no effect.\n */\n live: {\n value: live === false ? live : true,\n writable: true,\n },\n /**\n * Time used to display the trajectories. Can be a Date or a number in ms representing a Date.\n * If live property is true. The setter does nothing.\n */\n time: {\n get: () => currTime,\n set: (newTime) => {\n currTime = newTime && newTime.getTime ? newTime : new Date(newTime);\n // @ts-ignore function without parameters is defined in subclasses\n this.renderTrajectories();\n },\n },\n /**\n * Keep track of which trajectories are stored.\n */\n trajectories: {\n value: {},\n writable: true,\n },\n /**\n * Id of the hovered vehicle.\n */\n hoverVehicleId: {\n value: hoverVehicleId,\n writable: true,\n },\n /**\n * Id of the selected vehicle.\n */\n selectedVehicleId: {\n value: selectedVehicleId,\n writable: true,\n },\n /**\n * Id of the selected vehicle.\n */\n pixelRatio: {\n value: pixelRatio ||\n (typeof window !== 'undefined' ? window.devicePixelRatio : 1),\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a requestAnimationFrame.\n */\n useRequestAnimationFrame: {\n value: options.useRequestAnimationFrame || false,\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a throttle function. Default to true.\n */\n useThrottle: {\n value: options.useThrottle !== false,\n writable: true,\n },\n /**\n * If true, encapsulates the renderTrajectories calls in a debounce function.\n */\n useDebounce: {\n value: options.useDebounce || false,\n writable: true,\n },\n /**\n * Debug properties.\n */\n // Not used anymore, but could be useful for debugging.\n // showVehicleTraj: {\n // value:\n // options.showVehicleTraj !== undefined\n // ? options.showVehicleTraj\n // : true,\n // writable: true,\n // },\n });\n }\n attachToMap(map) {\n super.attachToMap(map);\n // If the layer is visible we start the rendering clock\n if (this.visible) {\n this.start();\n }\n // On change of visibility we start/stop the rendering clock\n this.visibilityRef = this.on('change:visible', (evt) => {\n if (evt.target.visible) {\n this.start();\n }\n else {\n this.stop();\n }\n });\n // To avoid browser hanging when the tab is not visible for a certain amount of time,\n // We stop the rendering and the websocket when hide and start again when show.\n document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);\n }\n detachFromMap() {\n document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);\n this.stop();\n unByKey(this.visibilityRef);\n if (this.canvas) {\n const context = this.canvas.getContext('2d');\n context?.clearRect(0, 0, this.canvas.width, this.canvas.height);\n super.detachFromMap();\n }\n }\n start() {\n this.stop();\n // Before starting to update trajectories, we remove trajectories that have\n // a time_intervals in the past, it will\n // avoid phantom train that are at the end of their route because we never\n // received the deleted_vehicle event because we have changed the browser tab.\n this.purgeOutOfDateTrajectories();\n // @ts-ignore function without parameters must be define in subclasses\n this.renderTrajectories();\n this.startUpdateTime();\n this.api.open();\n this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, undefined, this.isUpdateBboxOnMoveEnd);\n if (this.isUpdateBboxOnMoveEnd) {\n // Update the bbox on each move end\n // @ts-ignore function without parameters defined by subclasses\n this.setBbox();\n }\n }\n /**\n * Start the clock.\n * @private\n */\n startUpdateTime() {\n this.stopUpdateTime();\n this.updateTimeDelay = this.getRefreshTimeInMs() || 0;\n this.updateTimeInterval = window.setInterval(() => {\n // When live=true, we update the time with new Date();\n if (this.live) {\n this.time = new Date();\n }\n else if (this.time && this.updateTimeDelay && this.speed) {\n this.time = new Date(this.time.getTime() + this.updateTimeDelay * this.speed);\n }\n }, this.updateTimeDelay);\n }\n stop() {\n this.api.unsubscribeTrajectory(this.onTrajectoryMessage);\n this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);\n this.api.close();\n }\n /**\n * Stop the clock.\n * @private\n */\n stopUpdateTime() {\n if (this.updateTimeInterval) {\n clearInterval(this.updateTimeInterval);\n this.updateTimeInterval = undefined;\n }\n }\n /**\n * Launch renderTrajectories. it avoids duplicating code in renderTrajectories method.\n *\n * @param {object} viewState The view state of the map.\n * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.\n * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.\n * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.\n * @param {number} [viewState.rotation = 0] Rotation of the map to render.\n * @param {number} viewState.resolution Resolution of the map to render.\n * @param {boolean} noInterpolate If true trajectories are not interpolated but\n * drawn at the last known coordinate. Use this for performance optimization\n * during map navigation.\n * @private\n */\n renderTrajectoriesInternal(viewState, noInterpolate = false) {\n if (!this.map || !this.trajectories) {\n return false;\n }\n const time = this.live ? Date.now() : this.time?.getTime();\n const trajectories = Object.values(this.trajectories);\n // console.time('sort');\n if (this.sort) {\n // @ts-ignore\n trajectories.sort(this.sort);\n }\n // console.timeEnd('sort');\n if (!this.canvas || !this.style) {\n return true;\n }\n // console.time('render');\n this.renderState = renderTrajectories(this.canvas, trajectories, this.style, {\n ...viewState,\n pixelRatio: this.pixelRatio || 1,\n time,\n }, {\n filter: this.filter,\n noInterpolate: (viewState.zoom || 0) < this.minZoomInterpolation\n ? true\n : noInterpolate,\n hoverVehicleId: this.hoverVehicleId,\n selectedVehicleId: this.selectedVehicleId,\n ...this.styleOptions,\n });\n // console.timeEnd('render');\n return true;\n }\n /**\n * Render the trajectories requesting an animation frame and cancelling the previous one.\n * This function must be overrided by children to provide the correct parameters.\n *\n * @param {object} viewState The view state of the map.\n * @param {number[2]} viewState.center Center coordinate of the map in mercator coordinate.\n * @param {number[4]} viewState.extent Extent of the map in mercator coordinates.\n * @param {number[2]} viewState.size Size ([width, height]) of the canvas to render.\n * @param {number} [viewState.rotation = 0] Rotation of the map to render.\n * @param {number} viewState.resolution Resolution of the map to render.\n * @param {boolean} noInterpolate If true trajectories are not interpolated but\n * drawn at the last known coordinate. Use this for performance optimization\n * during map navigation.\n * @private\n */\n renderTrajectories(viewState, noInterpolate) {\n if (this.requestId) {\n cancelAnimationFrame(this.requestId);\n this.requestId = undefined;\n }\n if (!viewState) {\n return;\n }\n if (!noInterpolate && this.useRequestAnimationFrame) {\n this.requestId = requestAnimationFrame(() => {\n this.renderTrajectoriesInternal(viewState, noInterpolate);\n });\n }\n else if (!noInterpolate && this.useDebounce) {\n this.debounceRenderTrajectories(viewState, noInterpolate);\n }\n else if (!noInterpolate && this.useThrottle) {\n this.throttleRenderTrajectories(viewState, noInterpolate);\n }\n else {\n this.renderTrajectoriesInternal(viewState, noInterpolate);\n }\n }\n setBbox(extent, zoom) {\n // Clean trajectories before sending the new bbox\n // Purge trajectories:\n // - which are outside the extent\n // - when it's bus and zoom level is too low for them\n if (this.trajectories && extent && zoom) {\n const keys = Object.keys(this.trajectories);\n for (let i = keys.length - 1; i >= 0; i -= 1) {\n this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom);\n }\n }\n if (!extent) {\n return;\n }\n const bbox = [...extent];\n if (this.isUpdateBboxOnMoveEnd && zoom) {\n bbox.push(zoom);\n if (this.tenant) {\n bbox.push(`tenant=${this.tenant}`);\n }\n /* @ignore */\n this.generalizationLevel = this.getGeneralizationLevelByZoom(zoom);\n if (this.generalizationLevel) {\n bbox.push(`gen=${this.generalizationLevel}`);\n }\n /* @ignore */\n this.mots = this.getMotsByZoom(zoom);\n if (this.mots) {\n bbox.push(`mots=${this.mots}`);\n }\n }\n this.api.bbox = bbox;\n }\n /**\n * Get the duration before the next update depending on zoom level.\n *\n * @private\n * @param {number} zoom\n */\n getRefreshTimeInMs(zoom = 0) {\n const roundedZoom = zoom !== undefined ? Math.round(zoom) : -1;\n const timeStep = this.getRenderTimeIntervalByZoom(roundedZoom) || 25;\n const nextTick = Math.max(25, timeStep / (this.speed || 1));\n const nextThrottleTick = Math.min(nextTick, 500);\n // TODO: see if this should go elsewhere.\n if (this.useThrottle) {\n this.throttleRenderTrajectories = throttle(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true });\n }\n else if (this.useDebounce) {\n this.debounceRenderTrajectories = debounce(this.renderTrajectoriesInternal, nextThrottleTick, { leading: true, trailing: true, maxWait: 5000 });\n }\n if (this.api?.buffer) {\n const [, size] = this.api.buffer;\n this.api.buffer = [nextThrottleTick, size];\n }\n return nextTick;\n }\n /**\n * Get vehicle.\n * @param {function} filterFc A function use to filter results.\n * @return {Array} Array of vehicle.\n */\n getVehicle(filterFc) {\n return ((this.trajectories &&\n // @ts-ignore\n Object.values(this.trajectories).filter(filterFc)) ||\n []);\n }\n /**\n * Request feature information for a given coordinate.\n *\n * @param {ol/coordinate~Coordinate} coordinate Coordinate.\n * @param {Object} options Options See child classes to see which options are supported.\n * @param {number} [options.resolution=1] The resolution of the map.\n * @param {number} [options.nb=Infinity] The max number of vehicles to return.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate, options) {\n const { resolution, nb } = options;\n const ext = buffer([...coordinate, ...coordinate], this.hitTolerance * resolution);\n let trajectories = Object.values(this.trajectories || {});\n if (this.sort) {\n // @ts-ignore\n trajectories = trajectories.sort(this.sort);\n }\n const vehicles = [];\n for (let i = 0; i < trajectories.length; i += 1) {\n if (trajectories[i].properties.coordinate &&\n containsCoordinate(ext, trajectories[i].properties.coordinate)) {\n vehicles.push(trajectories[i]);\n }\n if (vehicles.length === nb) {\n break;\n }\n }\n return Promise.resolve({\n layer: this,\n features: vehicles.map((vehicle) => this.format.readFeature(vehicle)),\n coordinate,\n });\n }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id) {\n // When a vehicle is selected, we request the complete stop sequence and the complete full trajectory.\n // Then we combine them in one response and send them to inherited layers.\n const promises = [\n this.api.getStopSequence(id),\n this.api.getFullTrajectory(id, this.mode, this.generalizationLevel),\n ];\n return Promise.all(promises).then(([stopSequence, fullTrajectory]) => {\n const response = {\n stopSequence,\n fullTrajectory,\n };\n return response;\n });\n }\n /**\n * Remove all trajectories that are in the past.\n */\n purgeOutOfDateTrajectories() {\n Object.entries(this.trajectories || {}).forEach(([key, trajectory]) => {\n const timeIntervals = trajectory?.properties?.time_intervals;\n if (this.time && timeIntervals.length) {\n const lastTimeInterval = timeIntervals[timeIntervals.length - 1][0];\n if (lastTimeInterval < this.time) {\n this.removeTrajectory(key);\n }\n }\n });\n }\n /**\n * Determine if the trajectory is useless and should be removed from the list or not.\n * By default, this function exclude vehicles:\n * - that have their trajectory outside the current extent and\n * - that aren't in the MOT list.\n *\n * @param {RealtimeTrajectory} trajectory\n * @param {Array} extent\n * @param {number} zoom\n * @return {boolean} if the trajectory must be displayed or not.\n * @ignore\n */\n purgeTrajectory(trajectory, extent, zoom) {\n const { type, bounds } = trajectory.properties;\n if (!intersects(extent, bounds) ||\n (this.mots && !this.mots.includes(type))) {\n this.removeTrajectory(trajectory);\n return true;\n }\n return false;\n }\n /**\n * Add a trajectory.\n * @param {RealtimeTrajectory} trajectory The trajectory to add.\n * @private\n */\n addTrajectory(trajectory) {\n if (this.filter && !this.filter(trajectory)) {\n return;\n }\n if (!this.trajectories) {\n this.trajectories = {};\n }\n this.trajectories[trajectory.properties.train_id] = trajectory;\n // @ts-ignore the parameter are set by subclasses\n this.renderTrajectories();\n }\n removeTrajectory(trajectoryOrId) {\n let id;\n if (typeof trajectoryOrId !== 'string') {\n id = trajectoryOrId?.properties?.train_id;\n }\n else {\n id = trajectoryOrId;\n }\n if (this.trajectories) {\n delete this.trajectories[id];\n }\n }\n /**\n * On zoomend we adjust the time interval of the update of vehicles positions.\n *\n * @param evt Event that triggered the function.\n * @private\n */\n onZoomEnd() {\n this.startUpdateTime();\n }\n onDocumentVisibilityChange() {\n if (!this.visible) {\n return;\n }\n if (document.hidden) {\n this.stop();\n // Since we don't receive deleted_vehicles event when docuement\n // is hidden. We have to clean all the trajectories for a fresh\n // start when the document is visible again.\n this.trajectories = {};\n }\n else {\n this.start();\n }\n }\n /**\n * Callback on websocket's trajectory channel events.\n * It adds a trajectory to the list.\n *\n * @private\n */\n onTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n const trajectory = data.content;\n const { geometry, properties: { train_id: id, time_since_update: timeSinceUpdate, raw_coordinates: rawCoordinates, }, } = trajectory;\n // ignore old events [SBAHNM-97]\n if (timeSinceUpdate < 0) {\n return;\n }\n // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);\n // @ts-ignore default value for extentand zoom are provided by subclasses\n if (this.purgeTrajectory(trajectory)) {\n return;\n }\n if (this.debug &&\n this.mode === RealtimeModes.TOPOGRAPHIC &&\n rawCoordinates) {\n trajectory.properties.olGeometry = {\n type: 'Point',\n coordinates: fromLonLat(rawCoordinates, this.map.getView().getProjection()),\n };\n }\n else {\n trajectory.properties.olGeometry = this.format.readGeometry(geometry);\n }\n // TODO Make sure the timeOffset is useful. May be we can remove it.\n trajectory.properties.timeOffset = Date.now() - data.timestamp;\n this.addTrajectory(trajectory);\n }\n /**\n * Callback on websocket's deleted_vehicles channel events.\n * It removes the trajectory from the list.\n *\n * @private\n * @override\n */\n onDeleteTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n this.removeTrajectory(data.content);\n }\n /**\n * Callback when user moves the mouse/pointer over the map.\n * It sets the layer's hoverVehicleId property with the current hovered vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.hoverVehicleId !== id) {\n /** @ignore */\n this.hoverVehicleId = id;\n // @ts-ignore\n this.renderTrajectories(true);\n }\n }\n /**\n * Callback when user clicks on the map.\n * It sets the layer's selectedVehicleId property with the current selected vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureClick(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.selectedVehicleId !== id) {\n /** @ignore */\n this.selectedVehicleId = id;\n this.selectedVehicle = feature;\n // @ts-ignore parameters are provided by subclasses\n this.renderTrajectories(true);\n }\n }\n };\n}\nexport default RealtimeLayerMixin;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/mixins/RealtimeLayerMixin.js", "access": "public", @@ -5132,7 +5132,7 @@ "lineNumber": 1 }, { - "__docId__": 227, + "__docId__": 226, "kind": "class", "name": "RealtimeLayerInterface", "memberof": "build/common/mixins/RealtimeLayerMixin.js", @@ -5147,7 +5147,7 @@ "interface": false }, { - "__docId__": 228, + "__docId__": 227, "kind": "method", "name": "start", "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", @@ -5162,7 +5162,7 @@ "return": null }, { - "__docId__": 229, + "__docId__": 228, "kind": "method", "name": "stop", "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", @@ -5177,7 +5177,7 @@ "return": null }, { - "__docId__": 230, + "__docId__": 229, "kind": "method", "name": "setBbox", "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", @@ -5213,33 +5213,7 @@ "return": null }, { - "__docId__": 231, - "kind": "method", - "name": "setMode", - "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface#setMode", - "access": "public", - "description": "Set the Realtime api's mode.", - "lineNumber": 40, - "params": [ - { - "nullable": null, - "types": [ - "RealtimeMode" - ], - "spread": false, - "optional": false, - "name": "mode", - "description": "Realtime mode" - } - ], - "return": null - }, - { - "__docId__": 232, + "__docId__": 230, "kind": "method", "name": "renderTrajectories", "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", @@ -5249,12 +5223,12 @@ "longname": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface#renderTrajectories", "access": "public", "description": "Render the trajectories", - "lineNumber": 44, + "lineNumber": 38, "params": [], "return": null }, { - "__docId__": 233, + "__docId__": 231, "kind": "method", "name": "getTrajectoryInfos", "memberof": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface", @@ -5264,7 +5238,7 @@ "longname": "build/common/mixins/RealtimeLayerMixin.js~RealtimeLayerInterface#getTrajectoryInfos", "access": "public", "description": "Request the stopSequence and the fullTrajectory informations for a vehicle.", - "lineNumber": 52, + "lineNumber": 46, "params": [ { "nullable": null, @@ -5297,7 +5271,7 @@ } }, { - "__docId__": 234, + "__docId__": 232, "kind": "function", "name": "RealtimeLayerMixin", "memberof": "build/common/mixins/RealtimeLayerMixin.js", @@ -5310,7 +5284,7 @@ "importPath": "mobility-toolbox-js/build/common/mixins/RealtimeLayerMixin.js", "importStyle": "RealtimeLayerMixin", "description": "Mixin for RealtimeLayerInterface.", - "lineNumber": 61, + "lineNumber": 55, "params": [ { "nullable": null, @@ -5333,57 +5307,57 @@ } }, { - "__docId__": 235, + "__docId__": 233, "kind": "file", - "name": "build/common/mixins/TralisLayerMixin.js", - "content": "/* eslint-disable no-empty-function */\n/* eslint-disable no-useless-constructor */\n/* eslint-disable no-unused-vars */\n/* eslint-disable class-methods-use-this */\n/* eslint-disable max-classes-per-file */\nimport GeoJSON from 'ol/format/GeoJSON';\nimport { intersects } from 'ol/extent';\nimport { fromLonLat } from 'ol/proj';\nimport { TralisAPI, TralisModes } from '../../api';\n/**\n * TralisLayerInterface.\n */\nexport class TralisLayerInterface {\n /*\n * Constructor\n \n * @param {Object} options Layer options.\n * @param {string} options.url Tralis service url.\n * @param {string} options.apiKey Access key for [geOps services](https://developer.geops.io/).\n * @param {boolean} [options.debug=false] Display additional debug informations.\n * @param {TralisMode} [options.mode=TralisMode.TOPOGRAPHIC] Tralis's Mode.\n * @param {number} [options.minZoomNonTrain=9] Minimal zoom when non trains vehicles are allowed to be displayed.\n */\n constructor(options = {}) { }\n /**\n * Initialize the layer subscribing to the Tralis api.\n *\n * @param {ol/Map~Map} map\n */\n init(map) { }\n /**\n * Terminate the layer unsubscribing to the Tralis api.\n */\n terminate() { }\n /**\n * Set the Tralis api's bbox.\n *\n * @param {Array} extent Extent to request, [minX, minY, maxX, maxY, zoom].\n * @param {number} zoom Zoom level to request. Must be an integer.\n */\n setBbox(extent, zoom) { }\n /**\n * Set the Tralis api's mode.\n *\n * @param {TralisMode} mode Tralis mode\n */\n setMode(mode) { }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @param {TralisMode} mode The mode to request. If not defined, the layer´s mode propetrty will be used.\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id, mode) { }\n /**\n * Define the style of the vehicle.\n * Draw a blue circle with the id of the props parameter.\n *\n * @param {TralisTrajectory} trajectory A trajectory\n * @param {ViewState} viewState Map's view state (zoom, resolution, center, ...)\n */\n defaultStyle(trajectory, viewState) { }\n}\n/**\n * Mixin for TralisLayerInterface.\n *\n * @param {TrackerLayer} TrackerLayer A {TrackerLayer} class to extend with {TrajservLayerInterface} functionnalities.\n * @return {Class} A class that implements {TralisLayerInterface} class and extends Base;\n * @private\n */\nconst TralisLayerMixin = (TrackerLayer) => class extends TrackerLayer {\n constructor(options = {}) {\n super(Object.assign({}, options));\n this.debug = options.debug;\n this.mode = options.mode || TralisModes.TOPOGRAPHIC;\n this.api = options.api || new TralisAPI(options);\n this.tenant = options.tenant || ''; // sbb,sbh or sbm\n this.minZoomNonTrain = options.minZoomNonTrain || 9; // Min zoom level from which non trains are allowed to be displayed. Min value is 9 (as configured by the server\n this.format = new GeoJSON();\n this.generalizationLevelByZoom = options.generalizationLevelByZoom || {\n 0: 5,\n 1: 5,\n 2: 5,\n 3: 5,\n 4: 5,\n 5: 5,\n 6: 5,\n 7: 5,\n 8: 10,\n 9: 30,\n 10: 30,\n 11: 100,\n 12: 100,\n 13: 100,\n };\n // This property will call api.setBbox on each movend event\n this.isUpdateBboxOnMoveEnd = options.isUpdateBboxOnMoveEnd !== false;\n // Bind callbacks\n this.onTrajectoryMessage = this.onTrajectoryMessage.bind(this);\n this.onDeleteTrajectoryMessage =\n this.onDeleteTrajectoryMessage.bind(this);\n }\n start() {\n super.start();\n this.api.open();\n this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, this.isUpdateBboxOnMoveEnd);\n this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, this.isUpdateBboxOnMoveEnd);\n if (this.isUpdateBboxOnMoveEnd) {\n // Update the bbox on each move end\n this.setBbox();\n }\n }\n stop() {\n super.stop();\n this.api.unsubscribeTrajectory(this.onTrajectoryMessage);\n this.api.unsubscribeDeletedVehicles(this.onDeleteTrajectoryMessage);\n this.api.close();\n }\n setBbox(extent, zoom) {\n // Clean trajectories before sending the new bbox\n // Purge trajectories:\n // - which are outside the extent\n // - when it's bus and zoom level is too low for them\n const keys = Object.keys(this.trajectories);\n for (let i = keys.length - 1; i >= 0; i -= 1) {\n this.purgeTrajectory(this.trajectories[keys[i]], extent, zoom);\n }\n const bbox = [...extent];\n if (this.isUpdateBboxOnMoveEnd) {\n bbox.push(zoom);\n if (this.tenant) {\n bbox.push(`tenant=${this.tenant}`);\n }\n /* @ignore */\n this.generalizationLevel = this.generalizationLevelByZoom[zoom];\n if (this.generalizationLevel) {\n bbox.push(`gen=${this.generalizationLevel}`);\n }\n }\n this.api.bbox = bbox;\n }\n setMode(mode) {\n if (this.mode === mode) {\n return;\n }\n this.mode = mode;\n this.api.subscribeTrajectory(this.mode, this.onTrajectoryMessage, this.isUpdateBboxOnMoveEnd);\n this.api.subscribeDeletedVehicles(this.mode, this.onDeleteTrajectoryMessage, this.isUpdateBboxOnMoveEnd);\n }\n /**\n * Request the stopSequence and the fullTrajectory informations for a vehicle.\n *\n * @param {string} id The vehicle identifier (the train_id property).\n * @return {Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>} A promise that will be resolved with the trajectory informations.\n */\n getTrajectoryInfos(id) {\n // When a vehicle is selected, we request the complete stop sequence and the complete full trajectory.\n // Then we combine them in one response and send them to inherited layers.\n const promises = [\n this.api.getStopSequence(id, this.mode),\n this.api.getFullTrajectory(id, this.mode, this.generalizationLevel),\n ];\n return Promise.all(promises).then(([stopSequence, fullTrajectory]) => {\n const response = {\n stopSequence,\n fullTrajectory,\n };\n return response;\n });\n }\n /**\n * Determine if the trajectory is useless and should be removed from the list or not.\n * By default, this function exclude vehicles:\n * - that have their trajectory outside the current extent and\n * - that are not a train and zoom level is lower than layer's minZoomNonTrain property.\n *\n * @param {TralisTrajectory} trajectory\n * @param {Array} extent\n * @param {number} zoom\n * @return {boolean} if the trajectory must be displayed or not.\n * @ignore\n */\n purgeTrajectory(trajectory, extent, zoom) {\n const { type, bounds, train_id: id } = trajectory.properties;\n if (!intersects(extent, bounds) ||\n (type !== 'rail' && zoom < (this.minZoomNonTrain || 9))) {\n this.removeTrajectory(id);\n return true;\n }\n return false;\n }\n /**\n * Add a trajectory to the tracker.\n * @param {TralisTrajectory} trajectory The trajectory to add.\n * @private\n */\n addTrajectory(trajectory) {\n if (this.filter && !this.filter(trajectory)) {\n return;\n }\n this.trajectories[trajectory.properties.train_id] = trajectory;\n if (this.worker) {\n this.worker.postMessage({\n action: 'addTrajectory',\n trajectory,\n });\n }\n this.renderTrajectories();\n }\n removeTrajectory(id) {\n delete this.trajectories[id];\n if (this.worker) {\n this.worker.postMessage({\n action: 'removeTrajectory',\n trajectoryId: id,\n });\n }\n this.renderTrajectories();\n }\n // getRefreshTimeInMs() {\n // return 5000;\n // }\n /**\n * Callback on websocket's trajectory channel events.\n * It adds a trajectory to the list.\n *\n * @private\n */\n onTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n const trajectory = data.content;\n const { geometry, properties: { train_id: id, time_since_update: timeSinceUpdate, raw_coordinates: rawCoordinates, }, } = trajectory;\n // ignore old events [SBAHNM-97]\n if (timeSinceUpdate < 0) {\n return;\n }\n // console.time(`onTrajectoryMessage${data.content.properties.train_id}`);\n if (this.purgeTrajectory(trajectory)) {\n return;\n }\n if (this.debug &&\n this.mode === TralisModes.TOPOGRAPHIC &&\n rawCoordinates) {\n trajectory.properties.olGeometry = {\n type: 'Point',\n coordinates: fromLonLat(rawCoordinates, this.map.getView().getProjection()),\n };\n }\n else if (!this.worker) {\n // We can't pass the olGeometry to the worker because it's not serializable.\n trajectory.properties.olGeometry = this.format.readGeometry(geometry);\n }\n // TODO Make sure the timeOffset is useful. May be we can remove it.\n trajectory.properties.timeOffset = Date.now() - data.timestamp;\n this.addTrajectory(trajectory);\n }\n /**\n * Callback on websocket's deleted_vehicles channel events.\n * It removes the trajectory from the list.\n *\n * @private\n * @override\n */\n onDeleteTrajectoryMessage(data) {\n if (!data.content) {\n return;\n }\n this.removeTrajectory(data.content);\n }\n /**\n * Callback when user moves the mouse/pointer over the map.\n * It sets the layer's hoverVehicleId property with the current hovered vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.hoverVehicleId !== id) {\n /** @ignore */\n this.hoverVehicleId = id;\n this.renderTrajectories(true);\n }\n super.onFeatureHover(features, layer, coordinate);\n }\n /**\n * Callback when user clicks on the map.\n * It sets the layer's selectedVehicleId property with the current selected vehicle's id.\n *\n * @private\n * @override\n */\n onFeatureClick(features, layer, coordinate) {\n const [feature] = features;\n let id = null;\n if (feature) {\n id = feature.get('train_id');\n }\n if (this.selectedVehicleId !== id) {\n /** @ignore */\n this.selectedVehicleId = id;\n this.selectedVehicle = feature;\n this.renderTrajectories(true);\n }\n super.onFeatureClick(features, layer, coordinate);\n }\n};\nexport default TralisLayerMixin;\n", + "name": "build/common/mixins/UserInteractionsLayerMixin.js", + "content": "/* eslint-disable no-empty-function,@typescript-eslint/no-empty-function */\n/* eslint-disable no-useless-constructor,@typescript-eslint/no-useless-constructor */\n/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */\n/* eslint-disable class-methods-use-this */\n/* eslint-disable max-classes-per-file */\nimport { fromLonLat } from 'ol/proj';\nimport { unByKey } from 'ol/Observable';\nimport BaseEvent from 'ol/events/Event';\n/**\n * UserInteractionsLayerInterface.\n */\nexport class UserInteractionsLayerInterface {\n /*\n * Constructor\n \n * @param {Object} options Layer options.\n * @param {string} options.userInteractions If true, it listens for user mouse hover and click event.\n * @param {string} options.userClickInteractions If true, it listens for user click event.\n * @param {string} options.userHoverInteractions If true, it listens for user mouse over event.\n * @param {string} options.defaultUserInteractions If true, it adds default listeners for user mouse hover and click event.\n */\n constructor(options = {}) { }\n /**\n * Initialize the layer adding user interactions.\n *\n * @param {ol/Map~Map} map\n */\n attachToMap(map) { }\n /**\n * Terminate the layer unsubscribing user interactions.\n */\n detachFromMap() { }\n /**\n * Activate map listeners events.\n */\n activateUserInteractions() { }\n /**\n * Deactivate map listeners events.\n */\n deactivateUserInteractions() { }\n /**\n * Subscribe on user:click event.\n */\n onClick(callback) { }\n /**\n * Subscribe on user:hover event.\n */\n onHover(callback) { }\n /**\n * Unsubscribe on user:click event.\n */\n unClick(callback) { }\n /**\n * Unsubscribe on user:hover event.\n */\n unHover(callback) { }\n}\n/**\n * Mixin for UserInteractionsLayerInterface. It provide onClick and onHover functions.\n *\n * @param {Class} Base A class to extend with {UserInteractionsLayerInterface} functionnalities.\n * @return {Class} A class that implements {UserInteractionsLayerInterface} class and extends Base;\n * @private\n */\nfunction UserInteractionsLayerMixin(Base) {\n // @ts-ignore\n return class extends Base {\n constructor(options = {}) {\n super(options);\n const { userInteractions = true, userClickInteractions = true, userHoverInteractions = true, defaultUserInteractions = true, } = options;\n this.userInteractions = userInteractions;\n this.userClickInteractions = userClickInteractions;\n this.userHoverInteractions = userHoverInteractions;\n this.defaultUserInteractions = defaultUserInteractions;\n this.userClickCallbacks = [];\n this.userHoverCallbacks = [];\n this.userClickEventsKeys = [];\n this.userHoverEventsKeys = [];\n this.onUserClickCallback = this.onUserClickCallback.bind(this);\n this.onUserMoveCallback = this.onUserMoveCallback.bind(this);\n // Add mouse event callbacks\n const { onClick, onHover } = options;\n if (this.userInteractions && this.userClickInteractions && onClick) {\n this.onClick(onClick);\n }\n if (this.userInteractions && this.userHoverInteractions && onHover) {\n this.onHover(onHover);\n }\n }\n attachToMap(map) {\n super.attachToMap(map);\n if (this.userInteractions &&\n this.defaultUserInteractions &&\n this.userClickInteractions &&\n this.onFeatureClick) {\n this.onClick(this.onFeatureClick);\n }\n if (this.userInteractions &&\n this.defaultUserInteractions &&\n this.userHoverInteractions &&\n this.onFeatureHover) {\n this.onHover(this.onFeatureHover);\n }\n this.listenEvents();\n }\n detachFromMap() {\n this.unlistenEvents();\n super.detachFromMap();\n }\n listenEvents() {\n this.unlistenEvents();\n this.userClickCallbacks.forEach((callback) => {\n this.userClickEventsKeys.push(this.on(\n // @ts-ignore\n 'user:click', ({ target: { features, layer, coordinate, event }, }) => {\n callback(features, layer, coordinate, event);\n }));\n });\n this.userHoverCallbacks.forEach((callback) => {\n this.userHoverEventsKeys.push(this.on(\n // @ts-ignore\n 'user:hover', ({ target: { features, layer, coordinate, event }, }) => {\n callback(features, layer, coordinate, event);\n }));\n });\n }\n unlistenEvents() {\n unByKey(this.userClickEventsKeys);\n unByKey(this.userHoverEventsKeys);\n this.userClickEventsKeys = [];\n this.userHoverEventsKeys = [];\n }\n /**\n * Listens to click events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features,\n * the layer instance and the click event.\n */\n onClick(callback) {\n this.userClickCallbacks.push(callback);\n this.activateUserInteractions();\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Listens to hover events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features, the layer instance and the click event.\n */\n onHover(callback) {\n this.userHoverCallbacks.push(callback);\n this.activateUserInteractions();\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Unlistens to click events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features,\n * the layer instance and the click event.\n */\n unClick(callback) {\n const index = this.userClickCallbacks.indexOf(callback);\n if (index !== -1) {\n return;\n }\n this.userClickCallbacks = this.userClickCallbacks.slice(index, 1);\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Unlistens to hover events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features, the layer instance and the click event.\n */\n unHover(callback) {\n const index = this.userHoverCallbacks.indexOf(callback);\n if (index !== -1) {\n return;\n }\n this.userHoverCallbacks = this.userHoverCallbacks.slice(index, 1);\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Function triggered when the user click the map.\n * @private\n */\n onUserClickCallback(evt) {\n const coordinate = evt.coordinate ||\n fromLonLat(evt.lngLat.toArray());\n const emptyFeatureInfo = {\n features: [],\n layer: this,\n coordinate,\n event: evt,\n };\n return this.getFeatureInfoAtCoordinate(coordinate)\n .then((featureInfo) => {\n const event = new BaseEvent('user:click');\n event.target = featureInfo;\n this.dispatchEvent(event);\n return featureInfo;\n })\n .catch(() => emptyFeatureInfo);\n }\n /**\n * Function triggered when the user move the cursor.\n * @private\n */\n onUserMoveCallback(evt) {\n const coordinate = evt.coordinate ||\n fromLonLat(evt.lngLat.toArray());\n const emptyFeatureInfo = {\n features: [],\n layer: this,\n coordinate,\n event: evt,\n };\n return this.getFeatureInfoAtCoordinate(coordinate)\n .then((featureInfo) => {\n const event = new BaseEvent('user:hover');\n event.target = featureInfo;\n this.dispatchEvent(event);\n return featureInfo;\n })\n .catch(() => emptyFeatureInfo);\n }\n activateUserInteractions() { }\n deactivateUserInteractions() { }\n };\n}\nexport default UserInteractionsLayerMixin;\n", "static": true, - "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/mixins/TralisLayerMixin.js", + "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/mixins/UserInteractionsLayerMixin.js", "access": "public", "description": null, "lineNumber": 1 }, { - "__docId__": 236, + "__docId__": 234, "kind": "class", - "name": "TralisLayerInterface", - "memberof": "build/common/mixins/TralisLayerMixin.js", + "name": "UserInteractionsLayerInterface", + "memberof": "build/common/mixins/UserInteractionsLayerMixin.js", "static": true, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", + "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", "access": "public", "export": true, - "importPath": "mobility-toolbox-js/build/common/mixins/TralisLayerMixin.js", - "importStyle": "{TralisLayerInterface}", - "description": "TralisLayerInterface.", - "lineNumber": 13, + "importPath": "mobility-toolbox-js/build/common/mixins/UserInteractionsLayerMixin.js", + "importStyle": "{UserInteractionsLayerInterface}", + "description": "UserInteractionsLayerInterface.", + "lineNumber": 12, "interface": false }, { - "__docId__": 237, + "__docId__": 235, "kind": "constructor", "name": "constructor", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", + "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", "generator": false, "async": false, "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#constructor", + "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#constructor", "access": "public", "description": null, - "lineNumber": 24, + "lineNumber": 22, "undocument": true }, { - "__docId__": 238, + "__docId__": 236, "kind": "method", - "name": "init", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", + "name": "attachToMap", + "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", "generator": false, "async": false, "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#init", + "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#attachToMap", "access": "public", - "description": "Initialize the layer subscribing to the Tralis api.", - "lineNumber": 30, + "description": "Initialize the layer adding user interactions.", + "lineNumber": 28, "params": [ { "nullable": null, @@ -5399,287 +5373,29 @@ "return": null }, { - "__docId__": 239, + "__docId__": 237, "kind": "method", - "name": "terminate", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", + "name": "detachFromMap", + "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", "generator": false, "async": false, "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#terminate", + "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#detachFromMap", "access": "public", - "description": "Terminate the layer unsubscribing to the Tralis api.", - "lineNumber": 34, + "description": "Terminate the layer unsubscribing user interactions.", + "lineNumber": 32, "params": [], "return": null }, { - "__docId__": 240, + "__docId__": 238, "kind": "method", - "name": "setBbox", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", + "name": "activateUserInteractions", + "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", "generator": false, "async": false, "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#setBbox", - "access": "public", - "description": "Set the Tralis api's bbox.", - "lineNumber": 41, - "params": [ - { - "nullable": null, - "types": [ - "Array" - ], - "spread": false, - "optional": false, - "name": "extent", - "description": "Extent to request, [minX, minY, maxX, maxY, zoom]." - }, - { - "nullable": null, - "types": [ - "number" - ], - "spread": false, - "optional": false, - "name": "zoom", - "description": "Zoom level to request. Must be an integer." - } - ], - "return": null - }, - { - "__docId__": 241, - "kind": "method", - "name": "setMode", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#setMode", - "access": "public", - "description": "Set the Tralis api's mode.", - "lineNumber": 47, - "params": [ - { - "nullable": null, - "types": [ - "TralisMode" - ], - "spread": false, - "optional": false, - "name": "mode", - "description": "Tralis mode" - } - ], - "return": null - }, - { - "__docId__": 242, - "kind": "method", - "name": "getTrajectoryInfos", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#getTrajectoryInfos", - "access": "public", - "description": "Request the stopSequence and the fullTrajectory informations for a vehicle.", - "lineNumber": 55, - "params": [ - { - "nullable": null, - "types": [ - "string" - ], - "spread": false, - "optional": false, - "name": "id", - "description": "The vehicle identifier (the train_id property)." - }, - { - "nullable": null, - "types": [ - "TralisMode" - ], - "spread": false, - "optional": false, - "name": "mode", - "description": "The mode to request. If not defined, the layer´s mode propetrty will be used." - } - ], - "return": { - "nullable": null, - "types": [ - "Promise<{stopSequence: StopSequence, fullTrajectory: FullTrajectory>" - ], - "spread": false, - "description": "A promise that will be resolved with the trajectory informations." - } - }, - { - "__docId__": 243, - "kind": "method", - "name": "defaultStyle", - "memberof": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerInterface#defaultStyle", - "access": "public", - "description": "Define the style of the vehicle.\nDraw a blue circle with the id of the props parameter.", - "lineNumber": 63, - "params": [ - { - "nullable": null, - "types": [ - "TralisTrajectory" - ], - "spread": false, - "optional": false, - "name": "trajectory", - "description": "A trajectory" - }, - { - "nullable": null, - "types": [ - "ViewState" - ], - "spread": false, - "optional": false, - "name": "viewState", - "description": "Map's view state (zoom, resolution, center, ...)" - } - ], - "return": null - }, - { - "__docId__": 244, - "kind": "function", - "name": "TralisLayerMixin", - "memberof": "build/common/mixins/TralisLayerMixin.js", - "generator": false, - "async": false, - "static": true, - "longname": "build/common/mixins/TralisLayerMixin.js~TralisLayerMixin", - "access": "private", - "export": true, - "importPath": "mobility-toolbox-js/build/common/mixins/TralisLayerMixin.js", - "importStyle": "TralisLayerMixin", - "description": "Mixin for TralisLayerInterface.", - "lineNumber": 72, - "params": [ - { - "nullable": null, - "types": [ - "TrackerLayer" - ], - "spread": false, - "optional": false, - "name": "TrackerLayer", - "description": "A {TrackerLayer} class to extend with {TrajservLayerInterface} functionnalities." - } - ], - "return": { - "nullable": null, - "types": [ - "Class" - ], - "spread": false, - "description": "A class that implements {TralisLayerInterface} class and extends Base;" - } - }, - { - "__docId__": 245, - "kind": "file", - "name": "build/common/mixins/UserInteractionsLayerMixin.js", - "content": "/* eslint-disable no-empty-function,@typescript-eslint/no-empty-function */\n/* eslint-disable no-useless-constructor,@typescript-eslint/no-useless-constructor */\n/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */\n/* eslint-disable class-methods-use-this */\n/* eslint-disable max-classes-per-file */\nimport { fromLonLat } from 'ol/proj';\nimport { unByKey } from 'ol/Observable';\nimport BaseEvent from 'ol/events/Event';\n/**\n * UserInteractionsLayerInterface.\n */\nexport class UserInteractionsLayerInterface {\n /*\n * Constructor\n \n * @param {Object} options Layer options.\n * @param {string} options.userInteractions If true, it listens for user mouse hover and click event.\n * @param {string} options.userClickInteractions If true, it listens for user click event.\n * @param {string} options.userHoverInteractions If true, it listens for user mouse over event.\n * @param {string} options.defaultUserInteractions If true, it adds default listeners for user mouse hover and click event.\n */\n constructor(options = {}) { }\n /**\n * Initialize the layer adding user interactions.\n *\n * @param {ol/Map~Map} map\n */\n attachToMap(map) { }\n /**\n * Terminate the layer unsubscribing user interactions.\n */\n detachFromMap() { }\n /**\n * Activate map listeners events.\n */\n activateUserInteractions() { }\n /**\n * Deactivate map listeners events.\n */\n deactivateUserInteractions() { }\n /**\n * Subscribe on user:click event.\n */\n onClick(callback) { }\n /**\n * Subscribe on user:hover event.\n */\n onHover(callback) { }\n /**\n * Unsubscribe on user:click event.\n */\n unClick(callback) { }\n /**\n * Unsubscribe on user:hover event.\n */\n unHover(callback) { }\n}\n/**\n * Mixin for UserInteractionsLayerInterface. It provide onClick and onHover functions.\n *\n * @param {Class} Base A class to extend with {UserInteractionsLayerInterface} functionnalities.\n * @return {Class} A class that implements {UserInteractionsLayerInterface} class and extends Base;\n * @private\n */\nfunction UserInteractionsLayerMixin(Base) {\n // @ts-ignore\n return class extends Base {\n constructor(options = {}) {\n super(options);\n const { userInteractions = true, userClickInteractions = true, userHoverInteractions = true, defaultUserInteractions = true, } = options;\n this.userInteractions = userInteractions;\n this.userClickInteractions = userClickInteractions;\n this.userHoverInteractions = userHoverInteractions;\n this.defaultUserInteractions = defaultUserInteractions;\n this.userClickCallbacks = [];\n this.userHoverCallbacks = [];\n this.userClickEventsKeys = [];\n this.userHoverEventsKeys = [];\n this.onUserClickCallback = this.onUserClickCallback.bind(this);\n this.onUserMoveCallback = this.onUserMoveCallback.bind(this);\n // Add mouse event callbacks\n const { onClick, onHover } = options;\n if (this.userInteractions && this.userClickInteractions && onClick) {\n this.onClick(onClick);\n }\n if (this.userInteractions && this.userHoverInteractions && onHover) {\n this.onHover(onHover);\n }\n }\n attachToMap(map) {\n super.attachToMap(map);\n if (this.userInteractions &&\n this.defaultUserInteractions &&\n this.userClickInteractions &&\n this.onFeatureClick) {\n this.onClick(this.onFeatureClick);\n }\n if (this.userInteractions &&\n this.defaultUserInteractions &&\n this.userHoverInteractions &&\n this.onFeatureHover) {\n this.onHover(this.onFeatureHover);\n }\n this.listenEvents();\n }\n detachFromMap() {\n this.unlistenEvents();\n super.detachFromMap();\n }\n listenEvents() {\n this.unlistenEvents();\n this.userClickCallbacks.forEach((callback) => {\n this.userClickEventsKeys.push(this.on(\n // @ts-ignore\n 'user:click', ({ target: { features, layer, coordinate, event }, }) => {\n callback(features, layer, coordinate, event);\n }));\n });\n this.userHoverCallbacks.forEach((callback) => {\n this.userHoverEventsKeys.push(this.on(\n // @ts-ignore\n 'user:hover', ({ target: { features, layer, coordinate, event }, }) => {\n callback(features, layer, coordinate, event);\n }));\n });\n }\n unlistenEvents() {\n unByKey(this.userClickEventsKeys);\n unByKey(this.userHoverEventsKeys);\n this.userClickEventsKeys = [];\n this.userHoverEventsKeys = [];\n }\n /**\n * Listens to click events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features,\n * the layer instance and the click event.\n */\n onClick(callback) {\n this.userClickCallbacks.push(callback);\n this.activateUserInteractions();\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Listens to hover events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features, the layer instance and the click event.\n */\n onHover(callback) {\n this.userHoverCallbacks.push(callback);\n this.activateUserInteractions();\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Unlistens to click events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features,\n * the layer instance and the click event.\n */\n unClick(callback) {\n const index = this.userClickCallbacks.indexOf(callback);\n if (index !== -1) {\n return;\n }\n this.userClickCallbacks = this.userClickCallbacks.slice(index, 1);\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Unlistens to hover events on the layer.\n * @param {function} callback Callback function, called with the clicked\n * features, the layer instance and the click event.\n */\n unHover(callback) {\n const index = this.userHoverCallbacks.indexOf(callback);\n if (index !== -1) {\n return;\n }\n this.userHoverCallbacks = this.userHoverCallbacks.slice(index, 1);\n if (this.map) {\n // If the layer is already attached to the map we reload the events\n this.listenEvents();\n }\n }\n /**\n * Function triggered when the user click the map.\n * @private\n */\n onUserClickCallback(evt) {\n const coordinate = evt.coordinate ||\n fromLonLat(evt.lngLat.toArray());\n const emptyFeatureInfo = {\n features: [],\n layer: this,\n coordinate,\n event: evt,\n };\n return this.getFeatureInfoAtCoordinate(coordinate)\n .then((featureInfo) => {\n const event = new BaseEvent('user:click');\n event.target = featureInfo;\n this.dispatchEvent(event);\n return featureInfo;\n })\n .catch(() => emptyFeatureInfo);\n }\n /**\n * Function triggered when the user move the cursor.\n * @private\n */\n onUserMoveCallback(evt) {\n const coordinate = evt.coordinate ||\n fromLonLat(evt.lngLat.toArray());\n const emptyFeatureInfo = {\n features: [],\n layer: this,\n coordinate,\n event: evt,\n };\n return this.getFeatureInfoAtCoordinate(coordinate)\n .then((featureInfo) => {\n const event = new BaseEvent('user:hover');\n event.target = featureInfo;\n this.dispatchEvent(event);\n return featureInfo;\n })\n .catch(() => emptyFeatureInfo);\n }\n activateUserInteractions() { }\n deactivateUserInteractions() { }\n };\n}\nexport default UserInteractionsLayerMixin;\n", - "static": true, - "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/mixins/UserInteractionsLayerMixin.js", - "access": "public", - "description": null, - "lineNumber": 1 - }, - { - "__docId__": 246, - "kind": "class", - "name": "UserInteractionsLayerInterface", - "memberof": "build/common/mixins/UserInteractionsLayerMixin.js", - "static": true, - "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", - "access": "public", - "export": true, - "importPath": "mobility-toolbox-js/build/common/mixins/UserInteractionsLayerMixin.js", - "importStyle": "{UserInteractionsLayerInterface}", - "description": "UserInteractionsLayerInterface.", - "lineNumber": 12, - "interface": false - }, - { - "__docId__": 247, - "kind": "constructor", - "name": "constructor", - "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#constructor", - "access": "public", - "description": null, - "lineNumber": 22, - "undocument": true - }, - { - "__docId__": 248, - "kind": "method", - "name": "attachToMap", - "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#attachToMap", - "access": "public", - "description": "Initialize the layer adding user interactions.", - "lineNumber": 28, - "params": [ - { - "nullable": null, - "types": [ - "ol/Map~Map" - ], - "spread": false, - "optional": false, - "name": "map", - "description": "" - } - ], - "return": null - }, - { - "__docId__": 249, - "kind": "method", - "name": "detachFromMap", - "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#detachFromMap", - "access": "public", - "description": "Terminate the layer unsubscribing user interactions.", - "lineNumber": 32, - "params": [], - "return": null - }, - { - "__docId__": 250, - "kind": "method", - "name": "activateUserInteractions", - "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", - "generator": false, - "async": false, - "static": false, - "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#activateUserInteractions", + "longname": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface#activateUserInteractions", "access": "public", "description": "Activate map listeners events.", "lineNumber": 36, @@ -5687,7 +5403,7 @@ "return": null }, { - "__docId__": 251, + "__docId__": 239, "kind": "method", "name": "deactivateUserInteractions", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", @@ -5702,7 +5418,7 @@ "return": null }, { - "__docId__": 252, + "__docId__": 240, "kind": "method", "name": "onClick", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", @@ -5724,7 +5440,7 @@ "return": null }, { - "__docId__": 253, + "__docId__": 241, "kind": "method", "name": "onHover", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", @@ -5746,7 +5462,7 @@ "return": null }, { - "__docId__": 254, + "__docId__": 242, "kind": "method", "name": "unClick", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", @@ -5768,7 +5484,7 @@ "return": null }, { - "__docId__": 255, + "__docId__": 243, "kind": "method", "name": "unHover", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js~UserInteractionsLayerInterface", @@ -5790,7 +5506,7 @@ "return": null }, { - "__docId__": 256, + "__docId__": 244, "kind": "function", "name": "UserInteractionsLayerMixin", "memberof": "build/common/mixins/UserInteractionsLayerMixin.js", @@ -5826,7 +5542,7 @@ } }, { - "__docId__": 257, + "__docId__": 245, "kind": "file", "name": "build/common/styles/index.js", "content": "export { default as realtimeDefaultStyle } from './realtimeDefaultStyle';\nexport { default as realtimeDelayStyle } from './realtimeDelayStyle';\nexport { default as realtimeSimpleStyle } from './realtimeSimpleStyle';\nexport * from './realtimeDefaultStyle';\n", @@ -5837,10 +5553,10 @@ "lineNumber": 1 }, { - "__docId__": 258, + "__docId__": 246, "kind": "file", "name": "build/common/styles/realtimeDefaultStyle.js", - "content": "import createCanvas from '../utils/createCanvas';\n/** @private */\nconst cacheDelayBg = {};\n/**\n * Draw circle delay background\n *\n * @private\n */\nexport const getDelayBgCanvas = (origin, radius, color) => {\n const key = `${origin}, ${radius}, ${color}`;\n if (!cacheDelayBg[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.beginPath();\n ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);\n ctx.fillStyle = color;\n ctx.filter = 'blur(1px)';\n ctx.fill();\n cacheDelayBg[key] = canvas;\n }\n }\n return cacheDelayBg[key];\n};\n/** @private */\nconst cacheDelayText = {};\n/**\n * Draw delay text\n *\n * @private\n */\nexport const getDelayTextCanvas = (text, fontSize, font, delayColor, delayOutlineColor = '#000', pixelRatio = 1) => {\n const key = `${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`;\n if (!cacheDelayText[key]) {\n const canvas = createCanvas(Math.ceil(text.length * fontSize), Math.ceil(fontSize + 8 * pixelRatio));\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.font = font;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.font = font;\n ctx.fillStyle = delayColor;\n ctx.strokeStyle = delayOutlineColor;\n ctx.lineWidth = 1.5 * pixelRatio;\n ctx.strokeText(text, 0, fontSize);\n ctx.fillText(text, 0, fontSize);\n cacheDelayText[key] = canvas;\n }\n }\n return cacheDelayText[key];\n};\n/** @private */\nconst cacheCircle = {};\n/**\n * Draw colored circle with black border\n *\n * @private\n */\nexport const getCircleCanvas = (origin, radius, color, hasStroke, hasDash, pixelRatio) => {\n const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`;\n if (!cacheCircle[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.fillStyle = color;\n if (hasStroke) {\n ctx.lineWidth = 1 * pixelRatio;\n ctx.strokeStyle = '#000000';\n }\n ctx.beginPath();\n ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);\n ctx.fill();\n if (hasDash) {\n ctx.setLineDash([5, 3]);\n }\n if (hasStroke) {\n ctx.stroke();\n }\n cacheCircle[key] = canvas;\n }\n }\n return cacheCircle[key];\n};\n/** @private */\nconst cacheText = {};\n/**\n * Draw text in the circle\n *\n * @private\n */\nexport const getTextCanvas = (text, origin, textSize, fillColor, strokeColor, hasStroke, pixelRatio) => {\n const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`;\n if (!cacheText[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n // Draw a stroke to the text only if a provider provides realtime but we don't use it.\n if (hasStroke) {\n ctx.save();\n ctx.textBaseline = 'middle';\n ctx.textAlign = 'center';\n ctx.font = `bold ${textSize + 2}px Arial`;\n ctx.strokeStyle = strokeColor;\n ctx.strokeText(text, origin, origin);\n ctx.restore();\n }\n // Draw a text\n ctx.textBaseline = 'middle';\n ctx.textAlign = 'center';\n ctx.fillStyle = fillColor;\n ctx.font = `bold ${textSize}px Arial`;\n ctx.strokeStyle = strokeColor;\n ctx.strokeText(text, origin, origin);\n ctx.fillText(text, origin, origin);\n cacheText[key] = canvas;\n }\n }\n return cacheText[key];\n};\n/** @private */\nconst cache = {};\n/**\n * A tracker style that take in account the delay.\n *\n * @param {RealtimeTrajectory} trajectory The trajectory to render.\n * @param {ViewState} viewState The view state of the map.\n * @param {RealtimeStyleOptions} options Some options to change the rendering\n * @return a canvas\n */\nconst realtimeDefaultStyle = (trajectory, viewState, options) => {\n const { hoverVehicleId, selectedVehicleId, useDelayStyle, delayOutlineColor = '#000', delayDisplay = 300000, getRadius = () => 0, getBgColor = () => '#000', getDelayColor = () => '#000', getDelayText = () => null, getTextColor = () => '#000', getTextSize = () => 0, getMaxRadiusForText = () => 10, getMaxRadiusForStrokeAndDelay = () => 7, } = options;\n const { zoom, pixelRatio = 1 } = viewState;\n let { type } = trajectory.properties;\n const { train_id: id, line, delay, state, operator_provides_realtime_journey: operatorProvidesRealtime, } = trajectory.properties;\n let { name, text_color: textColor, color } = line || {};\n const cancelled = state === 'JOURNEY_CANCELLED';\n if (!type) {\n type = 'Rail';\n }\n if (!name) {\n name = 'I';\n }\n if (!textColor) {\n textColor = '#000000';\n }\n if (color && color[0] !== '#') {\n color = `#${color}`;\n }\n if (textColor[0] !== '#') {\n textColor = `#${textColor}`;\n }\n const z = Math.min(Math.floor(zoom || 1), 16);\n const hover = !!(hoverVehicleId && hoverVehicleId === id);\n const selected = !!(selectedVehicleId && selectedVehicleId === id);\n // Calcul the radius of the circle\n let radius = getRadius(type, z) * pixelRatio;\n const isDisplayStrokeAndDelay = radius >= getMaxRadiusForStrokeAndDelay() * pixelRatio;\n if (hover || selected) {\n radius = isDisplayStrokeAndDelay\n ? radius + 5 * pixelRatio\n : 14 * pixelRatio;\n }\n const isDisplayText = radius > getMaxRadiusForText() * pixelRatio;\n // Optimize the cache key, very important in high zoom level\n let key = `${radius}${hover}${selected}${cancelled}${delay}`;\n if (useDelayStyle) {\n key += `${operatorProvidesRealtime}`;\n }\n else {\n key += `${type}${color}`;\n }\n if (isDisplayText) {\n key += `${name}${textColor}`;\n }\n if (!cache[key]) {\n if (radius === 0) {\n return null;\n }\n const margin = 1 * pixelRatio;\n const radiusDelay = radius + 2;\n const markerSize = radius * 2;\n const size = radiusDelay * 2 + margin * 2;\n const origin = size / 2;\n // Draw circle delay background\n let delayBg = null;\n if (isDisplayStrokeAndDelay && delay !== null) {\n delayBg = getDelayBgCanvas(origin, radiusDelay, getDelayColor(delay, cancelled));\n }\n // Show delay if feature is hovered or if delay is above 5mins.\n let delayText = null;\n let fontSize = 0;\n if (isDisplayStrokeAndDelay &&\n (hover || (delay || 0) >= delayDisplay || cancelled)) {\n // Draw delay text\n fontSize =\n Math.max(cancelled ? 19 : 14, Math.min(cancelled ? 19 : 17, radius * 1.2)) * pixelRatio;\n const text = getDelayText(delay, cancelled);\n if (text) {\n delayText = getDelayTextCanvas(text, fontSize, `bold ${fontSize}px arial, sans-serif`, getDelayColor(delay, cancelled, true), delayOutlineColor, pixelRatio);\n }\n }\n // Draw colored circle with black border\n let circleFillColor;\n if (useDelayStyle) {\n circleFillColor = getDelayColor(delay, cancelled);\n }\n else {\n circleFillColor = color || getBgColor(type);\n }\n const hasStroke = isDisplayStrokeAndDelay || hover || selected;\n const hasDash = !!isDisplayStrokeAndDelay &&\n !!useDelayStyle &&\n delay === null &&\n operatorProvidesRealtime === 'yes';\n const circle = getCircleCanvas(origin, radius, circleFillColor, hasStroke, hasDash, pixelRatio);\n // Create the canvas\n const width = size + ((delayText === null || delayText === void 0 ? void 0 : delayText.width) || 0) * 2;\n const height = size;\n const canvas = createCanvas(width, height);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n // The renderTrajectories will center the image on the vehicle positions.\n const originX = (delayText === null || delayText === void 0 ? void 0 : delayText.width) || 0;\n if (delayBg) {\n ctx.drawImage(delayBg, originX, 0);\n }\n if (circle) {\n ctx.drawImage(circle, originX, 0);\n }\n // Draw text in the circle\n let circleText = null;\n if (isDisplayText) {\n const fontSize2 = Math.max(radius, 10);\n const textSize = getTextSize(ctx, markerSize, name, fontSize2);\n const textColor2 = !useDelayStyle\n ? textColor || getTextColor(type)\n : '#000000';\n const hasStroke2 = !!useDelayStyle &&\n delay === null &&\n operatorProvidesRealtime === 'yes';\n circleText = getTextCanvas(name, origin, textSize, textColor2, circleFillColor, hasStroke2, pixelRatio);\n }\n if (circleText) {\n ctx.drawImage(circleText, originX, 0);\n }\n if (delayText) {\n ctx.drawImage(delayText, originX + Math.ceil(origin + radiusDelay) + margin, Math.ceil(origin - fontSize));\n }\n cache[key] = canvas;\n }\n }\n return cache[key];\n};\nexport default realtimeDefaultStyle;\n", + "content": "import createCanvas from '../utils/createCanvas';\n/** @private */\nconst cacheDelayBg = {};\n/**\n * Draw circle delay background\n *\n * @private\n */\nexport const getDelayBgCanvas = (origin, radius, color) => {\n const key = `${origin}, ${radius}, ${color}`;\n if (!cacheDelayBg[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.beginPath();\n ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);\n ctx.fillStyle = color;\n ctx.filter = 'blur(1px)';\n ctx.fill();\n cacheDelayBg[key] = canvas;\n }\n }\n return cacheDelayBg[key];\n};\n/** @private */\nconst cacheDelayText = {};\n/**\n * Draw delay text\n *\n * @private\n */\nexport const getDelayTextCanvas = (text, fontSize, font, delayColor, delayOutlineColor = '#000', pixelRatio = 1) => {\n const key = `${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`;\n if (!cacheDelayText[key]) {\n const canvas = createCanvas(Math.ceil(text.length * fontSize), Math.ceil(fontSize + 8 * pixelRatio));\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.font = font;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.font = font;\n ctx.fillStyle = delayColor;\n ctx.strokeStyle = delayOutlineColor;\n ctx.lineWidth = 1.5 * pixelRatio;\n ctx.strokeText(text, 0, fontSize);\n ctx.fillText(text, 0, fontSize);\n cacheDelayText[key] = canvas;\n }\n }\n return cacheDelayText[key];\n};\n/** @private */\nconst cacheCircle = {};\n/**\n * Draw colored circle with black border\n *\n * @private\n */\nexport const getCircleCanvas = (origin, radius, color, hasStroke, hasDash, pixelRatio) => {\n const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`;\n if (!cacheCircle[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n ctx.fillStyle = color;\n if (hasStroke) {\n ctx.lineWidth = 1 * pixelRatio;\n ctx.strokeStyle = '#000000';\n }\n ctx.beginPath();\n ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);\n ctx.fill();\n if (hasDash) {\n ctx.setLineDash([5, 3]);\n }\n if (hasStroke) {\n ctx.stroke();\n }\n cacheCircle[key] = canvas;\n }\n }\n return cacheCircle[key];\n};\n/** @private */\nconst cacheText = {};\n/**\n * Draw text in the circle\n *\n * @private\n */\nexport const getTextCanvas = (text, origin, textSize, fillColor, strokeColor, hasStroke, pixelRatio) => {\n const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`;\n if (!cacheText[key]) {\n const canvas = createCanvas(origin * 2, origin * 2);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n // Draw a stroke to the text only if a provider provides realtime but we don't use it.\n if (hasStroke) {\n ctx.save();\n ctx.textBaseline = 'middle';\n ctx.textAlign = 'center';\n ctx.font = `bold ${textSize + 2}px Arial`;\n ctx.strokeStyle = strokeColor;\n ctx.strokeText(text, origin, origin);\n ctx.restore();\n }\n // Draw a text\n ctx.textBaseline = 'middle';\n ctx.textAlign = 'center';\n ctx.fillStyle = fillColor;\n ctx.font = `bold ${textSize}px Arial`;\n ctx.strokeStyle = strokeColor;\n ctx.strokeText(text, origin, origin);\n ctx.fillText(text, origin, origin);\n cacheText[key] = canvas;\n }\n }\n return cacheText[key];\n};\n/** @private */\nconst cache = {};\n/**\n * A tracker style that take in account the delay.\n *\n * @param {RealtimeTrajectory} trajectory The trajectory to render.\n * @param {ViewState} viewState The view state of the map.\n * @param {RealtimeStyleOptions} options Some options to change the rendering\n * @return a canvas\n */\nconst realtimeDefaultStyle = (trajectory, viewState, options) => {\n const { hoverVehicleId, selectedVehicleId, useDelayStyle, delayOutlineColor = '#000', delayDisplay = 300000, getRadius = () => 0, getBgColor = () => '#000', getDelayColor = () => '#000', getDelayText = () => null, getTextColor = () => '#000', getTextSize = () => 0, getMaxRadiusForText = () => 10, getMaxRadiusForStrokeAndDelay = () => 7, } = options;\n const { zoom, pixelRatio = 1 } = viewState;\n let { type } = trajectory.properties;\n const { train_id: id, line, delay, state, operator_provides_realtime_journey: operatorProvidesRealtime, } = trajectory.properties;\n let { name, text_color: textColor, color } = line || {};\n const cancelled = state === 'JOURNEY_CANCELLED';\n if (!type) {\n type = 'Rail';\n }\n if (!name) {\n name = 'I';\n }\n if (!textColor) {\n textColor = '#000000';\n }\n if (color && color[0] !== '#') {\n color = `#${color}`;\n }\n if (textColor[0] !== '#') {\n textColor = `#${textColor}`;\n }\n const z = Math.min(Math.floor(zoom || 1), 16);\n const hover = !!(hoverVehicleId && hoverVehicleId === id);\n const selected = !!(selectedVehicleId && selectedVehicleId === id);\n // Calcul the radius of the circle\n let radius = getRadius(type, z) * pixelRatio;\n const isDisplayStrokeAndDelay = radius >= getMaxRadiusForStrokeAndDelay() * pixelRatio;\n if (hover || selected) {\n radius = isDisplayStrokeAndDelay\n ? radius + 5 * pixelRatio\n : 14 * pixelRatio;\n }\n const isDisplayText = radius > getMaxRadiusForText() * pixelRatio;\n // Optimize the cache key, very important in high zoom level\n let key = `${radius}${hover}${selected}${cancelled}${delay}`;\n if (useDelayStyle) {\n key += `${operatorProvidesRealtime}`;\n }\n else {\n key += `${type}${color}`;\n }\n if (isDisplayText) {\n key += `${name}${textColor}`;\n }\n if (!cache[key]) {\n if (radius === 0) {\n return null;\n }\n const margin = 1 * pixelRatio;\n const radiusDelay = radius + 2;\n const markerSize = radius * 2;\n const size = radiusDelay * 2 + margin * 2;\n const origin = size / 2;\n // Draw circle delay background\n let delayBg = null;\n if (isDisplayStrokeAndDelay && delay !== null) {\n delayBg = getDelayBgCanvas(origin, radiusDelay, getDelayColor(delay, cancelled));\n }\n // Show delay if feature is hovered or if delay is above 5mins.\n let delayText = null;\n let fontSize = 0;\n if (isDisplayStrokeAndDelay &&\n (hover || (delay || 0) >= delayDisplay || cancelled)) {\n // Draw delay text\n fontSize =\n Math.max(cancelled ? 19 : 14, Math.min(cancelled ? 19 : 17, radius * 1.2)) * pixelRatio;\n const text = getDelayText(delay, cancelled);\n if (text) {\n delayText = getDelayTextCanvas(text, fontSize, `bold ${fontSize}px arial, sans-serif`, getDelayColor(delay, cancelled, true), delayOutlineColor, pixelRatio);\n }\n }\n // Draw colored circle with black border\n let circleFillColor;\n if (useDelayStyle) {\n circleFillColor = getDelayColor(delay, cancelled);\n }\n else {\n circleFillColor = color || getBgColor(type);\n }\n const hasStroke = isDisplayStrokeAndDelay || hover || selected;\n const hasDash = !!isDisplayStrokeAndDelay &&\n !!useDelayStyle &&\n delay === null &&\n operatorProvidesRealtime === 'yes';\n const circle = getCircleCanvas(origin, radius, circleFillColor, hasStroke, hasDash, pixelRatio);\n // Create the canvas\n const width = size + (delayText?.width || 0) * 2;\n const height = size;\n const canvas = createCanvas(width, height);\n if (canvas) {\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n // The renderTrajectories will center the image on the vehicle positions.\n const originX = delayText?.width || 0;\n if (delayBg) {\n ctx.drawImage(delayBg, originX, 0);\n }\n if (circle) {\n ctx.drawImage(circle, originX, 0);\n }\n // Draw text in the circle\n let circleText = null;\n if (isDisplayText) {\n const fontSize2 = Math.max(radius, 10);\n const textSize = getTextSize(ctx, markerSize, name, fontSize2);\n const textColor2 = !useDelayStyle\n ? textColor || getTextColor(type)\n : '#000000';\n const hasStroke2 = !!useDelayStyle &&\n delay === null &&\n operatorProvidesRealtime === 'yes';\n circleText = getTextCanvas(name, origin, textSize, textColor2, circleFillColor, hasStroke2, pixelRatio);\n }\n if (circleText) {\n ctx.drawImage(circleText, originX, 0);\n }\n if (delayText) {\n ctx.drawImage(delayText, originX + Math.ceil(origin + radiusDelay) + margin, Math.ceil(origin - fontSize));\n }\n cache[key] = canvas;\n }\n }\n return cache[key];\n};\nexport default realtimeDefaultStyle;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/styles/realtimeDefaultStyle.js", "access": "public", @@ -5848,7 +5564,7 @@ "lineNumber": 1 }, { - "__docId__": 259, + "__docId__": 247, "kind": "variable", "name": "cacheDelayBg", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -5868,7 +5584,7 @@ "ignore": true }, { - "__docId__": 260, + "__docId__": 248, "kind": "function", "name": "getDelayBgCanvas", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -5909,7 +5625,7 @@ } }, { - "__docId__": 261, + "__docId__": 249, "kind": "variable", "name": "cacheDelayText", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -5929,7 +5645,7 @@ "ignore": true }, { - "__docId__": 262, + "__docId__": 250, "kind": "function", "name": "getDelayTextCanvas", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -5994,7 +5710,7 @@ } }, { - "__docId__": 263, + "__docId__": 251, "kind": "variable", "name": "cacheCircle", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6014,7 +5730,7 @@ "ignore": true }, { - "__docId__": 264, + "__docId__": 252, "kind": "function", "name": "getCircleCanvas", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6073,7 +5789,7 @@ } }, { - "__docId__": 265, + "__docId__": 253, "kind": "variable", "name": "cacheText", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6093,7 +5809,7 @@ "ignore": true }, { - "__docId__": 266, + "__docId__": 254, "kind": "function", "name": "getTextCanvas", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6158,7 +5874,7 @@ } }, { - "__docId__": 267, + "__docId__": 255, "kind": "variable", "name": "cache", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6178,7 +5894,7 @@ "ignore": true }, { - "__docId__": 268, + "__docId__": 256, "kind": "function", "name": "realtimeDefaultStyle", "memberof": "build/common/styles/realtimeDefaultStyle.js", @@ -6234,10 +5950,10 @@ } }, { - "__docId__": 269, + "__docId__": 257, "kind": "file", "name": "build/common/styles/realtimeDelayStyle.js", - "content": "import realtimeDefaultStyle from './realtimeDefaultStyle';\n/**\n * A tracker style that display the delay as backgroundColor.\n *\n * @param {*} trajectory The trajectory to render.\n * @param {*} viewState The view state of the map.\n * @param {*} options Some options to change the rendering\n * @return a canvas\n */\nconst realtimeDelayStyle = (trajectory, viewState, options) => {\n return realtimeDefaultStyle(trajectory, viewState, Object.assign(Object.assign({}, options), { useDelayStyle: true }));\n};\nexport default realtimeDelayStyle;\n", + "content": "import realtimeDefaultStyle from './realtimeDefaultStyle';\n/**\n * A tracker style that display the delay as backgroundColor.\n *\n * @param {*} trajectory The trajectory to render.\n * @param {*} viewState The view state of the map.\n * @param {*} options Some options to change the rendering\n * @return a canvas\n */\nconst realtimeDelayStyle = (trajectory, viewState, options) => {\n return realtimeDefaultStyle(trajectory, viewState, {\n ...options,\n useDelayStyle: true,\n });\n};\nexport default realtimeDelayStyle;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/styles/realtimeDelayStyle.js", "access": "public", @@ -6245,7 +5961,7 @@ "lineNumber": 1 }, { - "__docId__": 270, + "__docId__": 258, "kind": "function", "name": "realtimeDelayStyle", "memberof": "build/common/styles/realtimeDelayStyle.js", @@ -6301,7 +6017,7 @@ } }, { - "__docId__": 271, + "__docId__": 259, "kind": "file", "name": "build/common/styles/realtimeSimpleStyle.js", "content": "/**\n * A very simple tracker style.\n * Display blue point for each train.\n */\nlet canvas;\nconst realtimeSimpleStyle = () => {\n if (!canvas) {\n canvas = document.createElement('canvas');\n canvas.width = 15;\n canvas.height = 15;\n const ctx = canvas.getContext('2d');\n if (ctx) {\n ctx.arc(8, 8, 5, 0, 2 * Math.PI, false);\n ctx.fillStyle = '#8ED6FF';\n ctx.fill();\n ctx.lineWidth = 3;\n ctx.strokeStyle = 'black';\n ctx.stroke();\n ctx.lineWidth = 3;\n }\n }\n return canvas;\n};\nexport default realtimeSimpleStyle;\n", @@ -6312,7 +6028,7 @@ "lineNumber": 1 }, { - "__docId__": 272, + "__docId__": 260, "kind": "function", "name": "realtimeSimpleStyle", "memberof": "build/common/styles/realtimeSimpleStyle.js", @@ -6335,10 +6051,10 @@ } }, { - "__docId__": 273, + "__docId__": 261, "kind": "file", "name": "build/common/tracker.worker.js", - "content": "import stringify from 'json-stringify-safe';\nimport { realtimeSimpleStyle } from './styles';\nimport { renderTrajectories } from './utils';\nconst debug = false;\nlet renderTimeout;\nlet count = 0;\n// const format = new GeoJSON();\nconst canvas = new OffscreenCanvas(1, 1);\nconst render = (evt) => {\n // eslint-disable-next-line no-console\n if (debug)\n console.time('render');\n // eslint-disable-next-line no-console\n if (debug)\n console.log('render', evt.data.frameState);\n count = 0;\n const { trajectories, frameState, viewState, options } = evt.data;\n const { renderedTrajectories } = renderTrajectories(canvas, Object.values(trajectories), realtimeSimpleStyle, viewState, options);\n if (debug)\n console.timeEnd('render');\n if (debug)\n console.log('NUMBER OF STYLES CREATED', count);\n const imageData = canvas.transferToImageBitmap();\n const state = Object.assign({}, frameState);\n // @ts-ignore\n delete state.layerStatesArray;\n // @ts-ignore\n delete state.viewState.projection;\n // eslint-disable-next-line no-restricted-globals\n self.postMessage({\n action: 'rendered',\n imageData,\n // transform: rendererTransform,\n renderedTrajectories,\n frameState: JSON.parse(stringify(state)),\n }, [imageData]);\n renderTimeout = null;\n};\n// eslint-disable-next-line no-restricted-globals\nself.onmessage = (evt) => {\n // debugger;\n // if (evt.data.action === 'addTrajectory') {\n // const { trajectory } = evt.data;\n // const id = trajectory.properties.train_id;\n // trajectories[id] = trajectory;\n // trajectories[id].properties.olGeometry = format.readGeometry(\n // trajectory.geometry,\n // );\n // return;\n // }\n // if (evt.data.action === 'removeTrajectory') {\n // delete trajectories[evt.data.trajectoryId];\n // return;\n // }\n // if (evt.data.action === 'sendData') {\n // // eslint-disable-next-line no-console\n // if (debug) console.log('sendData', evt.data);\n // if (debug) console.time('sendData');\n // trajectories = evt.data.trajectories;\n // if (debug) console.timeEnd('sendData');\n // return;\n // }\n if (evt.data.action !== 'render') {\n return;\n }\n if (renderTimeout) {\n clearTimeout(renderTimeout);\n }\n // eslint-disable-next-line no-restricted-globals\n renderTimeout = self.setTimeout(() => {\n render(evt);\n }, 0);\n};\n// eslint-disable-next-line no-restricted-globals\nexport default self;\n", + "content": "/// \n/// \n/// \n/// \nimport stringify from 'json-stringify-safe';\nimport { realtimeSimpleStyle } from './styles';\nimport { renderTrajectories } from './utils';\nconst debug = false;\nlet renderTimeout;\nlet count = 0;\n// const format = new GeoJSON();\nconst canvas = new OffscreenCanvas(1, 1);\nconst render = (evt) => {\n // eslint-disable-next-line no-console\n if (debug)\n console.time('render');\n // eslint-disable-next-line no-console\n if (debug)\n console.log('render', evt.data.frameState);\n count = 0;\n const { trajectories, frameState, viewState, options } = evt.data;\n const { renderedTrajectories } = renderTrajectories(canvas, Object.values(trajectories), realtimeSimpleStyle, viewState, options);\n if (debug)\n console.timeEnd('render');\n if (debug)\n console.log('NUMBER OF STYLES CREATED', count);\n const imageData = canvas.transferToImageBitmap();\n const state = { ...frameState };\n // @ts-ignore\n delete state.layerStatesArray;\n // @ts-ignore\n delete state.viewState.projection;\n // eslint-disable-next-line no-restricted-globals\n self.postMessage({\n action: 'rendered',\n imageData,\n // transform: rendererTransform,\n renderedTrajectories,\n frameState: JSON.parse(stringify(state)),\n }, [imageData]);\n renderTimeout = null;\n};\n// eslint-disable-next-line no-restricted-globals\nself.onmessage = (evt) => {\n // debugger;\n // if (evt.data.action === 'addTrajectory') {\n // const { trajectory } = evt.data;\n // const id = trajectory.properties.train_id;\n // trajectories[id] = trajectory;\n // trajectories[id].properties.olGeometry = format.readGeometry(\n // trajectory.geometry,\n // );\n // return;\n // }\n // if (evt.data.action === 'removeTrajectory') {\n // delete trajectories[evt.data.trajectoryId];\n // return;\n // }\n // if (evt.data.action === 'sendData') {\n // // eslint-disable-next-line no-console\n // if (debug) console.log('sendData', evt.data);\n // if (debug) console.time('sendData');\n // trajectories = evt.data.trajectories;\n // if (debug) console.timeEnd('sendData');\n // return;\n // }\n if (evt.data.action !== 'render') {\n return;\n }\n if (renderTimeout) {\n clearTimeout(renderTimeout);\n }\n // eslint-disable-next-line no-restricted-globals\n renderTimeout = self.setTimeout(() => {\n render(evt);\n }, 0);\n};\n// We need an export to force this file to act like a module, so TS will let us re-type `self`\n// export default null;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/tracker.worker.js", "access": "public", @@ -6346,7 +6062,7 @@ "lineNumber": 1 }, { - "__docId__": 274, + "__docId__": 262, "kind": "variable", "name": "debug", "memberof": "build/common/tracker.worker.js", @@ -6357,7 +6073,7 @@ "importPath": "mobility-toolbox-js/build/common/tracker.worker.js", "importStyle": null, "description": null, - "lineNumber": 4, + "lineNumber": 8, "undocument": true, "type": { "types": [ @@ -6367,7 +6083,7 @@ "ignore": true }, { - "__docId__": 275, + "__docId__": 263, "kind": "variable", "name": "count", "memberof": "build/common/tracker.worker.js", @@ -6378,7 +6094,7 @@ "importPath": "mobility-toolbox-js/build/common/tracker.worker.js", "importStyle": null, "description": null, - "lineNumber": 6, + "lineNumber": 10, "undocument": true, "type": { "types": [ @@ -6388,7 +6104,7 @@ "ignore": true }, { - "__docId__": 276, + "__docId__": 264, "kind": "variable", "name": "canvas", "memberof": "build/common/tracker.worker.js", @@ -6399,7 +6115,7 @@ "importPath": "mobility-toolbox-js/build/common/tracker.worker.js", "importStyle": null, "description": null, - "lineNumber": 8, + "lineNumber": 12, "undocument": true, "type": { "types": [ @@ -6409,7 +6125,7 @@ "ignore": true }, { - "__docId__": 277, + "__docId__": 265, "kind": "function", "name": "render", "memberof": "build/common/tracker.worker.js", @@ -6422,108 +6138,7 @@ "importPath": "mobility-toolbox-js/build/common/tracker.worker.js", "importStyle": null, "description": null, - "lineNumber": 9, - "undocument": true, - "params": [ - { - "name": "evt", - "types": [ - "*" - ] - } - ], - "return": null, - "ignore": true - }, - { - "__docId__": 278, - "kind": "file", - "name": "build/common/tracker2.worker.js", - "content": "import stringify from 'json-stringify-safe';\n// import GeoJSON from 'ol/format/GeoJSON';\nimport { realtimeSimpleStyle } from './styles';\nimport { renderTrajectories } from './utils';\nconst debug = false;\nlet renderTimeout;\nlet count = 0;\n// const format = new GeoJSON();\nconst canvas = new OffscreenCanvas(1, 1);\nconst render = (evt) => {\n // eslint-disable-next-line no-console\n if (debug)\n console.time('render');\n // eslint-disable-next-line no-console\n if (debug)\n console.log('render', evt.data.frameState);\n count = 0;\n const { trajectories, frameState, viewState, options } = evt.data;\n const { renderedTrajectories } = renderTrajectories(canvas, Object.values(trajectories), realtimeSimpleStyle, viewState, options);\n if (debug)\n console.timeEnd('render');\n if (debug)\n console.log('NUMBER OF STYLES CREATED', count);\n const imageData = canvas.transferToImageBitmap();\n const state = Object.assign({}, frameState);\n // @ts-ignore\n delete state.layerStatesArray;\n // @ts-ignore\n delete state.viewState.projection;\n // eslint-disable-next-line no-restricted-globals\n self.postMessage({\n action: 'rendered',\n imageData,\n // transform: rendererTransform,\n renderedTrajectories,\n frameState: JSON.parse(stringify(state)),\n }, [imageData]);\n renderTimeout = null;\n};\n// eslint-disable-next-line no-restricted-globals\nself.onmessage = (evt) => {\n // debugger;\n // if (evt.data.action === 'addTrajectory') {\n // const { trajectory } = evt.data;\n // const id = trajectory.properties.train_id;\n // trajectories[id] = trajectory;\n // trajectories[id].properties.olGeometry = format.readGeometry(\n // trajectory.geometry,\n // );\n // return;\n // }\n // if (evt.data.action === 'removeTrajectory') {\n // delete trajectories[evt.data.trajectoryId];\n // return;\n // }\n // if (evt.data.action === 'sendData') {\n // // eslint-disable-next-line no-console\n // if (debug) console.log('sendData', evt.data);\n // if (debug) console.time('sendData');\n // trajectories = evt.data.trajectories;\n // if (debug) console.timeEnd('sendData');\n // return;\n // }\n if (evt.data.action !== 'render') {\n return;\n }\n if (renderTimeout) {\n clearTimeout(renderTimeout);\n }\n // eslint-disable-next-line no-restricted-globals\n renderTimeout = self.setTimeout(() => {\n render(evt);\n }, 0);\n};\n// eslint-disable-next-line no-restricted-globals\nexport default self;\n", - "static": true, - "longname": "/home/olivier/GIT/mobility-toolbox-js/build/common/tracker2.worker.js", - "access": "public", - "description": null, - "lineNumber": 1 - }, - { - "__docId__": 279, - "kind": "variable", - "name": "debug", - "memberof": "build/common/tracker2.worker.js", - "static": true, - "longname": "build/common/tracker2.worker.js~debug", - "access": "public", - "export": false, - "importPath": "mobility-toolbox-js/build/common/tracker2.worker.js", - "importStyle": null, - "description": null, - "lineNumber": 5, - "undocument": true, - "type": { - "types": [ - "boolean" - ] - }, - "ignore": true - }, - { - "__docId__": 280, - "kind": "variable", - "name": "count", - "memberof": "build/common/tracker2.worker.js", - "static": true, - "longname": "build/common/tracker2.worker.js~count", - "access": "public", - "export": false, - "importPath": "mobility-toolbox-js/build/common/tracker2.worker.js", - "importStyle": null, - "description": null, - "lineNumber": 7, - "undocument": true, - "type": { - "types": [ - "number" - ] - }, - "ignore": true - }, - { - "__docId__": 281, - "kind": "variable", - "name": "canvas", - "memberof": "build/common/tracker2.worker.js", - "static": true, - "longname": "build/common/tracker2.worker.js~canvas", - "access": "public", - "export": false, - "importPath": "mobility-toolbox-js/build/common/tracker2.worker.js", - "importStyle": null, - "description": null, - "lineNumber": 9, - "undocument": true, - "type": { - "types": [ - "*" - ] - }, - "ignore": true - }, - { - "__docId__": 282, - "kind": "function", - "name": "render", - "memberof": "build/common/tracker2.worker.js", - "generator": false, - "async": false, - "static": true, - "longname": "build/common/tracker2.worker.js~render", - "access": "public", - "export": false, - "importPath": "mobility-toolbox-js/build/common/tracker2.worker.js", - "importStyle": null, - "description": null, - "lineNumber": 10, + "lineNumber": 13, "undocument": true, "params": [ { @@ -6537,7 +6152,7 @@ "ignore": true }, { - "__docId__": 283, + "__docId__": 266, "kind": "file", "name": "build/common/typedefs.js", "content": "\"use strict\";\n/**\n * @typedef {function} FilterFunction\n * @param {Vehicle} vehicle Vehicle to filter.\n * @returns boolean\n */\n/**\n * @typedef {function} SortFunction\n * @param {any} a Object a to compare.\n * @param {any} b Object b to compare.\n * @returns number\n */\n/**\n * @typedef {Object} ViewState\n * @property {number|undefined} time A time in ms.\n * @property {number[2]|undefined} center A center in mercator coordinate.\n * @property {number[4]} extent An Extent in mercator coordinates.\n * @property {number[2]} size A size ([width, height]).\n * @property {number} rotation A rotation in radians.\n * @property {number} resolution A resolution.\n * @property {number} zoom A zoom level.\n * @property {number|undefined} pixelRatio A pixel ratio.\n */\n/**\n * @typedef {Object} FeatureInfo\n * @property {Layer} layer A layer.\n * @property {ol/Feature~Feature[]} features An array of features.\n * @property {number[2]} coordinate The coordinate where to find the featue.\n */\n/**\n * @typedef {ol/Map~Map|mapboxgl.Map|maplibregl.Map} AnyMap\n */\n/**\n * @typedef {Object} ControlCommonOptions\n * @property {boolean} [active = true] Whether the control is active or not.\n * @property {HTMLElement} element The HTML element used to render the control.\n * @property {HTMLElement} target The HTML element where to render the element property. Default is the map's element.\n * @property {function} render Render function called whenever the control needs to be rerendered.\n */\n/**\n * @typedef {Object} LayerCommonOptions\n * @property {string!} key Identifier of the layer. Must be unique.\n * @property {string!} name Name of the layer.\n * @property {string!} group Group of the layer.\n * @property {string[]!} copyrights List of copyrights.\n * @property {Layer[]!} children List of children layers.\n * @property {boolean!} visible Define if the layer is currently display on the map.\n * @property {boolean!} disabled Define if the layer is currently display on the map but can't be seen (extent, zoom ,data restrictions).\n * @property {number!} hittolerance Hit-detection tolerance in css pixels. Pixels inside the radius around the given position will be checked for features.\n * @property {Object!} properties - Custom properties.\n * @property {AnyMap!} map - The map used to display the layer.\n */\n", @@ -6548,10 +6163,10 @@ "lineNumber": 1 }, { - "__docId__": 284, + "__docId__": 267, "kind": "file", "name": "build/index.js", - "content": "import * as ol from './ol';\nimport * as mapbox from './mapbox';\nimport * as ol_1 from './ol';\nexport { ol_1 as ol };\nimport * as mapbox_1 from './mapbox';\nexport { mapbox_1 as mapbox };\nexport default {\n ol,\n mapbox,\n};\n", + "content": "import * as ol from './ol';\nimport * as mapbox from './mapbox';\nexport * as ol from './ol';\nexport * as mapbox from './mapbox';\nexport default {\n ol,\n mapbox,\n};\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/index.js", "access": "public", @@ -6559,7 +6174,7 @@ "lineNumber": 1 }, { - "__docId__": 285, + "__docId__": 268, "kind": "file", "name": "build/mapbox/controls/CopyrightControl.js", "content": "import CopyrightControlCommon from '../../common/controls/CopyrightControlCommon';\nimport { getMapboxMapCopyrights } from '../../common/utils';\n/**\n * Display layer's copyrights.\n *\n * @example\n * import { Map } from 'mapbox-gl';\n * import { CopyrightControl } from 'mobility-toolbox-js/mapbox';\n *\n * const map = new Map({\n * container: 'map',\n * style: `https://maps.geops.io/styles/travic_v2/style.json?key=${window.apiKey}`,\n * });\n *\n * const control = new CopyrightControl();\n * control.attachToMap(map);\n *\n *\n * @see Mapbox copyright example\n *\n * @extends {CopyrightControlCommon}\n */\nclass CopyrightControl extends CopyrightControlCommon {\n constructor(options) {\n super(options);\n this.render = this.render.bind(this);\n }\n activate() {\n super.activate();\n if (this.map) {\n this.map.on('sourcedata', this.render);\n this.map.on('styledata', this.render);\n this.map.on('idle', this.render);\n }\n }\n deactivate() {\n if (this.map) {\n this.map.off('sourcedata', this.render);\n this.map.off('styledata', this.render);\n this.map.off('idle', this.render);\n }\n super.deactivate();\n }\n getCopyrights() {\n return getMapboxMapCopyrights(this.map);\n }\n}\nexport default CopyrightControl;\n", @@ -6570,7 +6185,7 @@ "lineNumber": 1 }, { - "__docId__": 286, + "__docId__": 269, "kind": "class", "name": "CopyrightControl", "memberof": "build/mapbox/controls/CopyrightControl.js", @@ -6594,7 +6209,7 @@ ] }, { - "__docId__": 287, + "__docId__": 270, "kind": "constructor", "name": "constructor", "memberof": "build/mapbox/controls/CopyrightControl.js~CopyrightControl", @@ -6608,7 +6223,7 @@ "undocument": true }, { - "__docId__": 288, + "__docId__": 271, "kind": "member", "name": "render", "memberof": "build/mapbox/controls/CopyrightControl.js~CopyrightControl", @@ -6625,7 +6240,7 @@ } }, { - "__docId__": 289, + "__docId__": 272, "kind": "method", "name": "activate", "memberof": "build/mapbox/controls/CopyrightControl.js~CopyrightControl", @@ -6641,7 +6256,7 @@ "return": null }, { - "__docId__": 290, + "__docId__": 273, "kind": "method", "name": "deactivate", "memberof": "build/mapbox/controls/CopyrightControl.js~CopyrightControl", @@ -6657,7 +6272,7 @@ "return": null }, { - "__docId__": 291, + "__docId__": 274, "kind": "method", "name": "getCopyrights", "memberof": "build/mapbox/controls/CopyrightControl.js~CopyrightControl", @@ -6677,7 +6292,7 @@ } }, { - "__docId__": 292, + "__docId__": 275, "kind": "file", "name": "build/mapbox/controls/index.js", "content": "// eslint-disable-next-line import/prefer-default-export\nexport { default as CopyrightControl } from './CopyrightControl';\n", @@ -6688,7 +6303,7 @@ "lineNumber": 1 }, { - "__docId__": 293, + "__docId__": 276, "kind": "file", "name": "build/mapbox/index.js", "content": "export * from '../api';\nexport * from '../common';\nexport * from './controls';\nexport * from './layers';\nexport * from './utils';\n", @@ -6699,10 +6314,10 @@ "lineNumber": 1 }, { - "__docId__": 294, + "__docId__": 277, "kind": "file", "name": "build/mapbox/layers/Layer.js", - "content": "import { unByKey } from 'ol/Observable';\nimport { transformExtent } from 'ol/proj';\nimport LayerCommon from '../../common/layers/LayerCommon';\nimport userInteractionsMixin from '../../common/mixins/UserInteractionsLayerMixin';\n/**\n * A class representing a layer to display on an OpenLayers map.\n *\n * @example\n * import { Layer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new Layer({\n * olLayer: ...,\n * });\n *\n * @see Map example\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass Layer extends userInteractionsMixin(LayerCommon) {\n /**\n * Initialize the layer and listen to user events.\n * @param {mapboxgl.Map|maplibregl.Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n if (this.userInteractions) {\n this.toggleVisibleListeners();\n this.onChangeVisibleKey = this.on(\n // @ts-ignore\n 'change:visible', this.toggleVisibleListeners);\n }\n }\n detachFromMap() {\n if (this.map) {\n this.deactivateUserInteractions();\n // @ts-ignore\n unByKey(this.onChangeVisibleKey);\n }\n super.detachFromMap();\n }\n activateUserInteractions() {\n var _a, _b;\n this.deactivateUserInteractions();\n if (this.map &&\n this.userInteractions &&\n this.userClickInteractions &&\n ((_a = this.userClickCallbacks) === null || _a === void 0 ? void 0 : _a.length)) {\n this.map.on('click', this.onUserClickCallback);\n }\n if (this.map &&\n this.userInteractions &&\n this.userHoverInteractions &&\n ((_b = this.userHoverCallbacks) === null || _b === void 0 ? void 0 : _b.length)) {\n this.map.on('mousemove', this.onUserMoveCallback);\n }\n }\n deactivateUserInteractions() {\n if (this.map) {\n this.map.off('mousemove', this.onUserMoveCallback);\n this.map.off('click', this.onUserClickCallback);\n }\n }\n /**\n * Toggle listeners needed when a layer is avisible or not.\n * @private\n */\n toggleVisibleListeners() {\n if (this.visible) {\n this.activateUserInteractions();\n }\n else {\n this.deactivateUserInteractions();\n }\n }\n /**\n * Returns the current extent in mercator coordinates.\n */\n getMercatorExtent() {\n const bounds = this.map.getBounds().toArray();\n return transformExtent([...bounds[0], ...bounds[1]], 'EPSG:4326', 'EPSG:3857');\n }\n /**\n * Returns the equivalent zoom in Openlayers.\n */\n getOlZoom() {\n return this.map.getZoom() + 1;\n }\n /**\n * Create a copy of the Layer.\n * @param {Object} newOptions Options to override\n * @return {Layer} A Layer\n */\n clone(newOptions) {\n return new Layer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default Layer;\n", + "content": "import { unByKey } from 'ol/Observable';\nimport { transformExtent } from 'ol/proj';\nimport LayerCommon from '../../common/layers/LayerCommon';\nimport userInteractionsMixin from '../../common/mixins/UserInteractionsLayerMixin';\n/**\n * A class representing a layer to display on an OpenLayers map.\n *\n * @example\n * import { Layer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new Layer({\n * olLayer: ...,\n * });\n *\n * @see Map example\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass Layer extends userInteractionsMixin(LayerCommon) {\n /**\n * Initialize the layer and listen to user events.\n * @param {mapboxgl.Map|maplibregl.Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n if (this.userInteractions) {\n this.toggleVisibleListeners();\n this.onChangeVisibleKey = this.on(\n // @ts-ignore\n 'change:visible', this.toggleVisibleListeners);\n }\n }\n detachFromMap() {\n if (this.map) {\n this.deactivateUserInteractions();\n // @ts-ignore\n unByKey(this.onChangeVisibleKey);\n }\n super.detachFromMap();\n }\n activateUserInteractions() {\n this.deactivateUserInteractions();\n if (this.map &&\n this.userInteractions &&\n this.userClickInteractions &&\n this.userClickCallbacks?.length) {\n this.map.on('click', this.onUserClickCallback);\n }\n if (this.map &&\n this.userInteractions &&\n this.userHoverInteractions &&\n this.userHoverCallbacks?.length) {\n this.map.on('mousemove', this.onUserMoveCallback);\n }\n }\n deactivateUserInteractions() {\n if (this.map) {\n this.map.off('mousemove', this.onUserMoveCallback);\n this.map.off('click', this.onUserClickCallback);\n }\n }\n /**\n * Toggle listeners needed when a layer is avisible or not.\n * @private\n */\n toggleVisibleListeners() {\n if (this.visible) {\n this.activateUserInteractions();\n }\n else {\n this.deactivateUserInteractions();\n }\n }\n /**\n * Returns the current extent in mercator coordinates.\n */\n getMercatorExtent() {\n const bounds = this.map.getBounds().toArray();\n return transformExtent([...bounds[0], ...bounds[1]], 'EPSG:4326', 'EPSG:3857');\n }\n /**\n * Returns the equivalent zoom in Openlayers.\n */\n getOlZoom() {\n return this.map.getZoom() + 1;\n }\n /**\n * Create a copy of the Layer.\n * @param {Object} newOptions Options to override\n * @return {Layer} A Layer\n */\n clone(newOptions) {\n return new Layer({ ...this.options, ...newOptions });\n }\n}\nexport default Layer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/mapbox/layers/Layer.js", "access": "public", @@ -6710,7 +6325,7 @@ "lineNumber": 1 }, { - "__docId__": 295, + "__docId__": 278, "kind": "class", "name": "Layer", "memberof": "build/mapbox/layers/Layer.js", @@ -6740,7 +6355,7 @@ ] }, { - "__docId__": 296, + "__docId__": 279, "kind": "method", "name": "attachToMap", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6767,7 +6382,7 @@ "return": null }, { - "__docId__": 297, + "__docId__": 280, "kind": "member", "name": "onChangeVisibleKey", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6784,7 +6399,7 @@ } }, { - "__docId__": 298, + "__docId__": 281, "kind": "method", "name": "detachFromMap", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6800,7 +6415,7 @@ "return": null }, { - "__docId__": 299, + "__docId__": 282, "kind": "method", "name": "activateUserInteractions", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6816,7 +6431,7 @@ "return": null }, { - "__docId__": 300, + "__docId__": 283, "kind": "method", "name": "deactivateUserInteractions", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6826,13 +6441,13 @@ "longname": "build/mapbox/layers/Layer.js~Layer#deactivateUserInteractions", "access": "public", "description": null, - "lineNumber": 61, + "lineNumber": 60, "undocument": true, "params": [], "return": null }, { - "__docId__": 301, + "__docId__": 284, "kind": "method", "name": "toggleVisibleListeners", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6842,12 +6457,12 @@ "longname": "build/mapbox/layers/Layer.js~Layer#toggleVisibleListeners", "access": "private", "description": "Toggle listeners needed when a layer is avisible or not.", - "lineNumber": 71, + "lineNumber": 70, "params": [], "return": null }, { - "__docId__": 302, + "__docId__": 285, "kind": "method", "name": "getMercatorExtent", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6857,7 +6472,7 @@ "longname": "build/mapbox/layers/Layer.js~Layer#getMercatorExtent", "access": "public", "description": "Returns the current extent in mercator coordinates.", - "lineNumber": 82, + "lineNumber": 81, "params": [], "return": { "types": [ @@ -6866,7 +6481,7 @@ } }, { - "__docId__": 303, + "__docId__": 286, "kind": "method", "name": "getOlZoom", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6876,7 +6491,7 @@ "longname": "build/mapbox/layers/Layer.js~Layer#getOlZoom", "access": "public", "description": "Returns the equivalent zoom in Openlayers.", - "lineNumber": 89, + "lineNumber": 88, "params": [], "return": { "types": [ @@ -6885,7 +6500,7 @@ } }, { - "__docId__": 304, + "__docId__": 287, "kind": "method", "name": "clone", "memberof": "build/mapbox/layers/Layer.js~Layer", @@ -6895,7 +6510,7 @@ "longname": "build/mapbox/layers/Layer.js~Layer#clone", "access": "public", "description": "Create a copy of the Layer.", - "lineNumber": 97, + "lineNumber": 96, "params": [ { "nullable": null, @@ -6918,10 +6533,10 @@ } }, { - "__docId__": 305, + "__docId__": 288, "kind": "file", "name": "build/mapbox/layers/RealtimeLayer.js", - "content": "import { fromLonLat } from 'ol/proj';\nimport { unByKey } from 'ol/Observable';\nimport { getWidth, getHeight } from 'ol/extent';\nimport transformRotate from '@turf/transform-rotate';\nimport { point } from '@turf/helpers';\nimport mixin from '../../common/mixins/RealtimeLayerMixin';\nimport Layer from './Layer';\nimport { getSourceCoordinates, getMercatorResolution } from '../utils';\n/**\n * Responsible for loading and display data from a Realtime service.\n *\n * @example\n * import { RealtimeLayer } from 'mobility-toolbox-js/mapbox';\n *\n * const layer = new RealtimeLayer({\n * url: [yourUrl],\n * apiKey: [yourApiKey],\n * });\n *\n *\n * @see RealtimeAPI\n *\n * @extends {Layer}\n * @implements {RealtimeLayerInterface}\n */\n// @ts-ignore\nclass RealtimeLayer extends mixin(Layer) {\n constructor(options = {}) {\n super(Object.assign({}, options));\n /** @ignore */\n this.onLoad = this.onLoad.bind(this);\n /** @ignore */\n this.onMove = this.onMove.bind(this);\n /** @ignore */\n this.onMoveEnd = this.onMoveEnd.bind(this);\n /** @ignore */\n this.onZoomEnd = this.onZoomEnd.bind(this);\n /** @ignore */\n this.onVisibilityChange = this.onVisibilityChange.bind(this);\n }\n /**\n * Initialize the layer.\n *\n * @param {mapboxgl.Map} map A [mapbox Map](https://docs.mapbox.com/mapbox-gl-js/api/map/).\n * @param {string} beforeId Layer's id before which we want to add the new layer.\n * @override\n */\n // @ts-ignore\n attachToMap(map, beforeId) {\n if (!map) {\n return;\n }\n super.attachToMap(map);\n this.source = {\n type: 'canvas',\n canvas: this.canvas,\n coordinates: getSourceCoordinates(map, this.pixelRatio),\n // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance.\n animate: true,\n attribution: this.copyrights && this.copyrights.join(', '),\n };\n this.beforeId = beforeId;\n this.layer = {\n id: this.key,\n type: 'raster',\n source: this.key,\n layout: {\n visibility: this.visible ? 'visible' : 'none',\n },\n paint: {\n 'raster-opacity': 1,\n 'raster-fade-duration': 0,\n 'raster-resampling': 'nearest', // important otherwise it looks blurry\n },\n };\n if (map.isStyleLoaded()) {\n this.onLoad();\n }\n this.map.on('load', this.onLoad);\n this.listeners = [this.on('change:visible', this.onVisibilityChange)];\n }\n /**\n * Remove listeners from the Mapbox Map.\n */\n detachFromMap() {\n if (this.map) {\n this.map.off('load', this.onLoad);\n // @ts-ignore\n this.listeners.forEach((listener) => {\n unByKey(listener);\n });\n if (this.map.style && this.map.getLayer(this.key)) {\n this.map.removeLayer(this.key);\n }\n if (this.map.style && this.map.getSource(this.key)) {\n this.map.removeSource(this.key);\n }\n }\n super.detachFromMap();\n }\n /**\n * Start updating vehicles position.\n *\n * @listens {mapboxgl.map.event:zoomend} Listen to zoom end event.\n * @listens {mapboxgl.map.event:mousemove} Listen to mousemove end.\n * @override\n */\n start() {\n super.start();\n this.map.on('move', this.onMove);\n this.map.on('moveend', this.onMoveEnd);\n this.map.on('zoomend', this.onZoomEnd);\n }\n /**\n * Stop updating vehicles position, and unlisten events.\n *\n * @override\n */\n stop() {\n super.stop();\n if (this.map) {\n this.map.off('move', this.onMove);\n this.map.off('moveend', this.onMoveEnd);\n this.map.off('zoomend', this.onZoomEnd);\n }\n }\n onLoad() {\n if (!this.map.getSource(this.key)) {\n this.map.addSource(this.key, this.source);\n }\n if (!this.map.getLayer(this.key)) {\n this.map.addLayer(this.layer, this.beforeId);\n }\n }\n /**\n * Function triggered when the user moves the cursor over the map.\n * @override\n */\n onUserMoveCallback(evt) {\n super.onUserMoveCallback(Object.assign({ coordinate: fromLonLat(evt.lngLat.toArray()) }, evt));\n }\n /**\n * Render the trajectories using current map's size, resolution and rotation.\n * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions.\n * @overrides\n */\n // @ts-ignore\n renderTrajectories(noInterpolate = false) {\n if (!this.map) {\n return;\n }\n if (!this.pixelRatio) {\n this.pixelRatio = 1;\n }\n const { width, height } = this.map.getCanvas();\n const center = this.map.getCenter();\n // We use turf here to have good transform.\n const leftBottom = this.map.unproject({\n x: 0,\n y: height / this.pixelRatio,\n }); // southWest\n const rightTop = this.map.unproject({\n x: width / this.pixelRatio,\n y: 0,\n }); // north east\n const coord0 = transformRotate(point([leftBottom.lng, leftBottom.lat]), -this.map.getBearing(), {\n pivot: [center.lng, center.lat],\n }).geometry.coordinates;\n const coord1 = transformRotate(point([rightTop.lng, rightTop.lat]), -this.map.getBearing(), {\n pivot: [center.lng, center.lat],\n }).geometry.coordinates;\n const bounds = [...fromLonLat(coord0), ...fromLonLat(coord1)];\n const xResolution = getWidth(bounds) / (width / this.pixelRatio);\n const yResolution = getHeight(bounds) / (height / this.pixelRatio);\n const res = Math.max(xResolution, yResolution);\n // Coordinate of trajectories are in mercator so we have to pass the proper resolution and center in mercator.\n const viewState = {\n size: [width / this.pixelRatio, height / this.pixelRatio],\n center: fromLonLat([center.lng, center.lat]),\n extent: bounds,\n resolution: res,\n zoom: this.getOlZoom(),\n rotation: -(this.map.getBearing() * Math.PI) / 180,\n pixelRatio: this.pixelRatio,\n };\n super.renderTrajectories(viewState, noInterpolate);\n }\n /**\n * Return the delay in ms before the next rendering.\n */\n getRefreshTimeInMs() {\n return super.getRefreshTimeInMs(this.map.getZoom());\n }\n getFeatureInfoAtCoordinate(coordinate, options = {}) {\n const resolution = getMercatorResolution(this.map);\n return super.getFeatureInfoAtCoordinate(coordinate, Object.assign({ resolution }, options));\n }\n onVisibilityChange() {\n if (this.visible && !this.map.getLayer(this.key)) {\n this.map.addLayer(this.layer, this.beforeId);\n }\n else if (this.map.getLayer(this.key)) {\n this.map.removeLayer(this.key);\n }\n // We can't use setLayoutProperty it triggers an error probably a bug in mapbox\n // this.map.setLayoutProperty(\n // this.key,\n // 'visibilty',\n // this.visible ? 'visible' : 'none',\n // );\n }\n /**\n * Remove the trajectory form the list if necessary.\n *\n * @private\n */\n purgeTrajectory(trajectory, extent, zoom) {\n return super.purgeTrajectory(trajectory, extent || this.getMercatorExtent(), zoom || Math.floor(this.getOlZoom()));\n }\n /**\n * Send the current bbox to the websocket\n */\n setBbox(extent, zoom) {\n let newExtent = extent;\n let newZoom = zoom;\n if (!newExtent && this.isUpdateBboxOnMoveEnd) {\n newExtent = extent || this.getMercatorExtent();\n newZoom = Math.floor(this.getOlZoom());\n }\n super.setBbox(newExtent, newZoom);\n }\n /**\n * Callback on 'move' event.\n *\n * @private\n */\n onMove() {\n this.renderTrajectories();\n }\n renderTrajectoriesInternal(viewState, noInterpolate = false) {\n const render = super.renderTrajectoriesInternal(viewState, noInterpolate);\n if (render && this.map.style) {\n const extent = getSourceCoordinates(this.map, this.pixelRatio);\n const source = this.map.getSource(this.key);\n if (source) {\n source.setCoordinates(extent);\n }\n }\n return render;\n }\n /**\n * Send the new BBOX to the websocket.\n *\n * @private\n * @override\n */\n onMoveEnd() {\n this.renderTrajectories();\n if (this.visible && this.isUpdateBboxOnMoveEnd) {\n this.setBbox();\n }\n }\n /**\n * Update the cursor style when hovering a vehicle.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n super.onFeatureHover(features, layer, coordinate);\n this.map.getCanvasContainer().style.cursor = features.length\n ? 'pointer'\n : 'auto';\n }\n}\nexport default RealtimeLayer;\n", + "content": "import { fromLonLat } from 'ol/proj';\nimport { unByKey } from 'ol/Observable';\nimport { getWidth, getHeight } from 'ol/extent';\nimport transformRotate from '@turf/transform-rotate';\nimport { point } from '@turf/helpers';\nimport mixin from '../../common/mixins/RealtimeLayerMixin';\nimport Layer from './Layer';\nimport { getSourceCoordinates, getMercatorResolution } from '../utils';\n/**\n * Responsible for loading and display data from a Realtime service.\n *\n * @example\n * import { RealtimeLayer } from 'mobility-toolbox-js/mapbox';\n *\n * const layer = new RealtimeLayer({\n * url: [yourUrl],\n * apiKey: [yourApiKey],\n * });\n *\n *\n * @see RealtimeAPI\n *\n * @extends {Layer}\n * @implements {RealtimeLayerInterface}\n */\n// @ts-ignore\nclass RealtimeLayer extends mixin(Layer) {\n constructor(options = {}) {\n super({\n ...options,\n });\n /** @ignore */\n this.onLoad = this.onLoad.bind(this);\n /** @ignore */\n this.onMove = this.onMove.bind(this);\n /** @ignore */\n this.onMoveEnd = this.onMoveEnd.bind(this);\n /** @ignore */\n this.onZoomEnd = this.onZoomEnd.bind(this);\n /** @ignore */\n this.onVisibilityChange = this.onVisibilityChange.bind(this);\n }\n /**\n * Initialize the layer.\n *\n * @param {mapboxgl.Map} map A [mapbox Map](https://docs.mapbox.com/mapbox-gl-js/api/map/).\n * @param {string} beforeId Layer's id before which we want to add the new layer.\n * @override\n */\n // @ts-ignore\n attachToMap(map, beforeId) {\n if (!map) {\n return;\n }\n super.attachToMap(map);\n this.source = {\n type: 'canvas',\n canvas: this.canvas,\n coordinates: getSourceCoordinates(map, this.pixelRatio),\n // Set to true if the canvas source is animated. If the canvas is static, animate should be set to false to improve performance.\n animate: true,\n attribution: this.copyrights && this.copyrights.join(', '),\n };\n this.beforeId = beforeId;\n this.layer = {\n id: this.key,\n type: 'raster',\n source: this.key,\n layout: {\n visibility: this.visible ? 'visible' : 'none',\n },\n paint: {\n 'raster-opacity': 1,\n 'raster-fade-duration': 0,\n 'raster-resampling': 'nearest', // important otherwise it looks blurry\n },\n };\n if (map.isStyleLoaded()) {\n this.onLoad();\n }\n this.map.on('load', this.onLoad);\n this.listeners = [this.on('change:visible', this.onVisibilityChange)];\n }\n /**\n * Remove listeners from the Mapbox Map.\n */\n detachFromMap() {\n if (this.map) {\n this.map.off('load', this.onLoad);\n // @ts-ignore\n this.listeners.forEach((listener) => {\n unByKey(listener);\n });\n if (this.map.style && this.map.getLayer(this.key)) {\n this.map.removeLayer(this.key);\n }\n if (this.map.style && this.map.getSource(this.key)) {\n this.map.removeSource(this.key);\n }\n }\n super.detachFromMap();\n }\n /**\n * Start updating vehicles position.\n *\n * @listens {mapboxgl.map.event:zoomend} Listen to zoom end event.\n * @listens {mapboxgl.map.event:mousemove} Listen to mousemove end.\n * @override\n */\n start() {\n super.start();\n this.map.on('move', this.onMove);\n this.map.on('moveend', this.onMoveEnd);\n this.map.on('zoomend', this.onZoomEnd);\n }\n /**\n * Stop updating vehicles position, and unlisten events.\n *\n * @override\n */\n stop() {\n super.stop();\n if (this.map) {\n this.map.off('move', this.onMove);\n this.map.off('moveend', this.onMoveEnd);\n this.map.off('zoomend', this.onZoomEnd);\n }\n }\n onLoad() {\n if (!this.map.getSource(this.key)) {\n this.map.addSource(this.key, this.source);\n }\n if (!this.map.getLayer(this.key)) {\n this.map.addLayer(this.layer, this.beforeId);\n }\n }\n /**\n * Function triggered when the user moves the cursor over the map.\n * @override\n */\n onUserMoveCallback(evt) {\n super.onUserMoveCallback({\n coordinate: fromLonLat(evt.lngLat.toArray()),\n ...evt,\n });\n }\n /**\n * Render the trajectories using current map's size, resolution and rotation.\n * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions.\n * @overrides\n */\n // @ts-ignore\n renderTrajectories(noInterpolate = false) {\n if (!this.map) {\n return;\n }\n if (!this.pixelRatio) {\n this.pixelRatio = 1;\n }\n const { width, height } = this.map.getCanvas();\n const center = this.map.getCenter();\n // We use turf here to have good transform.\n const leftBottom = this.map.unproject({\n x: 0,\n y: height / this.pixelRatio,\n }); // southWest\n const rightTop = this.map.unproject({\n x: width / this.pixelRatio,\n y: 0,\n }); // north east\n const coord0 = transformRotate(point([leftBottom.lng, leftBottom.lat]), -this.map.getBearing(), {\n pivot: [center.lng, center.lat],\n }).geometry.coordinates;\n const coord1 = transformRotate(point([rightTop.lng, rightTop.lat]), -this.map.getBearing(), {\n pivot: [center.lng, center.lat],\n }).geometry.coordinates;\n const bounds = [...fromLonLat(coord0), ...fromLonLat(coord1)];\n const xResolution = getWidth(bounds) / (width / this.pixelRatio);\n const yResolution = getHeight(bounds) / (height / this.pixelRatio);\n const res = Math.max(xResolution, yResolution);\n // Coordinate of trajectories are in mercator so we have to pass the proper resolution and center in mercator.\n const viewState = {\n size: [width / this.pixelRatio, height / this.pixelRatio],\n center: fromLonLat([center.lng, center.lat]),\n extent: bounds,\n resolution: res,\n zoom: this.getOlZoom(),\n rotation: -(this.map.getBearing() * Math.PI) / 180,\n pixelRatio: this.pixelRatio,\n };\n super.renderTrajectories(viewState, noInterpolate);\n }\n /**\n * Return the delay in ms before the next rendering.\n */\n getRefreshTimeInMs() {\n return super.getRefreshTimeInMs(this.map.getZoom());\n }\n getFeatureInfoAtCoordinate(coordinate, options = {}) {\n const resolution = getMercatorResolution(this.map);\n return super.getFeatureInfoAtCoordinate(coordinate, {\n resolution,\n ...options,\n });\n }\n onVisibilityChange() {\n if (this.visible && !this.map.getLayer(this.key)) {\n this.map.addLayer(this.layer, this.beforeId);\n }\n else if (this.map.getLayer(this.key)) {\n this.map.removeLayer(this.key);\n }\n // We can't use setLayoutProperty it triggers an error probably a bug in mapbox\n // this.map.setLayoutProperty(\n // this.key,\n // 'visibilty',\n // this.visible ? 'visible' : 'none',\n // );\n }\n /**\n * Remove the trajectory form the list if necessary.\n *\n * @private\n */\n purgeTrajectory(trajectory, extent, zoom) {\n return super.purgeTrajectory(trajectory, extent || this.getMercatorExtent(), zoom || Math.floor(this.getOlZoom()));\n }\n /**\n * Send the current bbox to the websocket\n */\n setBbox(extent, zoom) {\n let newExtent = extent;\n let newZoom = zoom;\n if (!newExtent && this.isUpdateBboxOnMoveEnd) {\n newExtent = extent || this.getMercatorExtent();\n newZoom = Math.floor(this.getOlZoom());\n }\n super.setBbox(newExtent, newZoom);\n }\n /**\n * Callback on 'move' event.\n *\n * @private\n */\n onMove() {\n this.renderTrajectories();\n }\n renderTrajectoriesInternal(viewState, noInterpolate = false) {\n const render = super.renderTrajectoriesInternal(viewState, noInterpolate);\n if (render && this.map.style) {\n const extent = getSourceCoordinates(this.map, this.pixelRatio);\n const source = this.map.getSource(this.key);\n if (source) {\n source.setCoordinates(extent);\n }\n }\n return render;\n }\n /**\n * Send the new BBOX to the websocket.\n *\n * @private\n * @override\n */\n onMoveEnd() {\n this.renderTrajectories();\n if (this.visible && this.isUpdateBboxOnMoveEnd) {\n this.setBbox();\n }\n }\n /**\n * Update the cursor style when hovering a vehicle.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n super.onFeatureHover(features, layer, coordinate);\n this.map.getCanvasContainer().style.cursor = features.length\n ? 'pointer'\n : 'auto';\n }\n}\nexport default RealtimeLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/mapbox/layers/RealtimeLayer.js", "access": "public", @@ -6929,7 +6544,7 @@ "lineNumber": 1 }, { - "__docId__": 306, + "__docId__": 289, "kind": "class", "name": "RealtimeLayer", "memberof": "build/mapbox/layers/RealtimeLayer.js", @@ -6956,7 +6571,7 @@ ] }, { - "__docId__": 307, + "__docId__": 290, "kind": "constructor", "name": "constructor", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -6970,7 +6585,7 @@ "undocument": true }, { - "__docId__": 311, + "__docId__": 294, "kind": "member", "name": "onZoomEnd", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -6978,7 +6593,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onZoomEnd", "access": "public", "description": null, - "lineNumber": 37, + "lineNumber": 39, "ignore": true, "type": { "types": [ @@ -6987,7 +6602,7 @@ } }, { - "__docId__": 313, + "__docId__": 296, "kind": "method", "name": "attachToMap", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -6997,7 +6612,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#attachToMap", "access": "public", "description": "Initialize the layer.", - "lineNumber": 49, + "lineNumber": 51, "params": [ { "nullable": null, @@ -7024,7 +6639,7 @@ "return": null }, { - "__docId__": 314, + "__docId__": 297, "kind": "member", "name": "source", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7032,7 +6647,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#source", "access": "public", "description": null, - "lineNumber": 54, + "lineNumber": 56, "undocument": true, "type": { "types": [ @@ -7041,7 +6656,7 @@ } }, { - "__docId__": 315, + "__docId__": 298, "kind": "member", "name": "beforeId", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7049,7 +6664,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#beforeId", "access": "public", "description": null, - "lineNumber": 62, + "lineNumber": 64, "undocument": true, "type": { "types": [ @@ -7058,7 +6673,7 @@ } }, { - "__docId__": 316, + "__docId__": 299, "kind": "member", "name": "layer", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7066,7 +6681,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#layer", "access": "public", "description": null, - "lineNumber": 63, + "lineNumber": 65, "undocument": true, "type": { "types": [ @@ -7075,7 +6690,7 @@ } }, { - "__docId__": 317, + "__docId__": 300, "kind": "member", "name": "listeners", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7083,7 +6698,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#listeners", "access": "public", "description": null, - "lineNumber": 80, + "lineNumber": 82, "undocument": true, "type": { "types": [ @@ -7092,7 +6707,7 @@ } }, { - "__docId__": 318, + "__docId__": 301, "kind": "method", "name": "detachFromMap", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7102,12 +6717,12 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#detachFromMap", "access": "public", "description": "Remove listeners from the Mapbox Map.", - "lineNumber": 85, + "lineNumber": 87, "params": [], "return": null }, { - "__docId__": 319, + "__docId__": 302, "kind": "method", "name": "start", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7117,7 +6732,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#start", "access": "public", "description": "Start updating vehicles position.", - "lineNumber": 108, + "lineNumber": 110, "override": true, "listens": [ { @@ -7137,7 +6752,7 @@ "return": null }, { - "__docId__": 320, + "__docId__": 303, "kind": "method", "name": "stop", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7147,13 +6762,13 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#stop", "access": "public", "description": "Stop updating vehicles position, and unlisten events.", - "lineNumber": 119, + "lineNumber": 121, "override": true, "params": [], "return": null }, { - "__docId__": 321, + "__docId__": 304, "kind": "method", "name": "onLoad", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7163,13 +6778,13 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onLoad", "access": "public", "description": null, - "lineNumber": 127, + "lineNumber": 129, "undocument": true, "params": [], "return": null }, { - "__docId__": 322, + "__docId__": 305, "kind": "method", "name": "onUserMoveCallback", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7179,7 +6794,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onUserMoveCallback", "access": "public", "description": "Function triggered when the user moves the cursor over the map.", - "lineNumber": 139, + "lineNumber": 141, "override": true, "params": [ { @@ -7192,7 +6807,7 @@ "return": null }, { - "__docId__": 323, + "__docId__": 306, "kind": "method", "name": "renderTrajectories", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7202,7 +6817,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#renderTrajectories", "access": "public", "description": "Render the trajectories using current map's size, resolution and rotation.", - "lineNumber": 148, + "lineNumber": 153, "unknown": [ { "tagName": "@overrides", @@ -7224,7 +6839,7 @@ "return": null }, { - "__docId__": 324, + "__docId__": 307, "kind": "member", "name": "pixelRatio", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7232,7 +6847,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#pixelRatio", "access": "public", "description": null, - "lineNumber": 153, + "lineNumber": 158, "undocument": true, "type": { "types": [ @@ -7241,7 +6856,7 @@ } }, { - "__docId__": 325, + "__docId__": 308, "kind": "method", "name": "getRefreshTimeInMs", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7251,7 +6866,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#getRefreshTimeInMs", "access": "public", "description": "Return the delay in ms before the next rendering.", - "lineNumber": 191, + "lineNumber": 196, "params": [], "return": { "types": [ @@ -7260,7 +6875,7 @@ } }, { - "__docId__": 326, + "__docId__": 309, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7270,7 +6885,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#getFeatureInfoAtCoordinate", "access": "public", "description": null, - "lineNumber": 194, + "lineNumber": 199, "undocument": true, "params": [ { @@ -7296,7 +6911,7 @@ } }, { - "__docId__": 327, + "__docId__": 310, "kind": "method", "name": "onVisibilityChange", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7306,13 +6921,13 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onVisibilityChange", "access": "public", "description": null, - "lineNumber": 198, + "lineNumber": 206, "undocument": true, "params": [], "return": null }, { - "__docId__": 328, + "__docId__": 311, "kind": "method", "name": "purgeTrajectory", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7322,7 +6937,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#purgeTrajectory", "access": "private", "description": "Remove the trajectory form the list if necessary.", - "lineNumber": 217, + "lineNumber": 225, "params": [ { "name": "trajectory", @@ -7350,7 +6965,7 @@ } }, { - "__docId__": 329, + "__docId__": 312, "kind": "method", "name": "setBbox", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7360,7 +6975,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#setBbox", "access": "public", "description": "Send the current bbox to the websocket", - "lineNumber": 223, + "lineNumber": 231, "params": [ { "name": "extent", @@ -7378,7 +6993,7 @@ "return": null }, { - "__docId__": 330, + "__docId__": 313, "kind": "method", "name": "onMove", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7388,12 +7003,12 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onMove", "access": "private", "description": "Callback on 'move' event.", - "lineNumber": 237, + "lineNumber": 245, "params": [], "return": null }, { - "__docId__": 331, + "__docId__": 314, "kind": "method", "name": "renderTrajectoriesInternal", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7403,7 +7018,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#renderTrajectoriesInternal", "access": "public", "description": null, - "lineNumber": 240, + "lineNumber": 248, "undocument": true, "params": [ { @@ -7429,7 +7044,7 @@ } }, { - "__docId__": 332, + "__docId__": 315, "kind": "method", "name": "onMoveEnd", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7439,13 +7054,13 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onMoveEnd", "access": "private", "description": "Send the new BBOX to the websocket.", - "lineNumber": 257, + "lineNumber": 265, "override": true, "params": [], "return": null }, { - "__docId__": 333, + "__docId__": 316, "kind": "method", "name": "onFeatureHover", "memberof": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer", @@ -7455,7 +7070,7 @@ "longname": "build/mapbox/layers/RealtimeLayer.js~RealtimeLayer#onFeatureHover", "access": "private", "description": "Update the cursor style when hovering a vehicle.", - "lineNumber": 269, + "lineNumber": 277, "override": true, "params": [ { @@ -7480,7 +7095,7 @@ "return": null }, { - "__docId__": 334, + "__docId__": 317, "kind": "file", "name": "build/mapbox/layers/index.js", "content": "export { default as Layer } from './Layer';\nexport { default as RealtimeLayer } from './RealtimeLayer';\n", @@ -7491,10 +7106,10 @@ "lineNumber": 1 }, { - "__docId__": 335, + "__docId__": 318, "kind": "file", "name": "build/ol/controls/CopyrightControl.js", - "content": "import { inView } from 'ol/layer/Layer';\nimport CopyrightControlCommon from '../../common/controls/CopyrightControlCommon';\nimport removeDuplicate from '../../common/utils/removeDuplicate';\n/**\n * Display layer's copyrights.\n *\n * @example\n * import { Map } from 'ol';\n * import { CopyrightControl } from 'mobility-toolbox-js/ol';\n *\n * const map = new Map({\n * target: 'map',\n * });\n * const control = new CopyrightControl();\n * control.attachToMap(map)\n *\n *\n * @see Openlayers copyright example\n *\n * @extends {CopyrightControlCommon}\n */\nclass CopyrightControl extends CopyrightControlCommon {\n constructor(options) {\n super(options);\n this.onPostRender = this.onPostRender.bind(this);\n }\n getCopyrights() {\n var _a;\n if (!this.frameState) {\n return [];\n }\n let copyrights = [];\n // This code loop comes mainly from ol.\n (_a = this.frameState) === null || _a === void 0 ? void 0 : _a.layerStatesArray.forEach((layerState) => {\n const { layer } = layerState;\n if (this.frameState &&\n inView(layerState, this.frameState.viewState) &&\n layer &&\n layer.getSource &&\n layer.getSource() &&\n layer.getSource().getAttributions()) {\n copyrights = copyrights.concat(layer.getSource().getAttributions()(this.frameState));\n }\n });\n return removeDuplicate(copyrights);\n }\n activate() {\n super.activate();\n if (this.map) {\n this.map.on('postrender', this.onPostRender);\n }\n }\n deactivate() {\n if (this.map) {\n this.map.un('postrender', this.onPostRender);\n }\n }\n onPostRender(evt) {\n if (this.map && this.element) {\n /**\n * @ignore\n */\n this.frameState = evt.frameState || undefined;\n this.render();\n }\n }\n}\nexport default CopyrightControl;\n", + "content": "import { inView } from 'ol/layer/Layer';\nimport CopyrightControlCommon from '../../common/controls/CopyrightControlCommon';\nimport removeDuplicate from '../../common/utils/removeDuplicate';\n/**\n * Display layer's copyrights.\n *\n * @example\n * import { Map } from 'ol';\n * import { CopyrightControl } from 'mobility-toolbox-js/ol';\n *\n * const map = new Map({\n * target: 'map',\n * });\n * const control = new CopyrightControl();\n * control.attachToMap(map)\n *\n *\n * @see Openlayers copyright example\n *\n * @extends {CopyrightControlCommon}\n */\nclass CopyrightControl extends CopyrightControlCommon {\n constructor(options) {\n super(options);\n this.onPostRender = this.onPostRender.bind(this);\n }\n getCopyrights() {\n if (!this.frameState) {\n return [];\n }\n let copyrights = [];\n // This code loop comes mainly from ol.\n this.frameState?.layerStatesArray.forEach((layerState) => {\n const { layer } = layerState;\n if (this.frameState &&\n inView(layerState, this.frameState.viewState) &&\n layer &&\n layer.getSource &&\n layer.getSource() &&\n layer.getSource().getAttributions()) {\n copyrights = copyrights.concat(layer.getSource().getAttributions()(this.frameState));\n }\n });\n return removeDuplicate(copyrights);\n }\n activate() {\n super.activate();\n if (this.map) {\n this.map.on('postrender', this.onPostRender);\n }\n }\n deactivate() {\n if (this.map) {\n this.map.un('postrender', this.onPostRender);\n }\n }\n onPostRender(evt) {\n if (this.map && this.element) {\n /**\n * @ignore\n */\n this.frameState = evt.frameState || undefined;\n this.render();\n }\n }\n}\nexport default CopyrightControl;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/controls/CopyrightControl.js", "access": "public", @@ -7502,7 +7117,7 @@ "lineNumber": 1 }, { - "__docId__": 336, + "__docId__": 319, "kind": "class", "name": "CopyrightControl", "memberof": "build/ol/controls/CopyrightControl.js", @@ -7526,7 +7141,7 @@ ] }, { - "__docId__": 337, + "__docId__": 320, "kind": "constructor", "name": "constructor", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7540,7 +7155,7 @@ "undocument": true }, { - "__docId__": 339, + "__docId__": 322, "kind": "method", "name": "getCopyrights", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7560,7 +7175,7 @@ } }, { - "__docId__": 340, + "__docId__": 323, "kind": "method", "name": "activate", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7570,13 +7185,13 @@ "longname": "build/ol/controls/CopyrightControl.js~CopyrightControl#activate", "access": "public", "description": null, - "lineNumber": 47, + "lineNumber": 46, "undocument": true, "params": [], "return": null }, { - "__docId__": 341, + "__docId__": 324, "kind": "method", "name": "deactivate", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7586,13 +7201,13 @@ "longname": "build/ol/controls/CopyrightControl.js~CopyrightControl#deactivate", "access": "public", "description": null, - "lineNumber": 53, + "lineNumber": 52, "undocument": true, "params": [], "return": null }, { - "__docId__": 342, + "__docId__": 325, "kind": "method", "name": "onPostRender", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7602,7 +7217,7 @@ "longname": "build/ol/controls/CopyrightControl.js~CopyrightControl#onPostRender", "access": "public", "description": null, - "lineNumber": 58, + "lineNumber": 57, "undocument": true, "params": [ { @@ -7615,7 +7230,7 @@ "return": null }, { - "__docId__": 343, + "__docId__": 326, "kind": "member", "name": "frameState", "memberof": "build/ol/controls/CopyrightControl.js~CopyrightControl", @@ -7623,7 +7238,7 @@ "longname": "build/ol/controls/CopyrightControl.js~CopyrightControl#frameState", "access": "public", "description": "", - "lineNumber": 63, + "lineNumber": 62, "ignore": true, "type": { "types": [ @@ -7632,10 +7247,10 @@ } }, { - "__docId__": 344, + "__docId__": 327, "kind": "file", "name": "build/ol/controls/RoutingControl.js", - "content": "import { Feature } from 'ol';\nimport { LineString, Point } from 'ol/geom';\nimport { Modify } from 'ol/interaction';\nimport { unByKey } from 'ol/Observable';\nimport { click } from 'ol/events/condition';\nimport { GeoJSON } from 'ol/format';\nimport { buffer } from 'ol/extent';\nimport { fromLonLat, toLonLat } from 'ol/proj';\nimport GeomType from 'ol/geom/GeometryType';\nimport BaseEvent from 'ol/events/Event';\nimport { RoutingAPI } from '../../api';\nimport ControlCommon from '../../common/controls/ControlCommon';\nimport RoutingLayer from '../layers/RoutingLayer';\n// Examples for a single hop:\n// basel sbb a station named \"basel sbb\"\n// ZUE, station \"Zürich HB\" by its common abbreviation\n// Zürich Hauptbahnhof or HBF Zürich are all valid synonyms für \"Zürich HB\"\n// @47.37811,8.53935 a station at position 47.37811, 8.53935\n// @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935\n// zürich hb@47.37811,8.53935$8 track 8 in station \"Zürich HB\" at position 47.37811, 8.53935\n/** @private */\nconst REGEX_VIA_POINT = /^([^@$!\\n]*)(@?([\\d.]+),([\\d.]+))?(\\$?([a-zA-Z0-9]{0,2}))$/;\n// Examples for a single hop:\n//\n// 47.37811,8.53935 a position 47.37811, 8.53935\n/** @private */\nconst REGEX_VIA_POINT_COORD = /^([\\d.]+),([\\d.]+)$/;\n// Examples for a single hop:\n//\n// !8596126 a station with id 8596126\n// !8596126$4 a station with id 8596126\n/** @private */\nconst REGEX_VIA_POINT_STATION_ID = /^!([^$]*)(\\$?([a-zA-Z0-9]{0,2}))$/;\n/** @private */\nconst STOP_FETCH_ABORT_CONTROLLER_KEY = 'stop-fetch';\n/** @private */\nconst getFlatCoordinatesFromSegments = (segmentArray) => {\n const coords = [];\n segmentArray.forEach((seg) => {\n var _a;\n // @ts-ignore\n const coordArr = (_a = seg.getGeometry()) === null || _a === void 0 ? void 0 : _a.getCoordinates();\n if (coordArr === null || coordArr === void 0 ? void 0 : coordArr.length) {\n coords.push(...coordArr);\n }\n });\n return coords;\n};\n/**\n * Display a route of a specified mean of transport.\n *\n * @example\n * import { Map } from 'ol';\n * import { RoutingControl } from 'mobility-toolbox-js/ol';\n *\n * const map = new Map({\n * target: 'map'\n * });\n *\n * const control = new RoutingControl();\n *\n * control.attachToMap(map)\n *\n * @classproperty {string} apiKey - Key used for RoutingApi requests.\n * @classproperty {string} stopsApiKey - Key used for Stop lookup requests (defaults to apiKey).\n * @classproperty {string} stopsApiUrl - Url used for Stop lookup requests (defaults to https://api.geops.io/stops/v1/lookup/).\n * @classproperty {Array.>} graphs - Array of routing graphs and min/max zoom levels. If you use the control in combination with the [geOps Maps API](https://developer.geops.io/apis/maps/), you may want to use the optimal level of generalizations: \"[['gen4', 0, 8], ['gen3', 8, 9], ['gen2', 9, 11], ['gen1', 11, 13], ['osm', 13, 99]]\"\n * @classproperty {string} mot - Mean of transport to be used for routing.\n * @classproperty {object} routingApiParams - object of additional parameters to pass to the routing api request.\n * @classproperty {object} snapToClosestStation - If true, the routing will snap the coordinate to the closest station. Default to false.\n * @classproperty {boolean} useRawViaPoints - Experimental property. Wen true, it allows the user to add via points using different kind of string. See \"via\" parameter defined by the [geOps Routing API](https://developer.geops.io/apis/routing/). Default to false, only array of coordinates and station's id are supported as via points.\n * @classproperty {RoutingLayer|Layer} routingLayer - Layer for adding route features.\n * @classproperty {function} onRouteError - Callback on error.\n * @classproperty {boolean} loading - True if the control is requesting the backend.\n * @see Openlayers routing example\n *\n * @extends {Control}\n * @implements {RoutingInterface}\n */\nclass RoutingControl extends ControlCommon {\n constructor(options = {}) {\n super(options);\n this.viaPoints = [];\n this.loading = false;\n this.graphs = [];\n this.modify = true;\n this.useRawViaPoints = false;\n this.snapToClosestStation = false;\n this.cacheStationData = {};\n this.abortControllers = {};\n this.segments = [];\n this.format = new GeoJSON({ featureProjection: 'EPSG:3857' });\n this.initialRouteDrag = {};\n Object.defineProperties(this, {\n mot: {\n get: () => this.get('mot'),\n set: (newMot) => {\n if (newMot) {\n this.set('mot', newMot);\n if (this.viaPoints) {\n this.drawRoute();\n }\n }\n },\n },\n loading: {\n get: () => this.get('loading'),\n set: (newLoading) => {\n this.set('loading', newLoading);\n },\n },\n modify: {\n get: () => this.get('modify'),\n set: (modify) => {\n this.set('modify', modify);\n },\n },\n });\n /** True if the control is requesting the backend. */\n this.loading = false;\n /** @ignore */\n this.graphs = options.graphs || [['osm', 0, 99]];\n /** @ignore */\n this.mot = options.mot || 'bus';\n /** @ignore */\n this.modify = options.modify !== false;\n /** @ignore */\n this.routingApiParams = options.routingApiParams;\n /** @ignore */\n this.useRawViaPoints = options.useRawViaPoints || false;\n /** @ignore */\n this.snapToClosestStation = options.snapToClosestStation || false;\n /** @ignore */\n this.apiKey = options.apiKey;\n /** @ignore */\n this.stopsApiKey = options.stopsApiKey || this.apiKey;\n /** @ignore */\n this.stopsApiUrl = options.stopsApiUrl || 'https://api.geops.io/stops/v1/';\n /** @ignore */\n this.api = new RoutingAPI(Object.assign({}, options));\n /** @ignore */\n this.routingLayer =\n options.routingLayer ||\n new RoutingLayer({\n name: 'routing-layer',\n style: options.style,\n });\n /** @ignore */\n this.onRouteError =\n options.onRouteError ||\n ((error) => {\n this.dispatchEvent(new BaseEvent('change:route'));\n this.reset();\n // eslint-disable-next-line no-console\n console.error(error);\n });\n /** @ignore */\n this.onMapClick = this.onMapClick.bind(this);\n /** @ignore */\n this.onModifyEnd = this.onModifyEnd.bind(this);\n /** @ignore */\n this.onModifyStart = this.onModifyStart.bind(this);\n /** @ignore */\n this.createModifyInteraction();\n }\n /**\n * Calculate at which resolutions corresponds each generalizations.\n *\n * @private\n */\n static getGraphsResolutions(graphs, map) {\n const view = map.getView();\n return graphs.map(([, minZoom, maxZoom]) => [\n view.getResolutionForZoom(minZoom),\n view.getResolutionForZoom(maxZoom || minZoom + 1),\n ]);\n }\n /**\n * Adds/Replaces a viaPoint to the viaPoints array and redraws route:\n * Adds a viaPoint at end of array by default.\n * If an index is passed a viaPoint is added at the specified index.\n * If an index is passed and overwrite x is > 0, x viaPoints at the specified\n * index are replaced with a single new viaPoint.\n * @param {number[]|string} coordinates Array of coordinates\n * @param {number} [index=-1] Integer representing the index of the added viaPoint. If not specified, the viaPoint is added at the end of the array.\n * @param {number} [overwrite=0] Marks the number of viaPoints that are removed at the specified index on add.\n */\n addViaPoint(coordinatesOrString, index = -1, overwrite = 0) {\n /* Add/Insert/Overwrite viapoint and redraw route */\n this.viaPoints.splice(index === -1 ? this.viaPoints.length : index, overwrite, coordinatesOrString);\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Removes a viaPoint at the passed array index and redraws route\n * By default the last viaPoint is removed.\n * @param {number} index Integer representing the index of the viaPoint to delete.\n */\n removeViaPoint(index = (this.viaPoints || []).length - 1) {\n /* Remove viapoint and redraw route */\n if (this.viaPoints.length && this.viaPoints[index]) {\n this.viaPoints.splice(index, 1);\n }\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Replaces the current viaPoints with a new coordinate array.\n * @param {Array>} coordinateArray Array of nested coordinates\n */\n setViaPoints(coordinateArray) {\n this.viaPoints = [...coordinateArray];\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Removes all viaPoints, clears the source and triggers a change event\n */\n reset() {\n var _a, _b, _c;\n // Clear viaPoints and source\n this.abortRequests();\n this.viaPoints = [];\n (_c = (_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.clear();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Aborts viapoint and route requests\n * @private\n */\n abortRequests() {\n var _a;\n // Abort Routing API requests\n this.graphs.forEach((graph) => {\n const graphName = graph[0];\n if (this.abortControllers[graphName]) {\n this.abortControllers[graphName].abort();\n }\n this.abortControllers[graphName] = new AbortController();\n });\n // Abort Stops API requests\n (_a = this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]) === null || _a === void 0 ? void 0 : _a.abort();\n this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY] =\n new AbortController();\n this.loading = false;\n }\n /**\n * Draws route on map using an array of coordinates:\n * If a single coordinate is passed a single point feature is added to map.\n * If two or more coordinates are passed a request to the RoutingAPI fetches\n * the route using the passed coordinates and the current mot.\n * @private\n */\n drawRoute() {\n var _a, _b, _c, _d, _e, _f;\n /* Calls RoutingAPI to draw a route using the viaPoints array */\n this.abortRequests();\n (_c = (_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.clear();\n if (!this.viaPoints.length) {\n return null;\n }\n if (this.viaPoints.length === 1) {\n // Add point for first node\n return this.drawViaPoint(this.viaPoints[0], 0, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]);\n }\n const formattedViaPoints = this.viaPoints.map((viaPoint) => {\n var _a;\n if (Array.isArray(viaPoint)) {\n const projection = (_a = this.map) === null || _a === void 0 ? void 0 : _a.getView().getProjection();\n // viaPoint is a coordinate\n // Coordinates need to be reversed as required by the backend RoutingAPI\n const [lon, lat] = toLonLat(viaPoint, projection);\n return this.snapToClosestStation ? [`@${lat}`, lon] : [lat, lon];\n }\n // viaPoint is a string to use as it is\n return this.useRawViaPoints ? viaPoint : `!${viaPoint}`;\n });\n this.loading = true;\n // Clear source\n (_f = (_e = (_d = this.routingLayer) === null || _d === void 0 ? void 0 : _d.olLayer) === null || _e === void 0 ? void 0 : _e.getSource()) === null || _f === void 0 ? void 0 : _f.clear();\n // Create point features for the viaPoints\n this.viaPoints.forEach((viaPoint, idx) => this.drawViaPoint(viaPoint, idx, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]));\n return Promise.all(this.graphs.map(([graph], index) => {\n const { signal } = this.abortControllers[graph];\n if (!this.api) {\n return Promise.resolve([]);\n }\n return this.api\n .route(Object.assign({ graph, via: `${formattedViaPoints.join('|')}`, mot: this.mot, \n // @ts-ignore missing property in swagger\n 'resolve-hops': false, elevation: false, 'coord-radius': 100.0, 'coord-punish': 1000.0 }, (this.routingApiParams || {})), { signal })\n .then((featureCollection) => {\n var _a, _b, _c, _d;\n this.segments = this.format.readFeatures(featureCollection);\n if (this.mot === 'foot') {\n // Extract unique values from viaPoint target value\n const uniqueVias = this.segments.reduce((resultVias, currentFeat) => {\n const segTrg = currentFeat.get('trg');\n return resultVias.find((via) => via[0] === segTrg[0] && via[1] === segTrg[1])\n ? resultVias\n : [...resultVias, segTrg];\n }, []);\n // Create LineString features from segments with same unique value\n this.segments = uniqueVias.map((via) => {\n const viaSegments = this.segments.filter((seg) => {\n const segTrg = seg.get('trg');\n return segTrg[0] === via[0] && segTrg[1] === via[1];\n });\n const coords = getFlatCoordinatesFromSegments(viaSegments);\n return new Feature({\n geometry: new LineString(coords),\n });\n });\n }\n // Create the new route. This route will be modifiable by the Modifiy interaction.\n const coords = getFlatCoordinatesFromSegments(this.segments);\n const routeFeature = new Feature({\n geometry: new LineString(coords),\n });\n routeFeature.set('graph', graph);\n routeFeature.set('mot', this.mot);\n if (this.graphsResolutions &&\n ((_a = this.graphsResolutions[index]) === null || _a === void 0 ? void 0 : _a.length) >= 2) {\n routeFeature.set('minResolution', this.graphsResolutions[index][0]);\n routeFeature.set('maxResolution', this.graphsResolutions[index][1]);\n }\n (_d = (_c = (_b = this.routingLayer) === null || _b === void 0 ? void 0 : _b.olLayer) === null || _c === void 0 ? void 0 : _c.getSource()) === null || _d === void 0 ? void 0 : _d.addFeature(routeFeature);\n this.loading = false;\n })\n .catch((error) => {\n if (error.name === 'AbortError') {\n // Ignore abort error\n return;\n }\n this.segments = [];\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n });\n }));\n }\n /**\n * Draw a via point. This function can parse all the possibilitiies\n *\n * @private\n */\n drawViaPoint(viaPoint, idx, abortController) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;\n const pointFeature = new Feature();\n pointFeature.set('viaPointIdx', idx);\n // The via point is a coordinate using the current map's projection\n if (Array.isArray(viaPoint)) {\n pointFeature.setGeometry(new Point(viaPoint));\n (_c = (_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n // Possibility to parse:\n //\n // !8596126 a station with id 8596126\n // !8596126$4 a station with id 8596126\n if (!this.useRawViaPoints || REGEX_VIA_POINT_STATION_ID.test(viaPoint)) {\n let stationId;\n let track;\n if (this.useRawViaPoints) {\n [, stationId, , track] =\n REGEX_VIA_POINT_STATION_ID.exec(viaPoint) || [];\n }\n else {\n [stationId, track] = viaPoint.split('$');\n }\n return fetch(`${this.stopsApiUrl}lookup/${stationId}?key=${this.stopsApiKey}`, { signal: abortController.signal })\n .then((res) => res.json())\n .then((stationData) => {\n var _a, _b, _c;\n const { coordinates } = stationData.features[0].geometry;\n this.cacheStationData[viaPoint] = fromLonLat(coordinates);\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(fromLonLat(coordinates)));\n (_c = (_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.addFeature(pointFeature);\n return pointFeature;\n })\n .catch((error) => {\n if (error.name === 'AbortError') {\n // Ignore abort error\n return;\n }\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n });\n }\n // Only when this.useRawViaPoints is true.\n // Possibility to parse:\n //\n // 47.37811,8.53935 a position 47.37811, 8.53935\n if (this.useRawViaPoints && REGEX_VIA_POINT_COORD.test(viaPoint)) {\n const [lat, lon] = REGEX_VIA_POINT_COORD.exec(viaPoint) || [];\n const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], (_d = this.map) === null || _d === void 0 ? void 0 : _d.getView().getProjection());\n pointFeature.setGeometry(new Point(coordinates));\n (_g = (_f = (_e = this.routingLayer) === null || _e === void 0 ? void 0 : _e.olLayer) === null || _f === void 0 ? void 0 : _f.getSource()) === null || _g === void 0 ? void 0 : _g.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n // Only when this.useRawViaPoints is true.\n // It will parse the via point to find some name, id, track coordinates.\n //\n // Possibility to parse:\n //\n // @47.37811,8.53935 a station at position 47.37811, 8.53935\n // @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935\n // zürich hb@47.37811,8.53935$8 track 8 in station \"Zürich HB\" at position 47.37811, 8.53935\n const [, stationName, , lat, lon, , track] = REGEX_VIA_POINT.exec(viaPoint) || [];\n if (lon && lat) {\n const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], (_h = this.map) === null || _h === void 0 ? void 0 : _h.getView().getProjection());\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(coordinates));\n (_l = (_k = (_j = this.routingLayer) === null || _j === void 0 ? void 0 : _j.olLayer) === null || _k === void 0 ? void 0 : _k.getSource()) === null || _l === void 0 ? void 0 : _l.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n if (stationName) {\n return fetch(`${this.stopsApiUrl}?key=${this.stopsApiKey}&q=${stationName}&limit=1`, { signal: abortController.signal })\n .then((res) => res.json())\n .then((stationData) => {\n var _a, _b, _c;\n const { coordinates } = stationData.features[0].geometry;\n this.cacheStationData[viaPoint] = fromLonLat(coordinates);\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(fromLonLat(coordinates)));\n (_c = (_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.addFeature(pointFeature);\n return pointFeature;\n })\n .catch((error) => {\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n return null;\n });\n }\n return Promise.resolve(null);\n }\n /**\n * Used on click on map while control is active:\n * By default adds a viaPoint to the end of array.\n * If an existing viaPoint is clicked removes the clicked viaPoint.\n * @private\n */\n onMapClick(evt) {\n const feats = evt.target.getFeaturesAtPixel(evt.pixel);\n const viaPoint = feats.find((feat) => {\n var _a;\n return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT &&\n feat.get('viaPointIdx') !== undefined;\n });\n if (viaPoint) {\n // Remove existing viaPoint on click and abort viaPoint add\n this.removeViaPoint(viaPoint.get('viaPointIdx'));\n return;\n }\n this.addViaPoint(evt.coordinate);\n }\n /**\n * Used on start of the modify interaction. Stores relevant data\n * in this.initialRouteDrag object\n * @private\n */\n onModifyStart(evt) {\n var _a;\n // When modify start, we search the index of the segment that is modifying.\n let segmentIndex = -1;\n const route = evt.features\n .getArray()\n .find((feat) => { var _a; return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.LINE_STRING; });\n // Find the segment index that is being modified\n if (route && route.getGeometry() && evt.mapBrowserEvent.coordinate) {\n // We use a buff extent to fix floating issues , see https://github.com/openlayers/openlayers/issues/7130#issuecomment-535856422\n const closestExtent = buffer(new Point(\n // @ts-ignore\n (_a = route.getGeometry()) === null || _a === void 0 ? void 0 : _a.getClosestPoint(evt.mapBrowserEvent.coordinate)).getExtent(), 0.001);\n segmentIndex = this.segments.findIndex((segment) => { var _a; return (_a = segment.getGeometry()) === null || _a === void 0 ? void 0 : _a.intersectsExtent(closestExtent); });\n }\n // Find the viaPoint that is being modified\n const viaPoint = (evt.features\n .getArray()\n .filter((feat) => { var _a; return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT; }) ||\n [])[0];\n // Write object with modify info\n /** @ignore */\n this.initialRouteDrag = {\n viaPoint,\n oldRoute: route && route.clone(),\n segmentIndex,\n };\n }\n /**\n * Used on end of the modify interaction. Resolves feature modification:\n * Line drag creates new viaPoint at the final coordinate of drag.\n * Point drag replaces old viaPoint.\n * @private\n */\n onModifyEnd(evt) {\n const coord = evt.mapBrowserEvent.coordinate;\n const { oldRoute, viaPoint, segmentIndex } = this.initialRouteDrag || {};\n // If viaPoint is being relocated overwrite the old viaPoint\n if (viaPoint) {\n return this.addViaPoint(coord, viaPoint.get('viaPointIdx'), 1);\n }\n // In case there is no route overwrite first coordinate\n if (!oldRoute) {\n return this.addViaPoint(coord, 0, 1);\n }\n // We can't add a via point because we haven't found which segment has been modified.\n if (segmentIndex === -1) {\n return Promise.reject(new Error('No segment found'));\n }\n // Insert new viaPoint at the modified segment index + 1\n return this.addViaPoint(coord, (segmentIndex || 0) + 1);\n }\n /**\n * Define a default element.\n *\n * @private\n */\n createDefaultElement() {\n /** @ignore */\n this.element = document.createElement('button');\n this.element.id = 'ol-toggle-routing';\n this.element.innerHTML = 'Toggle Route Control';\n this.element.onclick = () => this.active ? this.deactivate() : this.activate();\n Object.assign(this.element.style, {\n position: 'absolute',\n right: '10px',\n top: '10px',\n });\n }\n /**\n * Create the interaction used to modify vertexes of features.\n * @private\n */\n createModifyInteraction() {\n var _a, _b, _c;\n /**\n * @type {ol.interaction.Modify}\n * @private\n */\n // Define and add modify interaction\n this.modifyInteraction = new Modify({\n source: ((_b = (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) || undefined,\n pixelTolerance: 4,\n hitDetection: (_c = this.routingLayer) === null || _c === void 0 ? void 0 : _c.olLayer,\n deleteCondition: (e) => {\n const feats = e.target.getFeaturesAtPixel(e.pixel, {\n hitTolerance: 5,\n });\n const viaPoint = feats.find((feat) => {\n var _a;\n return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT &&\n feat.get('index');\n });\n if (click(e) && viaPoint) {\n // Remove node & viaPoint if an existing viaPoint was clicked\n this.removeViaPoint(viaPoint.get('index'));\n return true;\n }\n return false;\n },\n });\n this.modifyInteraction.on('modifystart', this.onModifyStart);\n this.modifyInteraction.on('modifyend', this.onModifyEnd);\n this.modifyInteraction.setActive(false);\n }\n /**\n * Add click listener to map.\n * @private\n */\n addListeners() {\n var _a;\n if (!this.modify) {\n return;\n }\n this.removeListeners();\n /** @ignore */\n this.onMapClickKey = (_a = this.map) === null || _a === void 0 ? void 0 : _a.on('singleclick', this.onMapClick);\n }\n /**\n * Remove click listener from map.\n * @private\n */\n removeListeners() {\n if (this.onMapClickKey) {\n unByKey(this.onMapClickKey);\n }\n }\n activate() {\n var _a, _b;\n super.activate();\n if (this.map) {\n /** @ignore */\n this.format = new GeoJSON({\n featureProjection: this.map.getView().getProjection(),\n });\n /** @ignore */\n this.graphsResolutions = RoutingControl.getGraphsResolutions(this.graphs, this.map);\n // Clean the modifyInteraction if present\n if (this.modifyInteraction) {\n this.map.removeInteraction(this.modifyInteraction);\n }\n // Add modify interaction, RoutingLayer and listeners\n (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.attachToMap(this.map);\n if (this.modifyInteraction) {\n this.map.addInteraction(this.modifyInteraction);\n }\n (_b = this.modifyInteraction) === null || _b === void 0 ? void 0 : _b.setActive(this.modify);\n this.addListeners();\n }\n }\n deactivate() {\n var _a;\n if (this.map) {\n // Remove modify interaction, RoutingLayer, listeners and viaPoints\n (_a = this.routingLayer) === null || _a === void 0 ? void 0 : _a.detachFromMap();\n if (this.modifyInteraction) {\n this.map.removeInteraction(this.modifyInteraction);\n }\n this.removeListeners();\n this.reset();\n }\n }\n render() { }\n}\nexport default RoutingControl;\n", + "content": "import { Feature } from 'ol';\nimport { LineString, Point } from 'ol/geom';\nimport { Modify } from 'ol/interaction';\nimport { unByKey } from 'ol/Observable';\nimport { click } from 'ol/events/condition';\nimport { GeoJSON } from 'ol/format';\nimport { buffer } from 'ol/extent';\nimport { fromLonLat, toLonLat } from 'ol/proj';\nimport GeomType from 'ol/geom/GeometryType';\nimport BaseEvent from 'ol/events/Event';\nimport { RoutingAPI } from '../../api';\nimport ControlCommon from '../../common/controls/ControlCommon';\nimport RoutingLayer from '../layers/RoutingLayer';\n// Examples for a single hop:\n// basel sbb a station named \"basel sbb\"\n// ZUE, station \"Zürich HB\" by its common abbreviation\n// Zürich Hauptbahnhof or HBF Zürich are all valid synonyms für \"Zürich HB\"\n// @47.37811,8.53935 a station at position 47.37811, 8.53935\n// @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935\n// zürich hb@47.37811,8.53935$8 track 8 in station \"Zürich HB\" at position 47.37811, 8.53935\n/** @private */\nconst REGEX_VIA_POINT = /^([^@$!\\n]*)(@?([\\d.]+),([\\d.]+))?(\\$?([a-zA-Z0-9]{0,2}))$/;\n// Examples for a single hop:\n//\n// 47.37811,8.53935 a position 47.37811, 8.53935\n/** @private */\nconst REGEX_VIA_POINT_COORD = /^([\\d.]+),([\\d.]+)$/;\n// Examples for a single hop:\n//\n// !8596126 a station with id 8596126\n// !8596126$4 a station with id 8596126\n/** @private */\nconst REGEX_VIA_POINT_STATION_ID = /^!([^$]*)(\\$?([a-zA-Z0-9]{0,2}))$/;\n/** @private */\nconst STOP_FETCH_ABORT_CONTROLLER_KEY = 'stop-fetch';\n/** @private */\nconst getFlatCoordinatesFromSegments = (segmentArray) => {\n const coords = [];\n segmentArray.forEach((seg) => {\n // @ts-ignore\n const coordArr = seg.getGeometry()?.getCoordinates();\n if (coordArr?.length) {\n coords.push(...coordArr);\n }\n });\n return coords;\n};\n/**\n * Display a route of a specified mean of transport.\n *\n * @example\n * import { Map } from 'ol';\n * import { RoutingControl } from 'mobility-toolbox-js/ol';\n *\n * const map = new Map({\n * target: 'map'\n * });\n *\n * const control = new RoutingControl();\n *\n * control.attachToMap(map)\n *\n * @classproperty {string} apiKey - Key used for RoutingApi requests.\n * @classproperty {string} stopsApiKey - Key used for Stop lookup requests (defaults to apiKey).\n * @classproperty {string} stopsApiUrl - Url used for Stop lookup requests (defaults to https://api.geops.io/stops/v1/lookup/).\n * @classproperty {Array.>} graphs - Array of routing graphs and min/max zoom levels. If you use the control in combination with the [geOps Maps API](https://developer.geops.io/apis/maps/), you may want to use the optimal level of generalizations: \"[['gen4', 0, 8], ['gen3', 8, 9], ['gen2', 9, 11], ['gen1', 11, 13], ['osm', 13, 99]]\"\n * @classproperty {string} mot - Mean of transport to be used for routing.\n * @classproperty {object} routingApiParams - object of additional parameters to pass to the routing api request.\n * @classproperty {object} snapToClosestStation - If true, the routing will snap the coordinate to the closest station. Default to false.\n * @classproperty {boolean} useRawViaPoints - Experimental property. Wen true, it allows the user to add via points using different kind of string. See \"via\" parameter defined by the [geOps Routing API](https://developer.geops.io/apis/routing/). Default to false, only array of coordinates and station's id are supported as via points.\n * @classproperty {RoutingLayer|Layer} routingLayer - Layer for adding route features.\n * @classproperty {function} onRouteError - Callback on error.\n * @classproperty {boolean} loading - True if the control is requesting the backend.\n * @see Openlayers routing example\n *\n * @extends {Control}\n * @implements {RoutingInterface}\n */\nclass RoutingControl extends ControlCommon {\n constructor(options = {}) {\n super(options);\n this.viaPoints = [];\n this.loading = false;\n this.graphs = [];\n this.modify = true;\n this.useRawViaPoints = false;\n this.snapToClosestStation = false;\n this.cacheStationData = {};\n this.abortControllers = {};\n this.segments = [];\n this.format = new GeoJSON({ featureProjection: 'EPSG:3857' });\n this.initialRouteDrag = {};\n Object.defineProperties(this, {\n mot: {\n get: () => this.get('mot'),\n set: (newMot) => {\n if (newMot) {\n this.set('mot', newMot);\n if (this.viaPoints) {\n this.drawRoute();\n }\n }\n },\n },\n loading: {\n get: () => this.get('loading'),\n set: (newLoading) => {\n this.set('loading', newLoading);\n },\n },\n modify: {\n get: () => this.get('modify'),\n set: (modify) => {\n this.set('modify', modify);\n },\n },\n });\n /** True if the control is requesting the backend. */\n this.loading = false;\n /** @ignore */\n this.graphs = options.graphs || [['osm', 0, 99]];\n /** @ignore */\n this.mot = options.mot || 'bus';\n /** @ignore */\n this.modify = options.modify !== false;\n /** @ignore */\n this.routingApiParams = options.routingApiParams;\n /** @ignore */\n this.useRawViaPoints = options.useRawViaPoints || false;\n /** @ignore */\n this.snapToClosestStation = options.snapToClosestStation || false;\n /** @ignore */\n this.apiKey = options.apiKey;\n /** @ignore */\n this.stopsApiKey = options.stopsApiKey || this.apiKey;\n /** @ignore */\n this.stopsApiUrl = options.stopsApiUrl || 'https://api.geops.io/stops/v1/';\n /** @ignore */\n this.api = new RoutingAPI({\n ...options,\n });\n /** @ignore */\n this.routingLayer =\n options.routingLayer ||\n new RoutingLayer({\n name: 'routing-layer',\n style: options.style,\n });\n /** @ignore */\n this.onRouteError =\n options.onRouteError ||\n ((error) => {\n this.dispatchEvent(new BaseEvent('change:route'));\n this.reset();\n // eslint-disable-next-line no-console\n console.error(error);\n });\n /** @ignore */\n this.onMapClick = this.onMapClick.bind(this);\n /** @ignore */\n this.onModifyEnd = this.onModifyEnd.bind(this);\n /** @ignore */\n this.onModifyStart = this.onModifyStart.bind(this);\n /** @ignore */\n this.createModifyInteraction();\n }\n /**\n * Calculate at which resolutions corresponds each generalizations.\n *\n * @private\n */\n static getGraphsResolutions(graphs, map) {\n const view = map.getView();\n return graphs.map(([, minZoom, maxZoom]) => [\n view.getResolutionForZoom(minZoom),\n view.getResolutionForZoom(maxZoom || minZoom + 1),\n ]);\n }\n /**\n * Adds/Replaces a viaPoint to the viaPoints array and redraws route:\n * Adds a viaPoint at end of array by default.\n * If an index is passed a viaPoint is added at the specified index.\n * If an index is passed and overwrite x is > 0, x viaPoints at the specified\n * index are replaced with a single new viaPoint.\n * @param {number[]|string} coordinates Array of coordinates\n * @param {number} [index=-1] Integer representing the index of the added viaPoint. If not specified, the viaPoint is added at the end of the array.\n * @param {number} [overwrite=0] Marks the number of viaPoints that are removed at the specified index on add.\n */\n addViaPoint(coordinatesOrString, index = -1, overwrite = 0) {\n /* Add/Insert/Overwrite viapoint and redraw route */\n this.viaPoints.splice(index === -1 ? this.viaPoints.length : index, overwrite, coordinatesOrString);\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Removes a viaPoint at the passed array index and redraws route\n * By default the last viaPoint is removed.\n * @param {number} index Integer representing the index of the viaPoint to delete.\n */\n removeViaPoint(index = (this.viaPoints || []).length - 1) {\n /* Remove viapoint and redraw route */\n if (this.viaPoints.length && this.viaPoints[index]) {\n this.viaPoints.splice(index, 1);\n }\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Replaces the current viaPoints with a new coordinate array.\n * @param {Array>} coordinateArray Array of nested coordinates\n */\n setViaPoints(coordinateArray) {\n this.viaPoints = [...coordinateArray];\n this.drawRoute();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Removes all viaPoints, clears the source and triggers a change event\n */\n reset() {\n // Clear viaPoints and source\n this.abortRequests();\n this.viaPoints = [];\n this.routingLayer?.olLayer?.getSource()?.clear();\n this.dispatchEvent(new BaseEvent('change:route'));\n }\n /**\n * Aborts viapoint and route requests\n * @private\n */\n abortRequests() {\n // Abort Routing API requests\n this.graphs.forEach((graph) => {\n const graphName = graph[0];\n if (this.abortControllers[graphName]) {\n this.abortControllers[graphName].abort();\n }\n this.abortControllers[graphName] = new AbortController();\n });\n // Abort Stops API requests\n this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]?.abort();\n this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY] =\n new AbortController();\n this.loading = false;\n }\n /**\n * Draws route on map using an array of coordinates:\n * If a single coordinate is passed a single point feature is added to map.\n * If two or more coordinates are passed a request to the RoutingAPI fetches\n * the route using the passed coordinates and the current mot.\n * @private\n */\n drawRoute() {\n /* Calls RoutingAPI to draw a route using the viaPoints array */\n this.abortRequests();\n this.routingLayer?.olLayer?.getSource()?.clear();\n if (!this.viaPoints.length) {\n return null;\n }\n if (this.viaPoints.length === 1) {\n // Add point for first node\n return this.drawViaPoint(this.viaPoints[0], 0, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]);\n }\n const formattedViaPoints = this.viaPoints.map((viaPoint) => {\n if (Array.isArray(viaPoint)) {\n const projection = this.map?.getView().getProjection();\n // viaPoint is a coordinate\n // Coordinates need to be reversed as required by the backend RoutingAPI\n const [lon, lat] = toLonLat(viaPoint, projection);\n return this.snapToClosestStation ? [`@${lat}`, lon] : [lat, lon];\n }\n // viaPoint is a string to use as it is\n return this.useRawViaPoints ? viaPoint : `!${viaPoint}`;\n });\n this.loading = true;\n // Clear source\n this.routingLayer?.olLayer?.getSource()?.clear();\n // Create point features for the viaPoints\n this.viaPoints.forEach((viaPoint, idx) => this.drawViaPoint(viaPoint, idx, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]));\n return Promise.all(this.graphs.map(([graph], index) => {\n const { signal } = this.abortControllers[graph];\n if (!this.api) {\n return Promise.resolve([]);\n }\n return this.api\n .route({\n graph,\n via: `${formattedViaPoints.join('|')}`,\n mot: this.mot,\n // @ts-ignore missing property in swagger\n 'resolve-hops': false,\n elevation: false,\n 'coord-radius': 100.0,\n 'coord-punish': 1000.0,\n ...(this.routingApiParams || {}),\n }, { signal })\n .then((featureCollection) => {\n this.segments = this.format.readFeatures(featureCollection);\n if (this.mot === 'foot') {\n // Extract unique values from viaPoint target value\n const uniqueVias = this.segments.reduce((resultVias, currentFeat) => {\n const segTrg = currentFeat.get('trg');\n return resultVias.find((via) => via[0] === segTrg[0] && via[1] === segTrg[1])\n ? resultVias\n : [...resultVias, segTrg];\n }, []);\n // Create LineString features from segments with same unique value\n this.segments = uniqueVias.map((via) => {\n const viaSegments = this.segments.filter((seg) => {\n const segTrg = seg.get('trg');\n return segTrg[0] === via[0] && segTrg[1] === via[1];\n });\n const coords = getFlatCoordinatesFromSegments(viaSegments);\n return new Feature({\n geometry: new LineString(coords),\n });\n });\n }\n // Create the new route. This route will be modifiable by the Modifiy interaction.\n const coords = getFlatCoordinatesFromSegments(this.segments);\n const routeFeature = new Feature({\n geometry: new LineString(coords),\n });\n routeFeature.set('graph', graph);\n routeFeature.set('mot', this.mot);\n if (this.graphsResolutions &&\n this.graphsResolutions[index]?.length >= 2) {\n routeFeature.set('minResolution', this.graphsResolutions[index][0]);\n routeFeature.set('maxResolution', this.graphsResolutions[index][1]);\n }\n this.routingLayer?.olLayer?.getSource()?.addFeature(routeFeature);\n this.loading = false;\n })\n .catch((error) => {\n if (error.name === 'AbortError') {\n // Ignore abort error\n return;\n }\n this.segments = [];\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n });\n }));\n }\n /**\n * Draw a via point. This function can parse all the possibilitiies\n *\n * @private\n */\n drawViaPoint(viaPoint, idx, abortController) {\n const pointFeature = new Feature();\n pointFeature.set('viaPointIdx', idx);\n // The via point is a coordinate using the current map's projection\n if (Array.isArray(viaPoint)) {\n pointFeature.setGeometry(new Point(viaPoint));\n this.routingLayer?.olLayer?.getSource()?.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n // Possibility to parse:\n //\n // !8596126 a station with id 8596126\n // !8596126$4 a station with id 8596126\n if (!this.useRawViaPoints || REGEX_VIA_POINT_STATION_ID.test(viaPoint)) {\n let stationId;\n let track;\n if (this.useRawViaPoints) {\n [, stationId, , track] =\n REGEX_VIA_POINT_STATION_ID.exec(viaPoint) || [];\n }\n else {\n [stationId, track] = viaPoint.split('$');\n }\n return fetch(`${this.stopsApiUrl}lookup/${stationId}?key=${this.stopsApiKey}`, { signal: abortController.signal })\n .then((res) => res.json())\n .then((stationData) => {\n const { coordinates } = stationData.features[0].geometry;\n this.cacheStationData[viaPoint] = fromLonLat(coordinates);\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(fromLonLat(coordinates)));\n this.routingLayer?.olLayer?.getSource()?.addFeature(pointFeature);\n return pointFeature;\n })\n .catch((error) => {\n if (error.name === 'AbortError') {\n // Ignore abort error\n return;\n }\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n });\n }\n // Only when this.useRawViaPoints is true.\n // Possibility to parse:\n //\n // 47.37811,8.53935 a position 47.37811, 8.53935\n if (this.useRawViaPoints && REGEX_VIA_POINT_COORD.test(viaPoint)) {\n const [lat, lon] = REGEX_VIA_POINT_COORD.exec(viaPoint) || [];\n const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], this.map?.getView().getProjection());\n pointFeature.setGeometry(new Point(coordinates));\n this.routingLayer?.olLayer?.getSource()?.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n // Only when this.useRawViaPoints is true.\n // It will parse the via point to find some name, id, track coordinates.\n //\n // Possibility to parse:\n //\n // @47.37811,8.53935 a station at position 47.37811, 8.53935\n // @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935\n // zürich hb@47.37811,8.53935$8 track 8 in station \"Zürich HB\" at position 47.37811, 8.53935\n const [, stationName, , lat, lon, , track] = REGEX_VIA_POINT.exec(viaPoint) || [];\n if (lon && lat) {\n const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], this.map?.getView().getProjection());\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(coordinates));\n this.routingLayer?.olLayer?.getSource()?.addFeature(pointFeature);\n return Promise.resolve(pointFeature);\n }\n if (stationName) {\n return fetch(`${this.stopsApiUrl}?key=${this.stopsApiKey}&q=${stationName}&limit=1`, { signal: abortController.signal })\n .then((res) => res.json())\n .then((stationData) => {\n const { coordinates } = stationData.features[0].geometry;\n this.cacheStationData[viaPoint] = fromLonLat(coordinates);\n pointFeature.set('viaPointTrack', track);\n pointFeature.setGeometry(new Point(fromLonLat(coordinates)));\n this.routingLayer?.olLayer?.getSource()?.addFeature(pointFeature);\n return pointFeature;\n })\n .catch((error) => {\n // Dispatch error event and execute error function\n this.dispatchEvent(new BaseEvent('error'));\n this.onRouteError(error, this);\n this.loading = false;\n return null;\n });\n }\n return Promise.resolve(null);\n }\n /**\n * Used on click on map while control is active:\n * By default adds a viaPoint to the end of array.\n * If an existing viaPoint is clicked removes the clicked viaPoint.\n * @private\n */\n onMapClick(evt) {\n const feats = evt.target.getFeaturesAtPixel(evt.pixel);\n const viaPoint = feats.find((feat) => feat.getGeometry()?.getType() === GeomType.POINT &&\n feat.get('viaPointIdx') !== undefined);\n if (viaPoint) {\n // Remove existing viaPoint on click and abort viaPoint add\n this.removeViaPoint(viaPoint.get('viaPointIdx'));\n return;\n }\n this.addViaPoint(evt.coordinate);\n }\n /**\n * Used on start of the modify interaction. Stores relevant data\n * in this.initialRouteDrag object\n * @private\n */\n onModifyStart(evt) {\n // When modify start, we search the index of the segment that is modifying.\n let segmentIndex = -1;\n const route = evt.features\n .getArray()\n .find((feat) => feat.getGeometry()?.getType() === GeomType.LINE_STRING);\n // Find the segment index that is being modified\n if (route && route.getGeometry() && evt.mapBrowserEvent.coordinate) {\n // We use a buff extent to fix floating issues , see https://github.com/openlayers/openlayers/issues/7130#issuecomment-535856422\n const closestExtent = buffer(new Point(\n // @ts-ignore\n route.getGeometry()?.getClosestPoint(evt.mapBrowserEvent.coordinate)).getExtent(), 0.001);\n segmentIndex = this.segments.findIndex((segment) => segment.getGeometry()?.intersectsExtent(closestExtent));\n }\n // Find the viaPoint that is being modified\n const viaPoint = (evt.features\n .getArray()\n .filter((feat) => feat.getGeometry()?.getType() === GeomType.POINT) ||\n [])[0];\n // Write object with modify info\n /** @ignore */\n this.initialRouteDrag = {\n viaPoint,\n oldRoute: route && route.clone(),\n segmentIndex,\n };\n }\n /**\n * Used on end of the modify interaction. Resolves feature modification:\n * Line drag creates new viaPoint at the final coordinate of drag.\n * Point drag replaces old viaPoint.\n * @private\n */\n onModifyEnd(evt) {\n const coord = evt.mapBrowserEvent.coordinate;\n const { oldRoute, viaPoint, segmentIndex } = this.initialRouteDrag || {};\n // If viaPoint is being relocated overwrite the old viaPoint\n if (viaPoint) {\n return this.addViaPoint(coord, viaPoint.get('viaPointIdx'), 1);\n }\n // In case there is no route overwrite first coordinate\n if (!oldRoute) {\n return this.addViaPoint(coord, 0, 1);\n }\n // We can't add a via point because we haven't found which segment has been modified.\n if (segmentIndex === -1) {\n return Promise.reject(new Error('No segment found'));\n }\n // Insert new viaPoint at the modified segment index + 1\n return this.addViaPoint(coord, (segmentIndex || 0) + 1);\n }\n /**\n * Define a default element.\n *\n * @private\n */\n createDefaultElement() {\n /** @ignore */\n this.element = document.createElement('button');\n this.element.id = 'ol-toggle-routing';\n this.element.innerHTML = 'Toggle Route Control';\n this.element.onclick = () => this.active ? this.deactivate() : this.activate();\n Object.assign(this.element.style, {\n position: 'absolute',\n right: '10px',\n top: '10px',\n });\n }\n /**\n * Create the interaction used to modify vertexes of features.\n * @private\n */\n createModifyInteraction() {\n /**\n * @type {ol.interaction.Modify}\n * @private\n */\n // Define and add modify interaction\n this.modifyInteraction = new Modify({\n source: this.routingLayer?.olLayer?.getSource() || undefined,\n pixelTolerance: 4,\n hitDetection: this.routingLayer?.olLayer,\n deleteCondition: (e) => {\n const feats = e.target.getFeaturesAtPixel(e.pixel, {\n hitTolerance: 5,\n });\n const viaPoint = feats.find((feat) => feat.getGeometry()?.getType() === GeomType.POINT &&\n feat.get('index'));\n if (click(e) && viaPoint) {\n // Remove node & viaPoint if an existing viaPoint was clicked\n this.removeViaPoint(viaPoint.get('index'));\n return true;\n }\n return false;\n },\n });\n this.modifyInteraction.on('modifystart', this.onModifyStart);\n this.modifyInteraction.on('modifyend', this.onModifyEnd);\n this.modifyInteraction.setActive(false);\n }\n /**\n * Add click listener to map.\n * @private\n */\n addListeners() {\n if (!this.modify) {\n return;\n }\n this.removeListeners();\n /** @ignore */\n this.onMapClickKey = this.map?.on('singleclick', this.onMapClick);\n }\n /**\n * Remove click listener from map.\n * @private\n */\n removeListeners() {\n if (this.onMapClickKey) {\n unByKey(this.onMapClickKey);\n }\n }\n activate() {\n super.activate();\n if (this.map) {\n /** @ignore */\n this.format = new GeoJSON({\n featureProjection: this.map.getView().getProjection(),\n });\n /** @ignore */\n this.graphsResolutions = RoutingControl.getGraphsResolutions(this.graphs, this.map);\n // Clean the modifyInteraction if present\n if (this.modifyInteraction) {\n this.map.removeInteraction(this.modifyInteraction);\n }\n // Add modify interaction, RoutingLayer and listeners\n this.routingLayer?.attachToMap(this.map);\n if (this.modifyInteraction) {\n this.map.addInteraction(this.modifyInteraction);\n }\n this.modifyInteraction?.setActive(this.modify);\n this.addListeners();\n }\n }\n deactivate() {\n if (this.map) {\n // Remove modify interaction, RoutingLayer, listeners and viaPoints\n this.routingLayer?.detachFromMap();\n if (this.modifyInteraction) {\n this.map.removeInteraction(this.modifyInteraction);\n }\n this.removeListeners();\n this.reset();\n }\n }\n render() { }\n}\nexport default RoutingControl;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/controls/RoutingControl.js", "access": "public", @@ -7643,7 +7258,7 @@ "lineNumber": 1 }, { - "__docId__": 345, + "__docId__": 328, "kind": "variable", "name": "REGEX_VIA_POINT", "memberof": "build/ol/controls/RoutingControl.js", @@ -7663,7 +7278,7 @@ "ignore": true }, { - "__docId__": 346, + "__docId__": 329, "kind": "variable", "name": "REGEX_VIA_POINT_COORD", "memberof": "build/ol/controls/RoutingControl.js", @@ -7683,7 +7298,7 @@ "ignore": true }, { - "__docId__": 347, + "__docId__": 330, "kind": "variable", "name": "REGEX_VIA_POINT_STATION_ID", "memberof": "build/ol/controls/RoutingControl.js", @@ -7703,7 +7318,7 @@ "ignore": true }, { - "__docId__": 348, + "__docId__": 331, "kind": "variable", "name": "STOP_FETCH_ABORT_CONTROLLER_KEY", "memberof": "build/ol/controls/RoutingControl.js", @@ -7723,7 +7338,7 @@ "ignore": true }, { - "__docId__": 349, + "__docId__": 332, "kind": "function", "name": "getFlatCoordinatesFromSegments", "memberof": "build/ol/controls/RoutingControl.js", @@ -7753,7 +7368,7 @@ "ignore": true }, { - "__docId__": 350, + "__docId__": 333, "kind": "class", "name": "RoutingControl", "memberof": "build/ol/controls/RoutingControl.js", @@ -7770,7 +7385,7 @@ "see": [ "Openlayers routing example" ], - "lineNumber": 80, + "lineNumber": 79, "unknown": [ { "tagName": "@classproperty", @@ -7826,7 +7441,7 @@ ] }, { - "__docId__": 351, + "__docId__": 334, "kind": "constructor", "name": "constructor", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7836,11 +7451,11 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#constructor", "access": "public", "description": null, - "lineNumber": 81, + "lineNumber": 80, "undocument": true }, { - "__docId__": 352, + "__docId__": 335, "kind": "member", "name": "viaPoints", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7848,7 +7463,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#viaPoints", "access": "public", "description": null, - "lineNumber": 83, + "lineNumber": 82, "undocument": true, "type": { "types": [ @@ -7857,7 +7472,7 @@ } }, { - "__docId__": 353, + "__docId__": 336, "kind": "member", "name": "loading", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7865,7 +7480,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#loading", "access": "public", "description": null, - "lineNumber": 84, + "lineNumber": 83, "undocument": true, "type": { "types": [ @@ -7874,7 +7489,7 @@ } }, { - "__docId__": 354, + "__docId__": 337, "kind": "member", "name": "graphs", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7882,7 +7497,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#graphs", "access": "public", "description": null, - "lineNumber": 85, + "lineNumber": 84, "undocument": true, "type": { "types": [ @@ -7891,7 +7506,7 @@ } }, { - "__docId__": 355, + "__docId__": 338, "kind": "member", "name": "modify", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7899,7 +7514,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#modify", "access": "public", "description": null, - "lineNumber": 86, + "lineNumber": 85, "undocument": true, "type": { "types": [ @@ -7908,7 +7523,7 @@ } }, { - "__docId__": 356, + "__docId__": 339, "kind": "member", "name": "useRawViaPoints", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7916,7 +7531,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#useRawViaPoints", "access": "public", "description": null, - "lineNumber": 87, + "lineNumber": 86, "undocument": true, "type": { "types": [ @@ -7925,7 +7540,7 @@ } }, { - "__docId__": 357, + "__docId__": 340, "kind": "member", "name": "snapToClosestStation", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7933,7 +7548,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#snapToClosestStation", "access": "public", "description": null, - "lineNumber": 88, + "lineNumber": 87, "undocument": true, "type": { "types": [ @@ -7942,7 +7557,7 @@ } }, { - "__docId__": 358, + "__docId__": 341, "kind": "member", "name": "cacheStationData", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7950,7 +7565,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#cacheStationData", "access": "public", "description": null, - "lineNumber": 89, + "lineNumber": 88, "undocument": true, "type": { "types": [ @@ -7959,7 +7574,7 @@ } }, { - "__docId__": 359, + "__docId__": 342, "kind": "member", "name": "abortControllers", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7967,7 +7582,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#abortControllers", "access": "public", "description": null, - "lineNumber": 90, + "lineNumber": 89, "undocument": true, "type": { "types": [ @@ -7976,7 +7591,7 @@ } }, { - "__docId__": 360, + "__docId__": 343, "kind": "member", "name": "segments", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -7984,7 +7599,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#segments", "access": "public", "description": null, - "lineNumber": 91, + "lineNumber": 90, "undocument": true, "type": { "types": [ @@ -7993,7 +7608,7 @@ } }, { - "__docId__": 361, + "__docId__": 344, "kind": "member", "name": "format", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8001,7 +7616,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#format", "access": "public", "description": null, - "lineNumber": 92, + "lineNumber": 91, "undocument": true, "type": { "types": [ @@ -8010,7 +7625,7 @@ } }, { - "__docId__": 362, + "__docId__": 345, "kind": "member", "name": "initialRouteDrag", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8018,7 +7633,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#initialRouteDrag", "access": "public", "description": null, - "lineNumber": 93, + "lineNumber": 92, "undocument": true, "type": { "types": [ @@ -8027,7 +7642,7 @@ } }, { - "__docId__": 365, + "__docId__": 348, "kind": "member", "name": "mot", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8035,7 +7650,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#mot", "access": "public", "description": null, - "lineNumber": 124, + "lineNumber": 123, "ignore": true, "type": { "types": [ @@ -8044,7 +7659,7 @@ } }, { - "__docId__": 367, + "__docId__": 350, "kind": "member", "name": "routingApiParams", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8052,7 +7667,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#routingApiParams", "access": "public", "description": null, - "lineNumber": 128, + "lineNumber": 127, "ignore": true, "type": { "types": [ @@ -8061,7 +7676,7 @@ } }, { - "__docId__": 370, + "__docId__": 353, "kind": "member", "name": "apiKey", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8069,7 +7684,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#apiKey", "access": "public", "description": null, - "lineNumber": 134, + "lineNumber": 133, "ignore": true, "type": { "types": [ @@ -8078,7 +7693,7 @@ } }, { - "__docId__": 371, + "__docId__": 354, "kind": "member", "name": "stopsApiKey", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8086,7 +7701,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#stopsApiKey", "access": "public", "description": null, - "lineNumber": 136, + "lineNumber": 135, "ignore": true, "type": { "types": [ @@ -8095,7 +7710,7 @@ } }, { - "__docId__": 372, + "__docId__": 355, "kind": "member", "name": "stopsApiUrl", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8103,7 +7718,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#stopsApiUrl", "access": "public", "description": null, - "lineNumber": 138, + "lineNumber": 137, "ignore": true, "type": { "types": [ @@ -8112,7 +7727,7 @@ } }, { - "__docId__": 373, + "__docId__": 356, "kind": "member", "name": "api", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8120,7 +7735,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#api", "access": "public", "description": null, - "lineNumber": 140, + "lineNumber": 139, "ignore": true, "type": { "types": [ @@ -8129,7 +7744,7 @@ } }, { - "__docId__": 374, + "__docId__": 357, "kind": "member", "name": "routingLayer", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8137,7 +7752,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#routingLayer", "access": "public", "description": null, - "lineNumber": 142, + "lineNumber": 143, "ignore": true, "type": { "types": [ @@ -8146,7 +7761,7 @@ } }, { - "__docId__": 375, + "__docId__": 358, "kind": "member", "name": "onRouteError", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8154,7 +7769,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onRouteError", "access": "public", "description": null, - "lineNumber": 149, + "lineNumber": 150, "ignore": true, "type": { "types": [ @@ -8163,7 +7778,7 @@ } }, { - "__docId__": 379, + "__docId__": 362, "kind": "method", "name": "getGraphsResolutions", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8173,7 +7788,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl.getGraphsResolutions", "access": "private", "description": "Calculate at which resolutions corresponds each generalizations.", - "lineNumber": 171, + "lineNumber": 172, "params": [ { "name": "graphs", @@ -8195,7 +7810,7 @@ } }, { - "__docId__": 380, + "__docId__": 363, "kind": "method", "name": "addViaPoint", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8205,7 +7820,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#addViaPoint", "access": "public", "description": "Adds/Replaces a viaPoint to the viaPoints array and redraws route:\n Adds a viaPoint at end of array by default.\n If an index is passed a viaPoint is added at the specified index.\n If an index is passed and overwrite x is > 0, x viaPoints at the specified\n index are replaced with a single new viaPoint.", - "lineNumber": 188, + "lineNumber": 189, "params": [ { "nullable": null, @@ -8246,7 +7861,7 @@ "return": null }, { - "__docId__": 381, + "__docId__": 364, "kind": "method", "name": "removeViaPoint", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8256,7 +7871,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#removeViaPoint", "access": "public", "description": "Removes a viaPoint at the passed array index and redraws route\nBy default the last viaPoint is removed.", - "lineNumber": 199, + "lineNumber": 200, "params": [ { "nullable": null, @@ -8272,7 +7887,7 @@ "return": null }, { - "__docId__": 382, + "__docId__": 365, "kind": "method", "name": "setViaPoints", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8282,7 +7897,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#setViaPoints", "access": "public", "description": "Replaces the current viaPoints with a new coordinate array.", - "lineNumber": 211, + "lineNumber": 212, "params": [ { "nullable": null, @@ -8298,7 +7913,7 @@ "return": null }, { - "__docId__": 384, + "__docId__": 367, "kind": "method", "name": "reset", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8308,12 +7923,12 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#reset", "access": "public", "description": "Removes all viaPoints, clears the source and triggers a change event", - "lineNumber": 219, + "lineNumber": 220, "params": [], "return": null }, { - "__docId__": 386, + "__docId__": 369, "kind": "method", "name": "abortRequests", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8328,7 +7943,7 @@ "return": null }, { - "__docId__": 388, + "__docId__": 371, "kind": "method", "name": "drawRoute", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8338,7 +7953,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#drawRoute", "access": "private", "description": "Draws route on map using an array of coordinates:\n If a single coordinate is passed a single point feature is added to map.\n If two or more coordinates are passed a request to the RoutingAPI fetches\n the route using the passed coordinates and the current mot.", - "lineNumber": 254, + "lineNumber": 253, "params": [], "return": { "types": [ @@ -8347,7 +7962,7 @@ } }, { - "__docId__": 395, + "__docId__": 378, "kind": "method", "name": "drawViaPoint", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8357,7 +7972,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#drawViaPoint", "access": "private", "description": "Draw a via point. This function can parse all the possibilitiies", - "lineNumber": 348, + "lineNumber": 352, "params": [ { "name": "viaPoint", @@ -8385,7 +8000,7 @@ } }, { - "__docId__": 398, + "__docId__": 381, "kind": "method", "name": "onMapClick", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8395,7 +8010,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onMapClick", "access": "private", "description": "Used on click on map while control is active:\n By default adds a viaPoint to the end of array.\n If an existing viaPoint is clicked removes the clicked viaPoint.", - "lineNumber": 449, + "lineNumber": 450, "params": [ { "name": "evt", @@ -8407,7 +8022,7 @@ "return": null }, { - "__docId__": 399, + "__docId__": 382, "kind": "method", "name": "onModifyStart", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8417,7 +8032,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onModifyStart", "access": "private", "description": "Used on start of the modify interaction. Stores relevant data\nin this.initialRouteDrag object", - "lineNumber": 468, + "lineNumber": 466, "params": [ { "name": "evt", @@ -8429,7 +8044,7 @@ "return": null }, { - "__docId__": 401, + "__docId__": 384, "kind": "method", "name": "onModifyEnd", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8439,7 +8054,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onModifyEnd", "access": "private", "description": "Used on end of the modify interaction. Resolves feature modification:\n Line drag creates new viaPoint at the final coordinate of drag.\n Point drag replaces old viaPoint.", - "lineNumber": 502, + "lineNumber": 499, "params": [ { "name": "evt", @@ -8455,7 +8070,7 @@ } }, { - "__docId__": 402, + "__docId__": 385, "kind": "method", "name": "createDefaultElement", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8465,12 +8080,12 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#createDefaultElement", "access": "private", "description": "Define a default element.", - "lineNumber": 525, + "lineNumber": 522, "params": [], "return": null }, { - "__docId__": 403, + "__docId__": 386, "kind": "member", "name": "element", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8478,7 +8093,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#element", "access": "public", "description": null, - "lineNumber": 527, + "lineNumber": 524, "ignore": true, "type": { "types": [ @@ -8487,7 +8102,7 @@ } }, { - "__docId__": 404, + "__docId__": 387, "kind": "method", "name": "createModifyInteraction", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8497,12 +8112,12 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#createModifyInteraction", "access": "private", "description": "Create the interaction used to modify vertexes of features.", - "lineNumber": 541, + "lineNumber": 538, "params": [], "return": null }, { - "__docId__": 405, + "__docId__": 388, "kind": "member", "name": "modifyInteraction", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8510,7 +8125,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#modifyInteraction", "access": "private", "description": "", - "lineNumber": 548, + "lineNumber": 544, "type": { "nullable": null, "types": [ @@ -8521,7 +8136,7 @@ } }, { - "__docId__": 406, + "__docId__": 389, "kind": "method", "name": "addListeners", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8531,12 +8146,12 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#addListeners", "access": "private", "description": "Add click listener to map.", - "lineNumber": 577, + "lineNumber": 570, "params": [], "return": null }, { - "__docId__": 407, + "__docId__": 390, "kind": "member", "name": "onMapClickKey", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8544,7 +8159,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onMapClickKey", "access": "public", "description": null, - "lineNumber": 584, + "lineNumber": 576, "ignore": true, "type": { "types": [ @@ -8553,7 +8168,7 @@ } }, { - "__docId__": 408, + "__docId__": 391, "kind": "method", "name": "removeListeners", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8563,12 +8178,12 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#removeListeners", "access": "private", "description": "Remove click listener from map.", - "lineNumber": 590, + "lineNumber": 582, "params": [], "return": null }, { - "__docId__": 409, + "__docId__": 392, "kind": "method", "name": "activate", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8578,13 +8193,13 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#activate", "access": "public", "description": null, - "lineNumber": 595, + "lineNumber": 587, "undocument": true, "params": [], "return": null }, { - "__docId__": 411, + "__docId__": 394, "kind": "member", "name": "graphsResolutions", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8592,7 +8207,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#graphsResolutions", "access": "public", "description": null, - "lineNumber": 604, + "lineNumber": 595, "ignore": true, "type": { "types": [ @@ -8601,7 +8216,7 @@ } }, { - "__docId__": 412, + "__docId__": 395, "kind": "method", "name": "deactivate", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8611,13 +8226,13 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#deactivate", "access": "public", "description": null, - "lineNumber": 618, + "lineNumber": 609, "undocument": true, "params": [], "return": null }, { - "__docId__": 413, + "__docId__": 396, "kind": "method", "name": "render", "memberof": "build/ol/controls/RoutingControl.js~RoutingControl", @@ -8627,13 +8242,13 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#render", "access": "public", "description": null, - "lineNumber": 630, + "lineNumber": 620, "undocument": true, "params": [], "return": null }, { - "__docId__": 414, + "__docId__": 397, "kind": "file", "name": "build/ol/controls/StopFinderControl.js", "content": "import { fromLonLat } from 'ol/proj';\nimport StopFinderControlCommon from '../../common/controls/StopFinderControlCommon';\n/**\n * Search stations.\n *\n * @example\n * import { Map } from 'ol';\n * import { StopFinderControl } from 'mobility-toolbox-js/ol';\n *\n * const map = new Map({\n * target: 'map',\n * });\n *\n * const control = new StopFinderControl({\n * apiKey: [yourApiKey]\n * });\n *\n * control.attachToMap(map);\n *\n *\n * @see Openlayers search example\n */\nclass StopFinderControl extends StopFinderControlCommon {\n /**\n * @private\n */\n onSuggestionClick(suggestion) {\n const coord = fromLonLat(suggestion.geometry.coordinates);\n this.map.getView().setCenter(coord);\n }\n}\nexport default StopFinderControl;\n", @@ -8644,7 +8259,7 @@ "lineNumber": 1 }, { - "__docId__": 415, + "__docId__": 398, "kind": "class", "name": "StopFinderControl", "memberof": "build/ol/controls/StopFinderControl.js", @@ -8668,7 +8283,7 @@ ] }, { - "__docId__": 416, + "__docId__": 399, "kind": "method", "name": "onSuggestionClick", "memberof": "build/ol/controls/StopFinderControl.js~StopFinderControl", @@ -8690,7 +8305,7 @@ "return": null }, { - "__docId__": 417, + "__docId__": 400, "kind": "file", "name": "build/ol/controls/index.js", "content": "export { default as CopyrightControl } from './CopyrightControl';\nexport { default as RoutingControl } from './RoutingControl';\nexport { default as StopFinderControl } from './StopFinderControl';\n", @@ -8701,7 +8316,7 @@ "lineNumber": 1 }, { - "__docId__": 418, + "__docId__": 401, "kind": "file", "name": "build/ol/index.js", "content": "export * from '../api';\nexport * from '../common';\nexport * from './controls';\nexport * from './layers';\nexport * from './styles';\n", @@ -8712,10 +8327,10 @@ "lineNumber": 1 }, { - "__docId__": 419, + "__docId__": 402, "kind": "file", "name": "build/ol/layers/Layer.js", - "content": "import { unByKey } from 'ol/Observable';\nimport LayerCommon from '../../common/layers/LayerCommon';\nimport userInteractionsMixin from '../../common/mixins/UserInteractionsLayerMixin';\n/**\n * A class representing a layer to display on an OpenLayers map.\n *\n * @example\n * import { Layer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new Layer({\n * olLayer: ...,\n * });\n *\n * @see Map example\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {LayerCommon}\n */\nclass Layer extends userInteractionsMixin(LayerCommon) {\n /**\n * Constructor.\n *\n * @param {LayerCommonOptions} options\n * @param {ol/layer/Layer~Layer} options.olLayer The layer (required).\n * @param {string} [options.name=uuid()] Layer name. Default use a generated uuid.\n * @param {string} [options.key=uuid().toLowerCase()] Layer key, will use options.name.toLowerCase() if not specified.\n * @param {string} [options.copyright=undefined] Copyright-Statement.\n * @param {Array} [options.children=[]] Sublayers.\n * @param {Object} [options.properties={}] Application-specific layer properties.\n * @param {boolean} [options.visible=true] If true this layer is the currently visible layer on the map.\n */\n constructor(options) {\n var _a;\n super(options);\n (_a = this.olLayer) === null || _a === void 0 ? void 0 : _a.setVisible(this.visible);\n }\n /**\n * Define layer's properties.\n *\n * @ignore\n */\n defineProperties(options) {\n super.defineProperties(options);\n Object.defineProperties(this, {\n olLayer: { value: options.olLayer, writable: true },\n olListenersKeys: {\n value: [],\n },\n });\n }\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n var _a, _b, _c, _d, _e;\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n // Make sure the visiblity is correct\n (_a = this.olLayer) === null || _a === void 0 ? void 0 : _a.setVisible(this.visible);\n if (this.olLayer &&\n !((_d = (_c = (_b = this.map) === null || _b === void 0 ? void 0 : _b.getLayers()) === null || _c === void 0 ? void 0 : _c.getArray()) === null || _d === void 0 ? void 0 : _d.includes(this.olLayer))) {\n this.map.addLayer(this.olLayer);\n }\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', () => {\n if (this.olLayer) {\n this.olLayer.setVisible(this.visible);\n }\n }));\n this.olListenersKeys.push(this.map.getLayers().on('remove', (evt) => {\n if (evt.element === this.olLayer) {\n this.detachFromMap();\n }\n }));\n this.toggleVisibleListeners();\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', this.toggleVisibleListeners));\n // We set the copyright to the source used by the layer.\n if (this.copyrights && this.olLayer) {\n const attributions = this.copyrights || [];\n if (this.olLayer.getLayers) {\n this.olLayer\n .getLayers()\n .getArray()\n .forEach((layer) => {\n var _a;\n // @ts-ignore\n if (layer.getSource) {\n // @ts-ignore\n (_a = layer.getSource()) === null || _a === void 0 ? void 0 : _a.setAttributions(attributions);\n }\n });\n // @ts-ignore\n }\n else if (this.olLayer.getSource) {\n // @ts-ignore\n (_e = this.olLayer.getSource()) === null || _e === void 0 ? void 0 : _e.setAttributions(attributions);\n }\n }\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n detachFromMap() {\n var _a, _b, _c;\n this.deactivateUserInteractions();\n unByKey(this.olListenersKeys);\n if (this.olLayer &&\n ((_c = (_b = (_a = this.map) === null || _a === void 0 ? void 0 : _a.getLayers()) === null || _b === void 0 ? void 0 : _b.getArray()) === null || _c === void 0 ? void 0 : _c.includes(this.olLayer))) {\n this.map.removeLayer(this.olLayer);\n }\n super.detachFromMap();\n }\n activateUserInteractions() {\n var _a, _b;\n this.deactivateUserInteractions();\n if (this.map &&\n this.userInteractions &&\n this.userClickInteractions &&\n ((_a = this.userClickCallbacks) === null || _a === void 0 ? void 0 : _a.length)) {\n this.singleClickListenerKey = this.map.on('singleclick', this.onUserClickCallback);\n this.olListenersKeys.push(this.singleClickListenerKey);\n }\n if (this.map &&\n this.userInteractions &&\n this.userHoverInteractions &&\n ((_b = this.userHoverCallbacks) === null || _b === void 0 ? void 0 : _b.length)) {\n this.pointerMoveListenerKey = this.map.on('pointermove', this.onUserMoveCallback);\n }\n }\n deactivateUserInteractions() {\n unByKey([this.pointerMoveListenerKey, this.singleClickListenerKey]);\n }\n /**\n * Toggle listeners needed when a layer is avisible or not.\n * @private\n */\n toggleVisibleListeners() {\n if (this.visible) {\n this.activateUserInteractions();\n }\n else {\n this.deactivateUserInteractions();\n }\n }\n /**\n * Create a copy of the Layer.\n * @param {Object} newOptions Options to override\n * @return {Layer} A Layer\n */\n clone(newOptions) {\n return new Layer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default Layer;\n", + "content": "import { unByKey } from 'ol/Observable';\nimport LayerCommon from '../../common/layers/LayerCommon';\nimport userInteractionsMixin from '../../common/mixins/UserInteractionsLayerMixin';\n/**\n * A class representing a layer to display on an OpenLayers map.\n *\n * @example\n * import { Layer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new Layer({\n * olLayer: ...,\n * });\n *\n * @see Map example\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {LayerCommon}\n */\nclass Layer extends userInteractionsMixin(LayerCommon) {\n /**\n * Constructor.\n *\n * @param {LayerCommonOptions} options\n * @param {ol/layer/Layer~Layer} options.olLayer The layer (required).\n * @param {string} [options.name=uuid()] Layer name. Default use a generated uuid.\n * @param {string} [options.key=uuid().toLowerCase()] Layer key, will use options.name.toLowerCase() if not specified.\n * @param {string} [options.copyright=undefined] Copyright-Statement.\n * @param {Array} [options.children=[]] Sublayers.\n * @param {Object} [options.properties={}] Application-specific layer properties.\n * @param {boolean} [options.visible=true] If true this layer is the currently visible layer on the map.\n */\n constructor(options) {\n super(options);\n this.olLayer?.setVisible(this.visible);\n }\n /**\n * Define layer's properties.\n *\n * @ignore\n */\n defineProperties(options) {\n super.defineProperties(options);\n Object.defineProperties(this, {\n olLayer: { value: options.olLayer, writable: true },\n olListenersKeys: {\n value: [],\n },\n });\n }\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n // Make sure the visiblity is correct\n this.olLayer?.setVisible(this.visible);\n if (this.olLayer &&\n !this.map?.getLayers()?.getArray()?.includes(this.olLayer)) {\n this.map.addLayer(this.olLayer);\n }\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', () => {\n if (this.olLayer) {\n this.olLayer.setVisible(this.visible);\n }\n }));\n this.olListenersKeys.push(this.map.getLayers().on('remove', (evt) => {\n if (evt.element === this.olLayer) {\n this.detachFromMap();\n }\n }));\n this.toggleVisibleListeners();\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', this.toggleVisibleListeners));\n // We set the copyright to the source used by the layer.\n if (this.copyrights && this.olLayer) {\n const attributions = this.copyrights || [];\n if (this.olLayer.getLayers) {\n this.olLayer\n .getLayers()\n .getArray()\n .forEach((layer) => {\n // @ts-ignore\n if (layer.getSource) {\n // @ts-ignore\n layer.getSource()?.setAttributions(attributions);\n }\n });\n // @ts-ignore\n }\n else if (this.olLayer.getSource) {\n // @ts-ignore\n this.olLayer.getSource()?.setAttributions(attributions);\n }\n }\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n detachFromMap() {\n this.deactivateUserInteractions();\n unByKey(this.olListenersKeys);\n if (this.olLayer &&\n this.map?.getLayers()?.getArray()?.includes(this.olLayer)) {\n this.map.removeLayer(this.olLayer);\n }\n super.detachFromMap();\n }\n activateUserInteractions() {\n this.deactivateUserInteractions();\n if (this.map &&\n this.userInteractions &&\n this.userClickInteractions &&\n this.userClickCallbacks?.length) {\n this.singleClickListenerKey = this.map.on('singleclick', this.onUserClickCallback);\n this.olListenersKeys.push(this.singleClickListenerKey);\n }\n if (this.map &&\n this.userInteractions &&\n this.userHoverInteractions &&\n this.userHoverCallbacks?.length) {\n this.pointerMoveListenerKey = this.map.on('pointermove', this.onUserMoveCallback);\n }\n }\n deactivateUserInteractions() {\n unByKey([this.pointerMoveListenerKey, this.singleClickListenerKey]);\n }\n /**\n * Toggle listeners needed when a layer is avisible or not.\n * @private\n */\n toggleVisibleListeners() {\n if (this.visible) {\n this.activateUserInteractions();\n }\n else {\n this.deactivateUserInteractions();\n }\n }\n /**\n * Create a copy of the Layer.\n * @param {Object} newOptions Options to override\n * @return {Layer} A Layer\n */\n clone(newOptions) {\n return new Layer({ ...this.options, ...newOptions });\n }\n}\nexport default Layer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/Layer.js", "access": "public", @@ -8723,7 +8338,7 @@ "lineNumber": 1 }, { - "__docId__": 420, + "__docId__": 403, "kind": "class", "name": "Layer", "memberof": "build/ol/layers/Layer.js", @@ -8753,7 +8368,7 @@ ] }, { - "__docId__": 421, + "__docId__": 404, "kind": "constructor", "name": "constructor", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8860,7 +8475,7 @@ ] }, { - "__docId__": 422, + "__docId__": 405, "kind": "method", "name": "defineProperties", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8870,7 +8485,7 @@ "longname": "build/ol/layers/Layer.js~Layer#defineProperties", "access": "public", "description": "Define layer's properties.", - "lineNumber": 42, + "lineNumber": 41, "ignore": true, "params": [ { @@ -8883,7 +8498,7 @@ "return": null }, { - "__docId__": 423, + "__docId__": 406, "kind": "method", "name": "attachToMap", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8893,7 +8508,7 @@ "longname": "build/ol/layers/Layer.js~Layer#attachToMap", "access": "public", "description": "Initialize the layer and listen to feature clicks.", - "lineNumber": 55, + "lineNumber": 54, "params": [ { "nullable": null, @@ -8909,7 +8524,7 @@ "return": null }, { - "__docId__": 424, + "__docId__": 407, "kind": "method", "name": "detachFromMap", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8919,12 +8534,12 @@ "longname": "build/ol/layers/Layer.js~Layer#detachFromMap", "access": "public", "description": "Terminate what was initialized in init function. Remove layer, events...", - "lineNumber": 109, + "lineNumber": 106, "params": [], "return": null }, { - "__docId__": 425, + "__docId__": 408, "kind": "method", "name": "activateUserInteractions", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8934,13 +8549,13 @@ "longname": "build/ol/layers/Layer.js~Layer#activateUserInteractions", "access": "public", "description": null, - "lineNumber": 119, + "lineNumber": 115, "undocument": true, "params": [], "return": null }, { - "__docId__": 426, + "__docId__": 409, "kind": "member", "name": "singleClickListenerKey", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8948,7 +8563,7 @@ "longname": "build/ol/layers/Layer.js~Layer#singleClickListenerKey", "access": "public", "description": null, - "lineNumber": 126, + "lineNumber": 121, "undocument": true, "type": { "types": [ @@ -8957,7 +8572,7 @@ } }, { - "__docId__": 427, + "__docId__": 410, "kind": "member", "name": "pointerMoveListenerKey", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8965,7 +8580,7 @@ "longname": "build/ol/layers/Layer.js~Layer#pointerMoveListenerKey", "access": "public", "description": null, - "lineNumber": 133, + "lineNumber": 128, "undocument": true, "type": { "types": [ @@ -8974,7 +8589,7 @@ } }, { - "__docId__": 428, + "__docId__": 411, "kind": "method", "name": "deactivateUserInteractions", "memberof": "build/ol/layers/Layer.js~Layer", @@ -8984,13 +8599,13 @@ "longname": "build/ol/layers/Layer.js~Layer#deactivateUserInteractions", "access": "public", "description": null, - "lineNumber": 136, + "lineNumber": 131, "undocument": true, "params": [], "return": null }, { - "__docId__": 429, + "__docId__": 412, "kind": "method", "name": "toggleVisibleListeners", "memberof": "build/ol/layers/Layer.js~Layer", @@ -9000,12 +8615,12 @@ "longname": "build/ol/layers/Layer.js~Layer#toggleVisibleListeners", "access": "private", "description": "Toggle listeners needed when a layer is avisible or not.", - "lineNumber": 143, + "lineNumber": 138, "params": [], "return": null }, { - "__docId__": 430, + "__docId__": 413, "kind": "method", "name": "clone", "memberof": "build/ol/layers/Layer.js~Layer", @@ -9015,7 +8630,7 @@ "longname": "build/ol/layers/Layer.js~Layer#clone", "access": "public", "description": "Create a copy of the Layer.", - "lineNumber": 156, + "lineNumber": 151, "params": [ { "nullable": null, @@ -9038,10 +8653,10 @@ } }, { - "__docId__": 431, + "__docId__": 414, "kind": "file", "name": "build/ol/layers/MapGlLayer.js", - "content": "/* eslint-disable max-classes-per-file */\nimport { toLonLat } from 'ol/proj';\nimport OlLayer from 'ol/layer/Layer';\nimport Source from 'ol/source/Source';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport BaseEvent from 'ol/events/Event';\nimport { getUrlWithParams, getMapboxMapCopyrights } from '../../common/utils';\nimport Layer from './Layer';\n/**\n * Common class for Mapbox and Maplibre and potential other fork from Mapbox.\n * It's used to share code between Mapbox and Maplibre layers without importing both libs.\n */\nclass MapGlLayer extends Layer {\n constructor(options) {\n super(options);\n this.olLayer = new OlLayer({\n source: new Source({}),\n render: this.getOlLayerRender(),\n });\n /**\n * Url of the mapbox style.\n * @type {string}\n * @private\n */\n this.styleUrl = options.url;\n /**\n * Api key for the url of the mapbox style.\n * If set to false, the apiKey is not required.\n * @type {string}\n * @private\n */\n this.apiKey = options.apiKey;\n /**\n * Name of the apiKey to set in the url request.\n * Default is 'key'.\n * @type {string}\n * @private\n */\n this.apiKeyName = options.apiKeyName || 'key';\n /** @ignore */\n this.updateAttribution = this.updateAttribution.bind(this);\n }\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n /**\n * The feature format.\n * @type {ol/format/GeoJSON}\n */\n this.format = new GeoJSON({\n featureProjection: this.map.getView().getProjection(),\n });\n this.loadMbMap();\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n detachFromMap() {\n if (this.mbMap) {\n this.mbMap.off('idle', this.updateAttribution);\n // Some asynchrone repaints are triggered even if the mbMap has been removed,\n // to avoid display of errors we set an empty function.\n this.mbMap.triggerRepaint = () => { };\n this.mbMap.remove();\n this.mbMap = undefined;\n }\n this.loaded = false;\n super.detachFromMap();\n }\n /**\n * Create the mapbox map.\n * @private\n */\n loadMbMap() {\n var _a, _b, _c;\n this.olListenersKeys.push(\n // @ts-ignore\n (_a = this.map) === null || _a === void 0 ? void 0 : _a.on('change:target', () => {\n this.loadMbMap();\n }));\n if (!((_b = this.map) === null || _b === void 0 ? void 0 : _b.getTargetElement())) {\n return;\n }\n if (!this.visible) {\n // On next change of visibility we load the map\n this.olListenersKeys.push(\n // @ts-ignore\n this.once('change:visible', () => {\n this.loadMbMap();\n }));\n return;\n }\n const container = document.createElement('div');\n container.style.position = 'absolute';\n container.style.width = '100%';\n container.style.height = '100%';\n if (!this.styleUrl) {\n // eslint-disable-next-line no-console\n console.error(`No styleUrl defined for mapbox layer: ${this.styleUrl}`);\n return;\n }\n if (!this.apiKey && !((_c = this.styleUrl) === null || _c === void 0 ? void 0 : _c.includes(this.apiKeyName))) {\n // eslint-disable-next-line no-console\n console.error(`No apiKey defined for mapbox layer with style url to ${this.styleUrl}`);\n }\n const Map = this.getMapboxMapClass();\n /**\n * A mapbox map\n * @type {mapboxgl.Map}\n */\n this.mbMap = new Map(Object.assign({ style: getUrlWithParams(this.styleUrl, {\n [this.apiKeyName]: this.apiKey,\n }).toString(), container, interactive: false, trackResize: false, attributionControl: false }, (this.options.mapOptions || {})));\n this.mbMap.once('load', () => {\n /**\n * Is the map loaded.\n * @type {boolean}\n */\n this.loaded = true;\n this.dispatchEvent(new BaseEvent('load'));\n });\n this.mbMap.on('idle', this.updateAttribution);\n }\n /**\n * Update attributions of the source.\n * @private\n */\n updateAttribution(evt) {\n var _a, _b, _c;\n const newAttributions = getMapboxMapCopyrights(evt.target) || [];\n if (((_a = this.copyrights) === null || _a === void 0 ? void 0 : _a.toString()) !== newAttributions.toString()) {\n this.copyrights = newAttributions;\n // @ts-ignore\n (_c = (_b = this.olLayer) === null || _b === void 0 ? void 0 : _b.getSource()) === null || _c === void 0 ? void 0 : _c.setAttributions(newAttributions);\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate Coordinate to request the information at.\n * @param {Object} options A [mapboxgl.Map#queryrenderedfeatures](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#queryrenderedfeatures) options parameter.\n * @return {Promise} Promise with features, layer and coordinate. The original Mapbox feature is available as a property named 'mapboxFeature'.\n */\n getFeatureInfoAtCoordinate(coordinate, options) {\n // Ignore the getFeatureInfo until the mapbox map is loaded\n if (!options ||\n !this.format ||\n !this.mbMap ||\n !this.mbMap.isStyleLoaded()) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n const pixel = coordinate &&\n this.mbMap.project(toLonLat(coordinate));\n let pixels;\n if (this.hitTolerance) {\n const { x, y } = pixel;\n pixels = [\n {\n x: x - this.hitTolerance,\n y: y - this.hitTolerance,\n },\n {\n x: x + this.hitTolerance,\n y: y + this.hitTolerance,\n },\n ];\n }\n // At this point we get GeoJSON Mapbox feature, we transform it to an OpenLayers\n // feature to be consistent with other layers.\n const features = this.mbMap\n // @ts-ignore\n .queryRenderedFeatures(pixels || pixel, options)\n .map((feature) => {\n const olFeature = this.format.readFeature(feature);\n if (olFeature) {\n // We save the original mapbox feature to avoid losing informations\n // potentially needed for other functionnality like highlighting\n // (id, layer id, source, sourceLayer ...)\n olFeature.set('mapboxFeature', feature);\n }\n return olFeature;\n });\n return Promise.resolve({\n layer: this,\n features,\n coordinate,\n });\n }\n /**\n * Return the render function function for the ol layer.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n getOlLayerRender() {\n // eslint-disable-next-line no-console\n console.error('This function must be implemented in subclasses');\n const div = document.createElement('div');\n return () => div;\n }\n /**\n * Return the Class to instanciate for the mapbox map.\n *\n * @return {mapboxgl.Map|maplibregl.Map} map\n */\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n // eslint-disable-next-line no-console\n console.error('This function must be implemented in subclasses');\n // @ts-ignore\n return null;\n }\n}\nexport default MapGlLayer;\n", + "content": "/* eslint-disable max-classes-per-file */\nimport { toLonLat } from 'ol/proj';\nimport OlLayer from 'ol/layer/Layer';\nimport Source from 'ol/source/Source';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport BaseEvent from 'ol/events/Event';\nimport { getUrlWithParams, getMapboxMapCopyrights } from '../../common/utils';\nimport Layer from './Layer';\n/**\n * Common class for Mapbox and Maplibre and potential other fork from Mapbox.\n * It's used to share code between Mapbox and Maplibre layers without importing both libs.\n */\nclass MapGlLayer extends Layer {\n constructor(options) {\n super(options);\n this.olLayer = new OlLayer({\n source: new Source({}),\n render: this.getOlLayerRender(),\n });\n /**\n * Url of the mapbox style.\n * @type {string}\n * @private\n */\n this.styleUrl = options.url;\n /**\n * Api key for the url of the mapbox style.\n * If set to false, the apiKey is not required.\n * @type {string}\n * @private\n */\n this.apiKey = options.apiKey;\n /**\n * Name of the apiKey to set in the url request.\n * Default is 'key'.\n * @type {string}\n * @private\n */\n this.apiKeyName = options.apiKeyName || 'key';\n /** @ignore */\n this.updateAttribution = this.updateAttribution.bind(this);\n }\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n /**\n * The feature format.\n * @type {ol/format/GeoJSON}\n */\n this.format = new GeoJSON({\n featureProjection: this.map.getView().getProjection(),\n });\n this.loadMbMap();\n }\n /**\n * Terminate what was initialized in init function. Remove layer, events...\n */\n detachFromMap() {\n if (this.mbMap) {\n this.mbMap.off('idle', this.updateAttribution);\n // Some asynchrone repaints are triggered even if the mbMap has been removed,\n // to avoid display of errors we set an empty function.\n this.mbMap.triggerRepaint = () => { };\n this.mbMap.remove();\n this.mbMap = undefined;\n }\n this.loaded = false;\n super.detachFromMap();\n }\n /**\n * Create the mapbox map.\n * @private\n */\n loadMbMap() {\n this.olListenersKeys.push(\n // @ts-ignore\n this.map?.on('change:target', () => {\n this.loadMbMap();\n }));\n if (!this.map?.getTargetElement()) {\n return;\n }\n if (!this.visible) {\n // On next change of visibility we load the map\n this.olListenersKeys.push(\n // @ts-ignore\n this.once('change:visible', () => {\n this.loadMbMap();\n }));\n return;\n }\n const container = document.createElement('div');\n container.style.position = 'absolute';\n container.style.width = '100%';\n container.style.height = '100%';\n if (!this.styleUrl) {\n // eslint-disable-next-line no-console\n console.error(`No styleUrl defined for mapbox layer: ${this.styleUrl}`);\n return;\n }\n if (!this.apiKey && !this.styleUrl?.includes(this.apiKeyName)) {\n // eslint-disable-next-line no-console\n console.error(`No apiKey defined for mapbox layer with style url to ${this.styleUrl}`);\n }\n const Map = this.getMapboxMapClass();\n /**\n * A mapbox map\n * @type {mapboxgl.Map}\n */\n this.mbMap = new Map({\n style: getUrlWithParams(this.styleUrl, {\n [this.apiKeyName]: this.apiKey,\n }).toString(),\n container,\n interactive: false,\n trackResize: false,\n attributionControl: false,\n ...(this.options.mapOptions || {}),\n });\n this.mbMap.once('load', () => {\n /**\n * Is the map loaded.\n * @type {boolean}\n */\n this.loaded = true;\n this.dispatchEvent(new BaseEvent('load'));\n });\n this.mbMap.on('idle', this.updateAttribution);\n }\n /**\n * Update attributions of the source.\n * @private\n */\n updateAttribution(evt) {\n const newAttributions = getMapboxMapCopyrights(evt.target) || [];\n if (this.copyrights?.toString() !== newAttributions.toString()) {\n this.copyrights = newAttributions;\n // @ts-ignore\n this.olLayer?.getSource()?.setAttributions(newAttributions);\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate Coordinate to request the information at.\n * @param {Object} options A [mapboxgl.Map#queryrenderedfeatures](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#queryrenderedfeatures) options parameter.\n * @return {Promise} Promise with features, layer and coordinate. The original Mapbox feature is available as a property named 'mapboxFeature'.\n */\n getFeatureInfoAtCoordinate(coordinate, options) {\n // Ignore the getFeatureInfo until the mapbox map is loaded\n if (!options ||\n !this.format ||\n !this.mbMap ||\n !this.mbMap.isStyleLoaded()) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n const pixel = coordinate &&\n this.mbMap.project(toLonLat(coordinate));\n let pixels;\n if (this.hitTolerance) {\n const { x, y } = pixel;\n pixels = [\n {\n x: x - this.hitTolerance,\n y: y - this.hitTolerance,\n },\n {\n x: x + this.hitTolerance,\n y: y + this.hitTolerance,\n },\n ];\n }\n // At this point we get GeoJSON Mapbox feature, we transform it to an OpenLayers\n // feature to be consistent with other layers.\n const features = this.mbMap\n // @ts-ignore\n .queryRenderedFeatures(pixels || pixel, options)\n .map((feature) => {\n const olFeature = this.format.readFeature(feature);\n if (olFeature) {\n // We save the original mapbox feature to avoid losing informations\n // potentially needed for other functionnality like highlighting\n // (id, layer id, source, sourceLayer ...)\n olFeature.set('mapboxFeature', feature);\n }\n return olFeature;\n });\n return Promise.resolve({\n layer: this,\n features,\n coordinate,\n });\n }\n /**\n * Return the render function function for the ol layer.\n *\n */\n // eslint-disable-next-line class-methods-use-this\n getOlLayerRender() {\n // eslint-disable-next-line no-console\n console.error('This function must be implemented in subclasses');\n const div = document.createElement('div');\n return () => div;\n }\n /**\n * Return the Class to instanciate for the mapbox map.\n *\n * @return {mapboxgl.Map|maplibregl.Map} map\n */\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n // eslint-disable-next-line no-console\n console.error('This function must be implemented in subclasses');\n // @ts-ignore\n return null;\n }\n}\nexport default MapGlLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/MapGlLayer.js", "access": "public", @@ -9049,7 +8664,7 @@ "lineNumber": 1 }, { - "__docId__": 432, + "__docId__": 415, "kind": "class", "name": "MapGlLayer", "memberof": "build/ol/layers/MapGlLayer.js", @@ -9067,7 +8682,7 @@ ] }, { - "__docId__": 433, + "__docId__": 416, "kind": "constructor", "name": "constructor", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9081,7 +8696,7 @@ "undocument": true }, { - "__docId__": 434, + "__docId__": 417, "kind": "member", "name": "olLayer", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9098,7 +8713,7 @@ } }, { - "__docId__": 435, + "__docId__": 418, "kind": "member", "name": "styleUrl", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9117,7 +8732,7 @@ } }, { - "__docId__": 436, + "__docId__": 419, "kind": "member", "name": "apiKey", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9136,7 +8751,7 @@ } }, { - "__docId__": 437, + "__docId__": 420, "kind": "member", "name": "apiKeyName", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9155,7 +8770,7 @@ } }, { - "__docId__": 439, + "__docId__": 422, "kind": "method", "name": "attachToMap", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9181,7 +8796,7 @@ "return": null }, { - "__docId__": 440, + "__docId__": 423, "kind": "member", "name": "format", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9200,7 +8815,7 @@ } }, { - "__docId__": 441, + "__docId__": 424, "kind": "method", "name": "detachFromMap", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9215,7 +8830,7 @@ "return": null }, { - "__docId__": 442, + "__docId__": 425, "kind": "member", "name": "mbMap", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9232,7 +8847,7 @@ } }, { - "__docId__": 443, + "__docId__": 426, "kind": "member", "name": "loaded", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9249,7 +8864,7 @@ } }, { - "__docId__": 444, + "__docId__": 427, "kind": "method", "name": "loadMbMap", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9264,7 +8879,7 @@ "return": null }, { - "__docId__": 447, + "__docId__": 430, "kind": "method", "name": "updateAttribution", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9274,7 +8889,7 @@ "longname": "build/ol/layers/MapGlLayer.js~MapGlLayer#updateAttribution", "access": "private", "description": "Update attributions of the source.", - "lineNumber": 134, + "lineNumber": 140, "params": [ { "name": "evt", @@ -9286,7 +8901,7 @@ "return": null }, { - "__docId__": 448, + "__docId__": 431, "kind": "member", "name": "copyrights", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9294,7 +8909,7 @@ "longname": "build/ol/layers/MapGlLayer.js~MapGlLayer#copyrights", "access": "public", "description": null, - "lineNumber": 138, + "lineNumber": 143, "undocument": true, "type": { "types": [ @@ -9303,7 +8918,7 @@ } }, { - "__docId__": 449, + "__docId__": 432, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9313,7 +8928,7 @@ "longname": "build/ol/layers/MapGlLayer.js~MapGlLayer#getFeatureInfoAtCoordinate", "access": "public", "description": "Request feature information for a given coordinate.", - "lineNumber": 149, + "lineNumber": 154, "params": [ { "nullable": null, @@ -9346,7 +8961,7 @@ } }, { - "__docId__": 450, + "__docId__": 433, "kind": "method", "name": "getOlLayerRender", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9356,7 +8971,7 @@ "longname": "build/ol/layers/MapGlLayer.js~MapGlLayer#getOlLayerRender", "access": "public", "description": "Return the render function function for the ol layer.", - "lineNumber": 199, + "lineNumber": 204, "params": [], "return": { "types": [ @@ -9365,7 +8980,7 @@ } }, { - "__docId__": 451, + "__docId__": 434, "kind": "method", "name": "getMapboxMapClass", "memberof": "build/ol/layers/MapGlLayer.js~MapGlLayer", @@ -9375,7 +8990,7 @@ "longname": "build/ol/layers/MapGlLayer.js~MapGlLayer#getMapboxMapClass", "access": "public", "description": "Return the Class to instanciate for the mapbox map.", - "lineNumber": 211, + "lineNumber": 216, "return": { "nullable": null, "types": [ @@ -9388,10 +9003,10 @@ "params": [] }, { - "__docId__": 452, + "__docId__": 435, "kind": "file", "name": "build/ol/layers/MapboxLayer.js", - "content": "/* eslint-disable no-underscore-dangle */\nimport { Map } from 'mapbox-gl';\nimport { getMapboxMapCopyrights, getMapboxRender } from '../../common/utils';\nimport MapGlLayer from './MapGlLayer';\n/**\n * A class representing Mapboxlayer to display on BasicMap\n *\n * @example\n * import { MapboxLayer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new MapboxLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json',\n * apikey: 'yourApiKey',\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nexport default class MapboxLayer extends MapGlLayer {\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n this.olListenersKeys.push(this.map.on('change:size', () => {\n try {\n if (this.mbMap) {\n this.mbMap.resize();\n }\n }\n catch (err) {\n // ignore render errors\n // eslint-disable-next-line no-console\n console.warn(err);\n }\n }));\n }\n /**\n * Create the mapbox map.\n * @private\n */\n loadMbMap() {\n var _a;\n // If the map hasn't been resized, the center could be [NaN,NaN].\n // We set default good value for the mapbox map, to avoid the app crashes.\n let [x, y] = ((_a = this.map) === null || _a === void 0 ? void 0 : _a.getView().getCenter()) || [];\n if (!x || !y) {\n x = 0;\n y = 0;\n }\n // Options the last render run did happen. If something changes\n // we have to render again\n /** @ignore */\n this.renderState = {\n center: [x, y],\n zoom: undefined,\n rotation: undefined,\n visible: undefined,\n opacity: undefined,\n size: [0, 0],\n };\n super.loadMbMap();\n if (!this.mbMap) {\n // mbMap could ne bull if the map is not in the dom yet.\n return;\n }\n this.mbMap.once('load', () => {\n var _a, _b;\n if (!this.mbMap) {\n return;\n }\n this.mbMap.resize();\n /** @ignore */\n this.copyrights = getMapboxMapCopyrights(this.mbMap) || [];\n // @ts-ignore\n (_b = (_a = this.olLayer) === null || _a === void 0 ? void 0 : _a.getSource()) === null || _b === void 0 ? void 0 : _b.setAttributions(this.copyrights);\n });\n const mapboxCanvas = this.mbMap.getCanvas();\n if (mapboxCanvas) {\n if (this.options.tabIndex) {\n mapboxCanvas.setAttribute('tabindex', `${this.options.tabIndex}`);\n }\n else {\n // With a tabIndex='-1' the mouse events works but the map is not focused when we click on it\n // so we remove completely the tabIndex attribute.\n mapboxCanvas.removeAttribute('tabindex');\n }\n }\n }\n getOlLayerRender() {\n return getMapboxRender(this);\n }\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n return Map;\n }\n /**\n * Create a copy of the MapboxLayer.\n * @param {Object} newOptions Options to override\n * @return {MapboxLayer} A MapboxLayer\n */\n clone(newOptions) {\n return new MapboxLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\n", + "content": "/* eslint-disable no-underscore-dangle */\nimport { Map } from 'mapbox-gl';\nimport { getMapboxMapCopyrights, getMapboxRender } from '../../common/utils';\nimport MapGlLayer from './MapGlLayer';\n/**\n * A class representing Mapboxlayer to display on BasicMap\n *\n * @example\n * import { MapboxLayer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new MapboxLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json',\n * apikey: 'yourApiKey',\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nexport default class MapboxLayer extends MapGlLayer {\n /**\n * Initialize the layer and listen to feature clicks.\n * @param {ol/Map~Map} map\n */\n attachToMap(map) {\n super.attachToMap(map);\n if (!this.map) {\n return;\n }\n this.olListenersKeys.push(this.map.on('change:size', () => {\n try {\n if (this.mbMap) {\n this.mbMap.resize();\n }\n }\n catch (err) {\n // ignore render errors\n // eslint-disable-next-line no-console\n console.warn(err);\n }\n }));\n }\n /**\n * Create the mapbox map.\n * @private\n */\n loadMbMap() {\n // If the map hasn't been resized, the center could be [NaN,NaN].\n // We set default good value for the mapbox map, to avoid the app crashes.\n let [x, y] = this.map?.getView().getCenter() || [];\n if (!x || !y) {\n x = 0;\n y = 0;\n }\n // Options the last render run did happen. If something changes\n // we have to render again\n /** @ignore */\n this.renderState = {\n center: [x, y],\n zoom: undefined,\n rotation: undefined,\n visible: undefined,\n opacity: undefined,\n size: [0, 0],\n };\n super.loadMbMap();\n if (!this.mbMap) {\n // mbMap could ne bull if the map is not in the dom yet.\n return;\n }\n this.mbMap.once('load', () => {\n if (!this.mbMap) {\n return;\n }\n this.mbMap.resize();\n /** @ignore */\n this.copyrights = getMapboxMapCopyrights(this.mbMap) || [];\n // @ts-ignore\n this.olLayer?.getSource()?.setAttributions(this.copyrights);\n });\n const mapboxCanvas = this.mbMap.getCanvas();\n if (mapboxCanvas) {\n if (this.options.tabIndex) {\n mapboxCanvas.setAttribute('tabindex', `${this.options.tabIndex}`);\n }\n else {\n // With a tabIndex='-1' the mouse events works but the map is not focused when we click on it\n // so we remove completely the tabIndex attribute.\n mapboxCanvas.removeAttribute('tabindex');\n }\n }\n }\n getOlLayerRender() {\n return getMapboxRender(this);\n }\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n return Map;\n }\n /**\n * Create a copy of the MapboxLayer.\n * @param {Object} newOptions Options to override\n * @return {MapboxLayer} A MapboxLayer\n */\n clone(newOptions) {\n return new MapboxLayer({ ...this.options, ...newOptions });\n }\n}\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/MapboxLayer.js", "access": "public", @@ -9399,7 +9014,7 @@ "lineNumber": 1 }, { - "__docId__": 453, + "__docId__": 436, "kind": "class", "name": "MapboxLayer", "memberof": "build/ol/layers/MapboxLayer.js", @@ -9426,7 +9041,7 @@ ] }, { - "__docId__": 454, + "__docId__": 437, "kind": "method", "name": "attachToMap", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9452,7 +9067,7 @@ "return": null }, { - "__docId__": 455, + "__docId__": 438, "kind": "method", "name": "loadMbMap", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9467,7 +9082,7 @@ "return": null }, { - "__docId__": 456, + "__docId__": 439, "kind": "member", "name": "renderState", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9475,7 +9090,7 @@ "longname": "build/ol/layers/MapboxLayer.js~MapboxLayer#renderState", "access": "public", "description": null, - "lineNumber": 58, + "lineNumber": 57, "ignore": true, "type": { "types": [ @@ -9484,7 +9099,7 @@ } }, { - "__docId__": 457, + "__docId__": 440, "kind": "member", "name": "copyrights", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9492,7 +9107,7 @@ "longname": "build/ol/layers/MapboxLayer.js~MapboxLayer#copyrights", "access": "public", "description": null, - "lineNumber": 78, + "lineNumber": 76, "ignore": true, "type": { "types": [ @@ -9501,7 +9116,7 @@ } }, { - "__docId__": 458, + "__docId__": 441, "kind": "method", "name": "getOlLayerRender", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9511,7 +9126,7 @@ "longname": "build/ol/layers/MapboxLayer.js~MapboxLayer#getOlLayerRender", "access": "public", "description": null, - "lineNumber": 94, + "lineNumber": 92, "undocument": true, "params": [], "return": { @@ -9521,7 +9136,7 @@ } }, { - "__docId__": 459, + "__docId__": 442, "kind": "method", "name": "getMapboxMapClass", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9531,7 +9146,7 @@ "longname": "build/ol/layers/MapboxLayer.js~MapboxLayer#getMapboxMapClass", "access": "public", "description": null, - "lineNumber": 98, + "lineNumber": 96, "undocument": true, "params": [], "return": { @@ -9541,7 +9156,7 @@ } }, { - "__docId__": 460, + "__docId__": 443, "kind": "method", "name": "clone", "memberof": "build/ol/layers/MapboxLayer.js~MapboxLayer", @@ -9551,7 +9166,7 @@ "longname": "build/ol/layers/MapboxLayer.js~MapboxLayer#clone", "access": "public", "description": "Create a copy of the MapboxLayer.", - "lineNumber": 106, + "lineNumber": 104, "params": [ { "nullable": null, @@ -9574,10 +9189,10 @@ } }, { - "__docId__": 461, + "__docId__": 444, "kind": "file", "name": "build/ol/layers/MapboxStyleLayer.js", - "content": "import Layer from './Layer';\n/**\n * Layer for visualizing a specific set of layer from a MapboxLayer.\n *\n * @example\n * import { MapboxLayer, MapboxStyleLayer } from 'mobility-toolbox-js/ol';\n *\n * const mapboxLayer = new MapboxLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json?key=[yourApiKey]',\n * });\n *\n * const layer = new MapboxStyleLayer({\n * mapboxLayer: mapboxLayer,\n * styleLayersFilter: () => {},\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass MapboxStyleLayer extends Layer {\n /**\n * Constructor.\n *\n * @param {Object} options\n * @param {MapboxLayer} [options.mapboxLayer] The MapboxLayer to use.\n * @param {Function} [options.styleLayersFilter] Filter function to decide which style layer to display.\n */\n constructor(options) {\n super(options);\n /**\n * MapboxLayer provided for the style Layer.\n * @type {MapboxLayer}\n * @private\n */\n this.mapboxLayer = options.mapboxLayer;\n /**\n * Define if the layer has data to display in the current mapbox layer.\n */\n this.disabled = false;\n /**\n * Function to filter features to be displayed.\n * @type {function}\n * @private\n */\n this.styleLayersFilter = options.styleLayersFilter;\n /**\n * Mapbox style layer id where to add the style layers.\n * See [mapbox.map.addLayer](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer) documentation.\n * @type {String}\n * @private\n */\n this.beforeId = options.beforeId;\n /**\n * Function to filter features for getFeatureInfoAtCoordinate method.\n * @type {function}\n * @private\n */\n this.featureInfoFilter = options.featureInfoFilter || ((obj) => obj);\n /**\n * Function to query the rendered features.\n * @type {function}\n * @private\n */\n this.queryRenderedLayersFilter = options.queryRenderedLayersFilter;\n /**\n * Array of features to highlight.\n * @type {Array
      }\n * @private\n */\n this.highlightedFeatures = [];\n /**\n * Array of selected features.\n * @type {Array
        }\n * @private\n */\n this.selectedFeatures = [];\n /**\n * Array of mapbox style layers to add.\n * @type {Array}\n * @private\n */\n this.styleLayers =\n (options.styleLayer ? [options.styleLayer] : options.styleLayers) || [];\n /**\n * @private\n */\n this.addStyleLayers = this.addStyleLayers.bind(this);\n /**\n * @private\n */\n this.onLoad = this.onLoad.bind(this);\n if (options.filters) {\n /** @private */\n this.addDynamicFilters = () => {\n this.setFilter(typeof options.filters === 'function'\n ? options.filters(this)\n : options.filters);\n };\n }\n if (!this.styleLayersFilter && this.styleLayers) {\n this.styleLayersFilter = (styleLayer) => {\n var _a;\n return !!((_a = this.styleLayers) === null || _a === void 0 ? void 0 : _a.find((sl) => styleLayer.id === sl.id));\n };\n }\n }\n /**\n * Initialize the layer.\n * @param {ol/Map~Map} map the mapbox map.\n * @override\n */\n attachToMap(map) {\n var _a;\n if (this.mapboxLayer && !this.mapboxLayer.map) {\n (_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.attachToMap(map);\n }\n super.attachToMap(map);\n if (!this.map || !this.mapboxLayer) {\n return;\n }\n // Apply the initial visibiltity.\n const { mbMap } = this.mapboxLayer;\n if (!mbMap) {\n // If the mbMap is not yet created because the map has no target yet, we\n // relaunch the initialisation when it's the case.\n this.olListenersKeys.push(this.map.on('change:target', () => {\n this.attachToMap(map);\n }));\n return;\n }\n // mbMap.loaded() and mbMap.isStyleLoaded() are reliable only on the first call of init.\n // On the next call (when a topic change for example), these functions returns false because\n // the style is being modified.\n // That's why we rely on a property instead for the next calls.\n if (this.mapboxLayer.loaded || mbMap.isStyleLoaded() || mbMap.loaded()) {\n this.onLoad();\n }\n else {\n mbMap.once('load', this.onLoad);\n }\n // Apply the visibiltity when layer's visibility change.\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', (evt) => {\n // Once the map is loaded we can apply vsiiblity without waiting\n // the style. Mapbox take care of the application of style changes.\n this.applyLayoutVisibility(evt);\n }));\n this.olListenersKeys.push(\n // @ts-ignore\n this.mapboxLayer.on('load', () => {\n this.onLoad();\n }));\n }\n /**\n * Terminate the layer.\n * @override\n */\n detachFromMap() {\n var _a;\n if ((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap) {\n const { mbMap } = this.mapboxLayer;\n mbMap.off('load', this.onLoad);\n this.removeStyleLayers();\n }\n super.detachFromMap();\n }\n /** @ignore */\n addStyleLayers() {\n var _a;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach((styleLayer) => {\n const { id, source } = styleLayer;\n if (mbMap.getSource(source) && id && !mbMap.getLayer(id)) {\n // @ts-ignore\n mbMap.addLayer(styleLayer, this.beforeId);\n }\n });\n this.applyLayoutVisibility();\n }\n /** @ignore */\n removeStyleLayers() {\n var _a;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach((styleLayer) => {\n const { id } = styleLayer;\n if (id && mbMap.getLayer(id)) {\n mbMap.removeLayer(id);\n }\n });\n }\n /**\n * On Mapbox map load callback function. Add style layers and dynaimc filters.\n * @ignore\n */\n onLoad() {\n var _a;\n this.addStyleLayers();\n if (this.addDynamicFilters) {\n this.addDynamicFilters();\n }\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n const style = mbMap.getStyle();\n if (style && this.styleLayersFilter) {\n // @ts-ignore\n const styles = style.layers.filter(this.styleLayersFilter);\n this.disabled = !styles.length;\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate Coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n var _a;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n const { mbMap } = this.mapboxLayer;\n // Ignore the getFeatureInfo until the mapbox map is loaded\n if (!mbMap.isStyleLoaded()) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n // We query features only on style layers used by this layer.\n let layers = this.styleLayers || [];\n if (this.styleLayersFilter) {\n // @ts-ignore\n layers = mbMap.getStyle().layers.filter(this.styleLayersFilter);\n }\n if (this.queryRenderedLayersFilter) {\n // @ts-ignore\n layers = mbMap.getStyle().layers.filter(this.queryRenderedLayersFilter);\n }\n return this.mapboxLayer\n .getFeatureInfoAtCoordinate(coordinate, {\n layers: layers.map((layer) => layer && layer.id),\n validate: false,\n })\n .then((featureInfo) => {\n const features = featureInfo.features.filter((feature) => {\n var _a;\n // @ts-ignore\n return this.featureInfoFilter(feature, (_a = this.map) === null || _a === void 0 ? void 0 : _a.getView().getResolution());\n });\n this.highlight(features);\n return Object.assign(Object.assign({}, featureInfo), { features, layer: this });\n });\n }\n /**\n * Set filter that determines which features should be rendered in a style layer.\n * @param {mapboxgl.filter} filter Determines which features should be rendered in a style layer.\n */\n setFilter(filter) {\n var _a;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach(({ id }) => {\n if (id && filter && mbMap.getLayer(id)) {\n // @ts-ignore\n mbMap.setFilter(id, filter);\n }\n });\n }\n /**\n * Set if features are hovered or not.\n * @param {Array
          } features\n * @param {boolean} state Is the feature hovered\n * @private\n */\n setHoverState(features, state) {\n var _a;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n if (!features || !mbMap) {\n return;\n }\n features.forEach((feature) => {\n const { source, sourceLayer } = feature.get('mapboxFeature') || {};\n if ((!source && !sourceLayer) || !feature.getId()) {\n if (!feature.getId()) {\n // eslint-disable-next-line no-console\n console.warn(\"No feature's id found. To use the feature state functionnality, tiles must be generated with --generate-ids. See https://github.com/mapbox/tippecanoe#adding-calculated-attributes.\", feature.getId(), feature.getProperties());\n }\n return;\n }\n mbMap.setFeatureState({\n id: feature.getId(),\n source,\n sourceLayer,\n }, { hover: state });\n });\n }\n /**\n * Select a list of features.\n * @param {Array
            } [features=[]] Features to select.\n * @private\n */\n select(features = []) {\n this.setHoverState(this.selectedFeatures || [], false);\n this.selectedFeatures = features;\n this.setHoverState(this.selectedFeatures || [], true);\n }\n /**\n * Highlight a list of features.\n * @param {Array
              } [features=[]] Features to highlight.\n * @private\n */\n highlight(features = []) {\n var _a;\n // Filter out selected features\n const filtered = ((_a = this.highlightedFeatures) === null || _a === void 0 ? void 0 : _a.filter((feature) => !(this.selectedFeatures || [])\n .map((feat) => feat.getId())\n .includes(feature.getId()))) || [];\n // Remove previous highlight\n this.setHoverState(filtered, false);\n this.highlightedFeatures = features;\n // Add highlight\n this.setHoverState(this.highlightedFeatures, true);\n }\n /**\n * Apply visibility to style layers that fits the styleLayersFilter function.\n * @param {Event} evt Layer's event that has called the function.\n * @private\n */\n // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars\n applyLayoutVisibility(evt) {\n var _a;\n const { visible } = this;\n const filterFunc = this.styleLayersFilter;\n if (!((_a = this.mapboxLayer) === null || _a === void 0 ? void 0 : _a.mbMap)) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n const style = mbMap.getStyle();\n if (!style) {\n return;\n }\n if (filterFunc) {\n const visibilityValue = visible ? 'visible' : 'none';\n const layers = style.layers || [];\n for (let i = 0; i < layers.length; i += 1) {\n const styleLayer = layers[i];\n if (filterFunc(styleLayer)) {\n if (mbMap.getLayer(styleLayer.id)) {\n mbMap.setLayoutProperty(styleLayer.id, 'visibility', visibilityValue);\n }\n }\n }\n }\n }\n /**\n * Create a copy of the MapboxStyleLayer.\n * @param {Object} newOptions Options to override.\n * @return {MapboxStyleLayer} A MapboxStyleLayer.\n */\n clone(newOptions) {\n return new MapboxStyleLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default MapboxStyleLayer;\n", + "content": "import Layer from './Layer';\n/**\n * Layer for visualizing a specific set of layer from a MapboxLayer.\n *\n * @example\n * import { MapboxLayer, MapboxStyleLayer } from 'mobility-toolbox-js/ol';\n *\n * const mapboxLayer = new MapboxLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json?key=[yourApiKey]',\n * });\n *\n * const layer = new MapboxStyleLayer({\n * mapboxLayer: mapboxLayer,\n * styleLayersFilter: () => {},\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass MapboxStyleLayer extends Layer {\n /**\n * Constructor.\n *\n * @param {Object} options\n * @param {MapboxLayer} [options.mapboxLayer] The MapboxLayer to use.\n * @param {Function} [options.styleLayersFilter] Filter function to decide which style layer to display.\n */\n constructor(options) {\n super(options);\n /**\n * MapboxLayer provided for the style Layer.\n * @type {MapboxLayer}\n * @private\n */\n this.mapboxLayer = options.mapboxLayer;\n /**\n * Define if the layer has data to display in the current mapbox layer.\n */\n this.disabled = false;\n /**\n * Function to filter features to be displayed.\n * @type {function}\n * @private\n */\n this.styleLayersFilter = options.styleLayersFilter;\n /**\n * Mapbox style layer id where to add the style layers.\n * See [mapbox.map.addLayer](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer) documentation.\n * @type {String}\n * @private\n */\n this.beforeId = options.beforeId;\n /**\n * Function to filter features for getFeatureInfoAtCoordinate method.\n * @type {function}\n * @private\n */\n this.featureInfoFilter = options.featureInfoFilter || ((obj) => obj);\n /**\n * Function to query the rendered features.\n * @type {function}\n * @private\n */\n this.queryRenderedLayersFilter = options.queryRenderedLayersFilter;\n /**\n * Array of features to highlight.\n * @type {Array
                }\n * @private\n */\n this.highlightedFeatures = [];\n /**\n * Array of selected features.\n * @type {Array
                  }\n * @private\n */\n this.selectedFeatures = [];\n /**\n * Array of mapbox style layers to add.\n * @type {Array}\n * @private\n */\n this.styleLayers =\n (options.styleLayer ? [options.styleLayer] : options.styleLayers) || [];\n /**\n * @private\n */\n this.addStyleLayers = this.addStyleLayers.bind(this);\n /**\n * @private\n */\n this.onLoad = this.onLoad.bind(this);\n if (options.filters) {\n /** @private */\n this.addDynamicFilters = () => {\n this.setFilter(typeof options.filters === 'function'\n ? options.filters(this)\n : options.filters);\n };\n }\n if (!this.styleLayersFilter && this.styleLayers) {\n this.styleLayersFilter = (styleLayer) => {\n return !!this.styleLayers?.find((sl) => styleLayer.id === sl.id);\n };\n }\n }\n /**\n * Initialize the layer.\n * @param {ol/Map~Map} map the mapbox map.\n * @override\n */\n attachToMap(map) {\n if (this.mapboxLayer && !this.mapboxLayer.map) {\n this.mapboxLayer?.attachToMap(map);\n }\n super.attachToMap(map);\n if (!this.map || !this.mapboxLayer) {\n return;\n }\n // Apply the initial visibiltity.\n const { mbMap } = this.mapboxLayer;\n if (!mbMap) {\n // If the mbMap is not yet created because the map has no target yet, we\n // relaunch the initialisation when it's the case.\n this.olListenersKeys.push(this.map.on('change:target', () => {\n this.attachToMap(map);\n }));\n return;\n }\n // mbMap.loaded() and mbMap.isStyleLoaded() are reliable only on the first call of init.\n // On the next call (when a topic change for example), these functions returns false because\n // the style is being modified.\n // That's why we rely on a property instead for the next calls.\n if (this.mapboxLayer.loaded || mbMap.isStyleLoaded() || mbMap.loaded()) {\n this.onLoad();\n }\n else {\n mbMap.once('load', this.onLoad);\n }\n // Apply the visibiltity when layer's visibility change.\n this.olListenersKeys.push(\n // @ts-ignore\n this.on('change:visible', (evt) => {\n // Once the map is loaded we can apply vsiiblity without waiting\n // the style. Mapbox take care of the application of style changes.\n this.applyLayoutVisibility(evt);\n }));\n this.olListenersKeys.push(\n // @ts-ignore\n this.mapboxLayer.on('load', () => {\n this.onLoad();\n }));\n }\n /**\n * Terminate the layer.\n * @override\n */\n detachFromMap() {\n if (this.mapboxLayer?.mbMap) {\n const { mbMap } = this.mapboxLayer;\n mbMap.off('load', this.onLoad);\n this.removeStyleLayers();\n }\n super.detachFromMap();\n }\n /** @ignore */\n addStyleLayers() {\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach((styleLayer) => {\n const { id, source } = styleLayer;\n if (mbMap.getSource(source) && id && !mbMap.getLayer(id)) {\n // @ts-ignore\n mbMap.addLayer(styleLayer, this.beforeId);\n }\n });\n this.applyLayoutVisibility();\n }\n /** @ignore */\n removeStyleLayers() {\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach((styleLayer) => {\n const { id } = styleLayer;\n if (id && mbMap.getLayer(id)) {\n mbMap.removeLayer(id);\n }\n });\n }\n /**\n * On Mapbox map load callback function. Add style layers and dynaimc filters.\n * @ignore\n */\n onLoad() {\n this.addStyleLayers();\n if (this.addDynamicFilters) {\n this.addDynamicFilters();\n }\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n const style = mbMap.getStyle();\n if (style && this.styleLayersFilter) {\n // @ts-ignore\n const styles = style.layers.filter(this.styleLayersFilter);\n this.disabled = !styles.length;\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate Coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n if (!this.mapboxLayer?.mbMap) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n const { mbMap } = this.mapboxLayer;\n // Ignore the getFeatureInfo until the mapbox map is loaded\n if (!mbMap.isStyleLoaded()) {\n return Promise.resolve({ coordinate, features: [], layer: this });\n }\n // We query features only on style layers used by this layer.\n let layers = this.styleLayers || [];\n if (this.styleLayersFilter) {\n // @ts-ignore\n layers = mbMap.getStyle().layers.filter(this.styleLayersFilter);\n }\n if (this.queryRenderedLayersFilter) {\n // @ts-ignore\n layers = mbMap.getStyle().layers.filter(this.queryRenderedLayersFilter);\n }\n return this.mapboxLayer\n .getFeatureInfoAtCoordinate(coordinate, {\n layers: layers.map((layer) => layer && layer.id),\n validate: false,\n })\n .then((featureInfo) => {\n const features = featureInfo.features.filter((feature) => {\n // @ts-ignore\n return this.featureInfoFilter(feature, this.map?.getView().getResolution());\n });\n this.highlight(features);\n return { ...featureInfo, features, layer: this };\n });\n }\n /**\n * Set filter that determines which features should be rendered in a style layer.\n * @param {mapboxgl.filter} filter Determines which features should be rendered in a style layer.\n */\n setFilter(filter) {\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n this.styleLayers.forEach(({ id }) => {\n if (id && filter && mbMap.getLayer(id)) {\n // @ts-ignore\n mbMap.setFilter(id, filter);\n }\n });\n }\n /**\n * Set if features are hovered or not.\n * @param {Array
                    } features\n * @param {boolean} state Is the feature hovered\n * @private\n */\n setHoverState(features, state) {\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n if (!features || !mbMap) {\n return;\n }\n features.forEach((feature) => {\n const { source, sourceLayer } = feature.get('mapboxFeature') || {};\n if ((!source && !sourceLayer) || !feature.getId()) {\n if (!feature.getId()) {\n // eslint-disable-next-line no-console\n console.warn(\"No feature's id found. To use the feature state functionnality, tiles must be generated with --generate-ids. See https://github.com/mapbox/tippecanoe#adding-calculated-attributes.\", feature.getId(), feature.getProperties());\n }\n return;\n }\n mbMap.setFeatureState({\n id: feature.getId(),\n source,\n sourceLayer,\n }, { hover: state });\n });\n }\n /**\n * Select a list of features.\n * @param {Array
                      } [features=[]] Features to select.\n * @private\n */\n select(features = []) {\n this.setHoverState(this.selectedFeatures || [], false);\n this.selectedFeatures = features;\n this.setHoverState(this.selectedFeatures || [], true);\n }\n /**\n * Highlight a list of features.\n * @param {Array
                        } [features=[]] Features to highlight.\n * @private\n */\n highlight(features = []) {\n // Filter out selected features\n const filtered = this.highlightedFeatures?.filter((feature) => !(this.selectedFeatures || [])\n .map((feat) => feat.getId())\n .includes(feature.getId())) || [];\n // Remove previous highlight\n this.setHoverState(filtered, false);\n this.highlightedFeatures = features;\n // Add highlight\n this.setHoverState(this.highlightedFeatures, true);\n }\n /**\n * Apply visibility to style layers that fits the styleLayersFilter function.\n * @param {Event} evt Layer's event that has called the function.\n * @private\n */\n // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars\n applyLayoutVisibility(evt) {\n const { visible } = this;\n const filterFunc = this.styleLayersFilter;\n if (!this.mapboxLayer?.mbMap) {\n return;\n }\n const { mbMap } = this.mapboxLayer;\n const style = mbMap.getStyle();\n if (!style) {\n return;\n }\n if (filterFunc) {\n const visibilityValue = visible ? 'visible' : 'none';\n const layers = style.layers || [];\n for (let i = 0; i < layers.length; i += 1) {\n const styleLayer = layers[i];\n if (filterFunc(styleLayer)) {\n if (mbMap.getLayer(styleLayer.id)) {\n mbMap.setLayoutProperty(styleLayer.id, 'visibility', visibilityValue);\n }\n }\n }\n }\n }\n /**\n * Create a copy of the MapboxStyleLayer.\n * @param {Object} newOptions Options to override.\n * @return {MapboxStyleLayer} A MapboxStyleLayer.\n */\n clone(newOptions) {\n return new MapboxStyleLayer({ ...this.options, ...newOptions });\n }\n}\nexport default MapboxStyleLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/MapboxStyleLayer.js", "access": "public", @@ -9585,7 +9200,7 @@ "lineNumber": 1 }, { - "__docId__": 462, + "__docId__": 445, "kind": "class", "name": "MapboxStyleLayer", "memberof": "build/ol/layers/MapboxStyleLayer.js", @@ -9612,7 +9227,7 @@ ] }, { - "__docId__": 463, + "__docId__": 446, "kind": "constructor", "name": "constructor", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9657,7 +9272,7 @@ ] }, { - "__docId__": 464, + "__docId__": 447, "kind": "member", "name": "mapboxLayer", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9676,7 +9291,7 @@ } }, { - "__docId__": 465, + "__docId__": 448, "kind": "member", "name": "disabled", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9692,7 +9307,7 @@ } }, { - "__docId__": 466, + "__docId__": 449, "kind": "member", "name": "styleLayersFilter", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9711,7 +9326,7 @@ } }, { - "__docId__": 467, + "__docId__": 450, "kind": "member", "name": "beforeId", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9730,7 +9345,7 @@ } }, { - "__docId__": 468, + "__docId__": 451, "kind": "member", "name": "featureInfoFilter", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9749,7 +9364,7 @@ } }, { - "__docId__": 469, + "__docId__": 452, "kind": "member", "name": "queryRenderedLayersFilter", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9768,7 +9383,7 @@ } }, { - "__docId__": 470, + "__docId__": 453, "kind": "member", "name": "highlightedFeatures", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9787,7 +9402,7 @@ } }, { - "__docId__": 471, + "__docId__": 454, "kind": "member", "name": "selectedFeatures", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9806,7 +9421,7 @@ } }, { - "__docId__": 472, + "__docId__": 455, "kind": "member", "name": "styleLayers", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9825,7 +9440,7 @@ } }, { - "__docId__": 475, + "__docId__": 458, "kind": "member", "name": "addDynamicFilters", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9841,7 +9456,7 @@ } }, { - "__docId__": 477, + "__docId__": 460, "kind": "method", "name": "attachToMap", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9851,7 +9466,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#attachToMap", "access": "public", "description": "Initialize the layer.", - "lineNumber": 112, + "lineNumber": 111, "params": [ { "nullable": null, @@ -9868,7 +9483,7 @@ "return": null }, { - "__docId__": 478, + "__docId__": 461, "kind": "method", "name": "detachFromMap", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9878,13 +9493,13 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#detachFromMap", "access": "public", "description": "Terminate the layer.", - "lineNumber": 159, + "lineNumber": 157, "override": true, "params": [], "return": null }, { - "__docId__": 479, + "__docId__": 462, "kind": "method", "name": "addStyleLayers", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9894,13 +9509,13 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#addStyleLayers", "access": "public", "description": null, - "lineNumber": 169, + "lineNumber": 166, "ignore": true, "params": [], "return": null }, { - "__docId__": 480, + "__docId__": 463, "kind": "method", "name": "removeStyleLayers", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9910,13 +9525,13 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#removeStyleLayers", "access": "public", "description": null, - "lineNumber": 185, + "lineNumber": 181, "ignore": true, "params": [], "return": null }, { - "__docId__": 481, + "__docId__": 464, "kind": "method", "name": "onLoad", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9926,13 +9541,13 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#onLoad", "access": "public", "description": "On Mapbox map load callback function. Add style layers and dynaimc filters.", - "lineNumber": 202, + "lineNumber": 197, "ignore": true, "params": [], "return": null }, { - "__docId__": 483, + "__docId__": 466, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9942,7 +9557,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#getFeatureInfoAtCoordinate", "access": "public", "description": "Request feature information for a given coordinate.", - "lineNumber": 224, + "lineNumber": 218, "params": [ { "nullable": null, @@ -9965,7 +9580,7 @@ } }, { - "__docId__": 484, + "__docId__": 467, "kind": "method", "name": "setFilter", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -9975,7 +9590,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#setFilter", "access": "public", "description": "Set filter that determines which features should be rendered in a style layer.", - "lineNumber": 263, + "lineNumber": 255, "params": [ { "nullable": null, @@ -9991,7 +9606,7 @@ "return": null }, { - "__docId__": 485, + "__docId__": 468, "kind": "method", "name": "setHoverState", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -10001,7 +9616,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#setHoverState", "access": "private", "description": "Set if features are hovered or not.", - "lineNumber": 282, + "lineNumber": 273, "params": [ { "nullable": null, @@ -10027,7 +9642,7 @@ "return": null }, { - "__docId__": 486, + "__docId__": 469, "kind": "method", "name": "select", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -10037,7 +9652,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#select", "access": "private", "description": "Select a list of features.", - "lineNumber": 312, + "lineNumber": 302, "params": [ { "nullable": null, @@ -10055,7 +9670,7 @@ "return": null }, { - "__docId__": 488, + "__docId__": 471, "kind": "method", "name": "highlight", "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", @@ -10065,7 +9680,7 @@ "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#highlight", "access": "private", "description": "Highlight a list of features.", - "lineNumber": 322, + "lineNumber": 312, "params": [ { "nullable": null, @@ -10083,887 +9698,154 @@ "return": null }, { - "__docId__": 490, + "__docId__": 473, "kind": "method", - "name": "applyLayoutVisibility", - "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#applyLayoutVisibility", - "access": "private", - "description": "Apply visibility to style layers that fits the styleLayersFilter function.", - "lineNumber": 340, - "params": [ - { - "nullable": null, - "types": [ - "Event" - ], - "spread": false, - "optional": false, - "name": "evt", - "description": "Layer's event that has called the function." - } - ], - "return": null - }, - { - "__docId__": 491, - "kind": "method", - "name": "clone", - "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#clone", - "access": "public", - "description": "Create a copy of the MapboxStyleLayer.", - "lineNumber": 370, - "params": [ - { - "nullable": null, - "types": [ - "Object" - ], - "spread": false, - "optional": false, - "name": "newOptions", - "description": "Options to override." - } - ], - "return": { - "nullable": null, - "types": [ - "MapboxStyleLayer" - ], - "spread": false, - "description": "A MapboxStyleLayer." - } - }, - { - "__docId__": 492, - "kind": "file", - "name": "build/ol/layers/MaplibreLayer.js", - "content": "import { Map } from 'maplibre-gl';\nimport { getMaplibreRender } from '../../common/utils';\nimport MapGlLayer from './MapGlLayer';\n/**\n * A class representing MaplibreLayer to display on BasicMap\n *\n * @example\n * import { MaplibreLayer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new MaplibreLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json',\n * apikey: 'yourApiKey',\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nexport default class MaplibreLayer extends MapGlLayer {\n getOlLayerRender() {\n return getMaplibreRender(this);\n }\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n return Map;\n }\n /**\n * Create a copy of the MapboxLayer.\n * @param {Object} newOptions Options to override\n * @return {MapboxLayer} A MapboxLayer\n */\n clone(newOptions) {\n return new MaplibreLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\n", - "static": true, - "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/MaplibreLayer.js", - "access": "public", - "description": null, - "lineNumber": 1 - }, - { - "__docId__": 493, - "kind": "class", - "name": "MaplibreLayer", - "memberof": "build/ol/layers/MaplibreLayer.js", - "static": true, - "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", - "access": "public", - "export": true, - "importPath": "mobility-toolbox-js/build/ol/layers/MaplibreLayer.js", - "importStyle": "MaplibreLayer", - "description": "A class representing MaplibreLayer to display on BasicMap", - "examples": [ - "import { MaplibreLayer } from 'mobility-toolbox-js/ol';\n\nconst layer = new MaplibreLayer({\n url: 'https://maps.geops.io/styles/travic_v2/style.json',\n apikey: 'yourApiKey',\n});" - ], - "lineNumber": 18, - "unknown": [ - { - "tagName": "@classproperty", - "tagValue": "{ol/Map~Map} map - The map where the layer is displayed." - } - ], - "interface": false, - "extends": [ - "Layer" - ] - }, - { - "__docId__": 494, - "kind": "method", - "name": "getOlLayerRender", - "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#getOlLayerRender", - "access": "public", - "description": null, - "lineNumber": 19, - "undocument": true, - "params": [], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 495, - "kind": "method", - "name": "getMapboxMapClass", - "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#getMapboxMapClass", - "access": "public", - "description": null, - "lineNumber": 23, - "undocument": true, - "params": [], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 496, - "kind": "method", - "name": "clone", - "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#clone", - "access": "public", - "description": "Create a copy of the MapboxLayer.", - "lineNumber": 31, - "params": [ - { - "nullable": null, - "types": [ - "Object" - ], - "spread": false, - "optional": false, - "name": "newOptions", - "description": "Options to override" - } - ], - "return": { - "nullable": null, - "types": [ - "MapboxLayer" - ], - "spread": false, - "description": "A MapboxLayer" - } - }, - { - "__docId__": 497, - "kind": "file", - "name": "build/ol/layers/RealtimeLayer.js", - "content": "import GeoJSON from 'ol/format/GeoJSON';\nimport { Layer as OLLayer, Group, Vector as VectorLayer } from 'ol/layer';\nimport Source from 'ol/source/Source';\nimport { composeCssTransform } from 'ol/transform';\nimport { Vector as VectorSource } from 'ol/source';\nimport stringify from 'json-stringify-safe';\nimport Layer from './Layer';\nimport mixin from '../../common/mixins/RealtimeLayerMixin';\nimport { fullTrajectoryStyle } from '../styles';\n// import Worker from '../../common/tracker.worker';\n/** @private */\nconst format = new GeoJSON();\nconst updateContainerTransform = (layer) => {\n if (layer.renderedViewState) {\n const { center, resolution, rotation } = layer.mainThreadFrameState.viewState;\n const { center: renderedCenter, resolution: renderedResolution, rotation: renderedRotation, } = layer.renderedViewState;\n const pixelCenterRendered = layer.map.getPixelFromCoordinate(renderedCenter);\n const pixelCenter = layer.map.getPixelFromCoordinate(center);\n // eslint-disable-next-line no-param-reassign\n layer.transformContainer.style.transform = composeCssTransform(pixelCenterRendered[0] - pixelCenter[0], pixelCenterRendered[1] - pixelCenter[1], renderedResolution / resolution, renderedResolution / resolution, rotation - renderedRotation, 0, 0);\n }\n};\n/**\n * Responsible for loading and display data from a Realtime service.\n *\n * @example\n * import { RealtimeLayer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new RealtimeLayer({\n * url: [yourUrl],\n * apiKey: [yourApiKey],\n * });\n *\n *\n * @see RealtimeAPI\n *\n * @extends {Layer}\n * @implements {UserInteractionsLayerInterface}\n * @implements {RealtimeLayerInterface}\n */\n// @ts-ignore\nclass RealtimeLayer extends mixin(Layer) {\n /**\n * Constructor.\n *\n * @param {Object} options\n * @private\n */\n constructor(options) {\n // We use a group to be able to add custom vector layer in extended class.\n // For example TrajservLayer use a vectorLayer to display the complete trajectory.\n super(Object.assign({}, options));\n this.allowRenderWhenAnimating = false;\n // Worker that render trajectories.\n this.worker = new Worker('../../common/tracker.worker.ts', {\n type: 'module',\n });\n // Worker messaging and actions\n const that = this;\n this.worker.onmessage = (message) => {\n var _a, _b, _c;\n if (message.data.action === 'requestRender') {\n console.log('icci');\n // Worker requested a new render frame\n that.map.render();\n }\n else if (that.canvas && message.data.action === 'rendered') {\n if (that.map.getView().getInteracting() ||\n that.map.getView().getAnimating()) {\n return;\n }\n // Worker provides a new render frame\n // requestAnimationFrame(() => {\n // if (\n // !that.renderWhenInteracting(\n // that.mainThreadFrameState.viewState,\n // that.renderedViewState,\n // ) &&\n // (that.map.getView().getInteracting() ||\n // that.map.getView().getAnimating())\n // ) {\n // return;\n // }\n const { imageData, nbRenderedTrajectories } = message.data;\n this.nbRenderedTrajectories = nbRenderedTrajectories;\n that.canvas.width = imageData.width;\n that.canvas.height = imageData.height;\n if ((_a = that.canvas) === null || _a === void 0 ? void 0 : _a.style) {\n that.canvas.style.transform = ``;\n that.canvas.style.width = `${that.canvas.width / (that.pixelRatio || 1)}px`;\n that.canvas.style.height = `${that.canvas.height / (that.pixelRatio || 1)}px`;\n }\n that.renderedViewState = message.data.frameState.viewState;\n updateContainerTransform(that);\n (_c = (_b = that.canvas) === null || _b === void 0 ? void 0 : _b.getContext('2d')) === null || _c === void 0 ? void 0 : _c.drawImage(imageData, 0, 0);\n // });\n that.rendering = false;\n }\n };\n this.allowRenderWhenAnimating = !!options.allowRenderWhenAnimating;\n /** @ignore */\n this.olLayer =\n options.olLayer ||\n new Group({\n layers: [\n new VectorLayer({\n source: new VectorSource({ features: [] }),\n style: (feature, resolution) => {\n return (options.fullTrajectoryStyle || fullTrajectoryStyle)(feature, resolution, this.styleOptions);\n },\n }),\n new OLLayer({\n source: new Source({}),\n render: (frameState) => {\n var _a, _b, _c;\n if (!this.container) {\n this.container = document.createElement('div');\n this.container.style.position = 'absolute';\n this.container.style.width = '100%';\n this.container.style.height = '100%';\n this.transformContainer = document.createElement('div');\n this.transformContainer.style.position = 'absolute';\n this.transformContainer.style.width = '100%';\n this.transformContainer.style.height = '100%';\n this.container.appendChild(this.transformContainer);\n if (this.canvas) {\n this.canvas.style.position =\n 'absolute';\n this.canvas.style.top = '0';\n this.canvas.style.left = '0';\n this.canvas.style.transformOrigin =\n 'top left';\n this.transformContainer.appendChild(this.canvas);\n }\n }\n this.mainThreadFrameState = frameState;\n if (this.renderedViewState) {\n const { \n // center,\n resolution,\n // rotation\n } = frameState.viewState;\n const { \n // center: renderedCenter,\n resolution: renderedResolution,\n // rotation: renderedRotation,\n } = this.renderedViewState;\n if (renderedResolution / resolution >= 3) {\n // Avoid having really big points when zooming fast.\n const context = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getContext('2d');\n context === null || context === void 0 ? void 0 : context.clearRect(0, 0, (_b = this.canvas) === null || _b === void 0 ? void 0 : _b.width, (_c = this.canvas) === null || _c === void 0 ? void 0 : _c.height);\n }\n else {\n updateContainerTransform(this);\n // const pixelCenterRendered =\n // this.map.getPixelFromCoordinate(renderedCenter);\n // const pixelCenter = this.map.getPixelFromCoordinate(center);\n // this.transformContainer.style.transform = composeCssTransform(\n // pixelCenterRendered[0] - pixelCenter[0],\n // pixelCenterRendered[1] - pixelCenter[1],\n // renderedResolution / resolution,\n // renderedResolution / resolution,\n // rotation - renderedRotation,\n // 0,\n // 0,\n // );\n }\n }\n return this.container;\n },\n }),\n ],\n });\n // We store the layer used to highlight the full Trajectory\n this.vectorLayer = this.olLayer.getLayers().item(0);\n // Options the last render run did happen. If something changes\n // we have to render again\n /** @ignore */\n this.renderState = {\n center: [0, 0],\n zoom: undefined,\n rotation: 0,\n };\n }\n attachToMap(map) {\n super.attachToMap(map);\n if (this.map) {\n this.olListenersKeys.push(...this.map.on(['moveend', 'change:target'], (evt) => {\n const view = (evt.map || evt.target).getView();\n if (view.getAnimating() || view.getInteracting()) {\n return;\n }\n const zoom = view.getZoom();\n // Update the interval between render updates\n if (this.currentZoom !== zoom) {\n this.onZoomEnd();\n }\n this.currentZoom = zoom;\n this.onMoveEnd(evt);\n }));\n }\n }\n /**\n * Destroy the container of the tracker.\n */\n detachFromMap() {\n super.detachFromMap();\n this.container = null;\n }\n /**\n * Detect in the canvas if there is data to query at a specific coordinate.\n * @param {ol/coordinate~Coordinate} coordinate The coordinate to test\n * @returns\n */\n hasFeatureInfoAtCoordinate(coordinate) {\n if (this.map && this.canvas) {\n const context = this.canvas.getContext('2d', {\n willReadFrequently: true,\n });\n const pixel = this.map.getPixelFromCoordinate(coordinate);\n return !!(context === null || context === void 0 ? void 0 : context.getImageData(pixel[0] * (this.pixelRatio || 1), pixel[1] * (this.pixelRatio || 1), 1, 1).data[3]);\n }\n return false;\n }\n /**\n * Render the trajectories using current map's size, resolution and rotation.\n * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions.\n * @overrides\n */\n // @ts-ignore\n renderTrajectories(noInterpolate) {\n if (!this.map) {\n return;\n }\n const view = this.map.getView();\n super.renderTrajectories({\n size: this.map.getSize(),\n center: this.map.getView().getCenter(),\n extent: view.calculateExtent(),\n resolution: view.getResolution(),\n rotation: view.getRotation(),\n zoom: view.getZoom(),\n pixelRatio: this.pixelRatio,\n }, noInterpolate);\n }\n /**\n * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod.\n * @private\n * @override\n */\n renderTrajectoriesInternal(viewState, noInterpolate) {\n if (!this.map) {\n return false;\n }\n let isRendered = false;\n const blockRendering = this.allowRenderWhenAnimating\n ? false\n : this.map.getView().getAnimating() ||\n this.map.getView().getInteracting();\n if (!blockRendering && this.worker && this.mainThreadFrameState) {\n const frameState = Object.assign({}, this.mainThreadFrameState);\n delete frameState.layerStatesArray;\n delete frameState.viewState.projection;\n this.worker.postMessage({\n action: 'render',\n trajectories: this.trajectories,\n frameState: JSON.parse(stringify(frameState)),\n viewState,\n options: {\n noInterpolate,\n iconScale: this.iconScale,\n hoverVehicleId: this.hoverVehicleId,\n selectedVehicleId: this.selectedVehicleId,\n delayDisplay: this.delayDisplay,\n delayOutlineColor: this.delayOutlineColor,\n useDelayStyle: this.useDelayStyle,\n },\n });\n }\n else if (!this.worker) {\n // Don't render the map when the map is animating or interacting.\n isRendered = blockRendering\n ? false\n : super.renderTrajectoriesInternal(viewState, noInterpolate);\n // We update the current render state.\n if (isRendered) {\n this.renderedViewState = Object.assign({}, viewState);\n if (this.transformContainer) {\n this.transformContainer.style.transform = '';\n }\n }\n }\n return isRendered;\n }\n /**\n * Return the delay in ms before the next rendering.\n */\n getRefreshTimeInMs() {\n return super.getRefreshTimeInMs(this.map.getView().getZoom());\n }\n getFeatureInfoAtCoordinate(coordinate, options = {}) {\n const resolution = this.map.getView().getResolution();\n return super.getFeatureInfoAtCoordinate(coordinate, Object.assign({ resolution }, options));\n }\n /**\n * On move end we update the websocket with the new bbox.\n *\n * @private\n * @override\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onMoveEnd(evt) {\n if (this.visible && this.isUpdateBboxOnMoveEnd) {\n this.setBbox();\n }\n if (this.visible &&\n this.isUpdateBboxOnMoveEnd &&\n this.userClickInteractions &&\n this.selectedVehicleId) {\n this.highlightTrajectory(this.selectedVehicleId);\n }\n }\n /**\n * Function called on moveend event only when the zoom has changed.\n *\n * @param {ol/MapEvent~MapEvent} evt Moveend event.\n * @private\n * @override\n */\n // eslint-disable-next-line no-unused-vars\n onZoomEnd() {\n super.onZoomEnd();\n if (this.visible && this.isUpdateBboxOnMoveEnd) {\n this.setBbox();\n }\n if (this.visible &&\n this.isUpdateBboxOnMoveEnd &&\n this.userClickInteractions &&\n this.selectedVehicleId) {\n this.highlightTrajectory(this.selectedVehicleId);\n }\n }\n /**\n * Update the cursor style when hovering a vehicle.\n *\n * @private\n * @override\n */\n onFeatureHover(features, layer, coordinate) {\n super.onFeatureHover(features, layer, coordinate);\n this.map.getTargetElement().style.cursor = features.length\n ? 'pointer'\n : 'auto';\n }\n /**\n * Display the complete trajectory of the vehicle.\n *\n * @private\n * @override\n */\n onFeatureClick(features, layer, coordinate) {\n super.onFeatureClick(features, layer, coordinate);\n if (!features.length && this.vectorLayer) {\n this.vectorLayer.getSource().clear();\n }\n if (this.selectedVehicleId) {\n this.highlightTrajectory(this.selectedVehicleId);\n }\n }\n /**\n * Remove the trajectory form the list if necessary.\n *\n * @private\n */\n purgeTrajectory(trajectory, extent, zoom) {\n return super.purgeTrajectory(trajectory, extent || this.map.getView().calculateExtent(), zoom || this.map.getView().getZoom());\n }\n /**\n * Send the current bbox to the websocket\n *\n * @private\n */\n setBbox(extent, zoom) {\n let newExtent = extent;\n let newZoom = zoom;\n if (!newExtent && this.isUpdateBboxOnMoveEnd) {\n newExtent = extent || this.map.getView().calculateExtent();\n newZoom = Math.floor(this.map.getView().getZoom());\n }\n super.setBbox(newExtent, newZoom);\n }\n /**\n * Highlight the trajectory of journey.\n * @private\n */\n highlightTrajectory(id) {\n this.api\n .getFullTrajectory(id, this.mode, this.generalizationLevel)\n .then((data) => {\n const fullTrajectory = data.content;\n this.vectorLayer.getSource().clear();\n if (!fullTrajectory ||\n !fullTrajectory.features ||\n !fullTrajectory.features.length) {\n return;\n }\n const features = format.readFeatures(fullTrajectory);\n this.vectorLayer.getSource().addFeatures(features);\n });\n }\n /**\n * Create a copy of the RealtimeLayer.\n * @param {Object} newOptions Options to override\n * @return {RealtimeLayer} A RealtimeLayer\n */\n clone(newOptions) {\n return new RealtimeLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default RealtimeLayer;\n", - "static": true, - "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/RealtimeLayer.js", - "access": "public", - "description": null, - "lineNumber": 1 - }, - { - "__docId__": 498, - "kind": "variable", - "name": "format", - "memberof": "build/ol/layers/RealtimeLayer.js", - "static": true, - "longname": "build/ol/layers/RealtimeLayer.js~format", - "access": "private", - "export": false, - "importPath": "mobility-toolbox-js/build/ol/layers/RealtimeLayer.js", - "importStyle": null, - "description": null, - "lineNumber": 12, - "type": { - "types": [ - "*" - ] - }, - "ignore": true - }, - { - "__docId__": 499, - "kind": "function", - "name": "updateContainerTransform", - "memberof": "build/ol/layers/RealtimeLayer.js", - "generator": false, - "async": false, - "static": true, - "longname": "build/ol/layers/RealtimeLayer.js~updateContainerTransform", - "access": "public", - "export": false, - "importPath": "mobility-toolbox-js/build/ol/layers/RealtimeLayer.js", - "importStyle": null, - "description": null, - "lineNumber": 13, - "undocument": true, - "params": [ - { - "name": "layer", - "types": [ - "*" - ] - } - ], - "return": null, - "ignore": true - }, - { - "__docId__": 500, - "kind": "class", - "name": "RealtimeLayer", - "memberof": "build/ol/layers/RealtimeLayer.js", - "static": true, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "access": "public", - "export": true, - "importPath": "mobility-toolbox-js/build/ol/layers/RealtimeLayer.js", - "importStyle": "RealtimeLayer", - "description": "Responsible for loading and display data from a Realtime service.", - "examples": [ - "import { RealtimeLayer } from 'mobility-toolbox-js/ol';\n\nconst layer = new RealtimeLayer({\n url: [yourUrl],\n apiKey: [yourApiKey],\n});" - ], - "see": [ - "RealtimeAPI" - ], - "lineNumber": 42, - "interface": false, - "extends": [ - "Layer" - ], - "implements": [ - "UserInteractionsLayerInterface", - "RealtimeLayerInterface" - ] - }, - { - "__docId__": 501, - "kind": "constructor", - "name": "constructor", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#constructor", - "access": "private", - "description": "Constructor.", - "lineNumber": 49, - "params": [ - { - "nullable": null, - "types": [ - "Object" - ], - "spread": false, - "optional": false, - "name": "options", - "description": "" - } - ] - }, - { - "__docId__": 502, - "kind": "member", - "name": "allowRenderWhenAnimating", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#allowRenderWhenAnimating", - "access": "public", - "description": null, - "lineNumber": 53, - "undocument": true, - "type": { - "types": [ - "boolean" - ] - } - }, - { - "__docId__": 503, - "kind": "member", - "name": "worker", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#worker", - "access": "public", - "description": null, - "lineNumber": 55, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 504, - "kind": "member", - "name": "nbRenderedTrajectories", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#nbRenderedTrajectories", - "access": "public", - "description": null, - "lineNumber": 85, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 506, - "kind": "member", - "name": "olLayer", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#olLayer", - "access": "public", - "description": null, - "lineNumber": 102, - "ignore": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 507, - "kind": "member", - "name": "container", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#container", - "access": "public", - "description": null, - "lineNumber": 117, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 508, - "kind": "member", - "name": "transformContainer", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#transformContainer", - "access": "public", - "description": null, - "lineNumber": 121, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 509, - "kind": "member", - "name": "mainThreadFrameState", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#mainThreadFrameState", - "access": "public", - "description": null, - "lineNumber": 136, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 510, - "kind": "member", - "name": "vectorLayer", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#vectorLayer", - "access": "public", - "description": null, - "lineNumber": 175, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 511, - "kind": "member", - "name": "renderState", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#renderState", - "access": "public", - "description": null, - "lineNumber": 179, - "ignore": true, - "type": { - "types": [ - "{\"center\": *, \"zoom\": *, \"rotation\": *}" - ] - } - }, - { - "__docId__": 512, - "kind": "method", - "name": "attachToMap", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#attachToMap", - "access": "public", - "description": null, - "lineNumber": 185, - "undocument": true, - "params": [ - { - "name": "map", - "types": [ - "*" - ] - } - ], - "return": null - }, - { - "__docId__": 513, - "kind": "member", - "name": "currentZoom", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#currentZoom", - "access": "public", - "description": null, - "lineNumber": 198, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 514, - "kind": "method", - "name": "detachFromMap", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#detachFromMap", - "access": "public", - "description": "Destroy the container of the tracker.", - "lineNumber": 206, - "params": [], - "return": null - }, - { - "__docId__": 516, - "kind": "method", - "name": "hasFeatureInfoAtCoordinate", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#hasFeatureInfoAtCoordinate", - "access": "public", - "description": "Detect in the canvas if there is data to query at a specific coordinate.", - "lineNumber": 215, - "unknown": [ - { - "tagName": "@returns", - "tagValue": "" - } - ], - "params": [ - { - "nullable": null, - "types": [ - "ol/coordinate~Coordinate" - ], - "spread": false, - "optional": false, - "name": "coordinate", - "description": "The coordinate to test" - } - ], - "return": { - "types": [ - "boolean" - ] - } - }, - { - "__docId__": 517, - "kind": "method", - "name": "renderTrajectories", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#renderTrajectories", - "access": "public", - "description": "Render the trajectories using current map's size, resolution and rotation.", - "lineNumber": 231, - "unknown": [ - { - "tagName": "@overrides", - "tagValue": "" - } - ], - "params": [ - { - "nullable": null, - "types": [ - "boolean" - ], - "spread": false, - "optional": false, - "name": "noInterpolate", - "description": "if true, renders the vehicles without interpolating theirs positions." - } - ], - "return": null - }, - { - "__docId__": 518, - "kind": "method", - "name": "renderTrajectoriesInternal", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#renderTrajectoriesInternal", - "access": "private", - "description": "Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod.", - "lineNumber": 251, - "override": true, - "params": [ - { - "name": "viewState", - "types": [ - "*" - ] - }, - { - "name": "noInterpolate", - "types": [ - "*" - ] - } - ], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 519, - "kind": "member", - "name": "renderedViewState", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#renderedViewState", - "access": "public", - "description": null, - "lineNumber": 287, - "undocument": true, - "type": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 520, - "kind": "method", - "name": "getRefreshTimeInMs", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#getRefreshTimeInMs", - "access": "public", - "description": "Return the delay in ms before the next rendering.", - "lineNumber": 298, - "params": [], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 521, - "kind": "method", - "name": "getFeatureInfoAtCoordinate", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#getFeatureInfoAtCoordinate", - "access": "public", - "description": null, - "lineNumber": 301, - "undocument": true, - "params": [ - { - "name": "coordinate", - "types": [ - "*" - ] - }, - { - "name": "options", - "optional": true, - "types": [ - "{}" - ], - "defaultRaw": {}, - "defaultValue": "{}" - } - ], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 522, - "kind": "method", - "name": "onMoveEnd", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#onMoveEnd", - "access": "private", - "description": "On move end we update the websocket with the new bbox.", - "lineNumber": 312, - "override": true, - "params": [ - { - "name": "evt", - "types": [ - "*" - ] - } - ], - "return": null - }, - { - "__docId__": 523, - "kind": "method", - "name": "onZoomEnd", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#onZoomEnd", - "access": "private", - "description": "Function called on moveend event only when the zoom has changed.", - "lineNumber": 331, - "params": [ - { - "nullable": null, - "types": [ - "ol/MapEvent~MapEvent" - ], - "spread": false, - "optional": false, - "name": "evt", - "description": "Moveend event." - } - ], - "override": true, - "return": null - }, - { - "__docId__": 524, - "kind": "method", - "name": "onFeatureHover", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#onFeatureHover", - "access": "private", - "description": "Update the cursor style when hovering a vehicle.", - "lineNumber": 349, - "override": true, - "params": [ - { - "name": "features", - "types": [ - "*" - ] - }, - { - "name": "layer", - "types": [ - "*" - ] - }, - { - "name": "coordinate", - "types": [ - "*" - ] - } - ], - "return": null - }, - { - "__docId__": 525, - "kind": "method", - "name": "onFeatureClick", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#onFeatureClick", - "access": "private", - "description": "Display the complete trajectory of the vehicle.", - "lineNumber": 361, - "override": true, - "params": [ - { - "name": "features", - "types": [ - "*" - ] - }, - { - "name": "layer", - "types": [ - "*" - ] - }, - { - "name": "coordinate", - "types": [ - "*" - ] - } - ], - "return": null - }, - { - "__docId__": 526, - "kind": "method", - "name": "purgeTrajectory", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", - "generator": false, - "async": false, - "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#purgeTrajectory", - "access": "private", - "description": "Remove the trajectory form the list if necessary.", - "lineNumber": 375, - "params": [ - { - "name": "trajectory", - "types": [ - "*" - ] - }, - { - "name": "extent", - "types": [ - "*" - ] - }, - { - "name": "zoom", - "types": [ - "*" - ] - } - ], - "return": { - "types": [ - "*" - ] - } - }, - { - "__docId__": 527, - "kind": "method", - "name": "setBbox", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", + "name": "applyLayoutVisibility", + "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", "generator": false, "async": false, "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#setBbox", + "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#applyLayoutVisibility", "access": "private", - "description": "Send the current bbox to the websocket", - "lineNumber": 383, + "description": "Apply visibility to style layers that fits the styleLayersFilter function.", + "lineNumber": 329, "params": [ { - "name": "extent", - "types": [ - "*" - ] - }, - { - "name": "zoom", + "nullable": null, "types": [ - "*" - ] + "Event" + ], + "spread": false, + "optional": false, + "name": "evt", + "description": "Layer's event that has called the function." } ], "return": null }, { - "__docId__": 528, + "__docId__": 474, "kind": "method", - "name": "highlightTrajectory", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", + "name": "clone", + "memberof": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer", "generator": false, "async": false, "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#highlightTrajectory", - "access": "private", - "description": "Highlight the trajectory of journey.", - "lineNumber": 396, + "longname": "build/ol/layers/MapboxStyleLayer.js~MapboxStyleLayer#clone", + "access": "public", + "description": "Create a copy of the MapboxStyleLayer.", + "lineNumber": 358, "params": [ { - "name": "id", + "nullable": null, "types": [ - "*" - ] + "Object" + ], + "spread": false, + "optional": false, + "name": "newOptions", + "description": "Options to override." } ], - "return": null + "return": { + "nullable": null, + "types": [ + "MapboxStyleLayer" + ], + "spread": false, + "description": "A MapboxStyleLayer." + } + }, + { + "__docId__": 475, + "kind": "file", + "name": "build/ol/layers/MaplibreLayer.js", + "content": "import { Map } from 'maplibre-gl';\nimport { getMaplibreRender } from '../../common/utils';\nimport MapGlLayer from './MapGlLayer';\n/**\n * A class representing MaplibreLayer to display on BasicMap\n *\n * @example\n * import { MaplibreLayer } from 'mobility-toolbox-js/ol';\n *\n * const layer = new MaplibreLayer({\n * url: 'https://maps.geops.io/styles/travic_v2/style.json',\n * apikey: 'yourApiKey',\n * });\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nexport default class MaplibreLayer extends MapGlLayer {\n getOlLayerRender() {\n return getMaplibreRender(this);\n }\n // eslint-disable-next-line class-methods-use-this\n getMapboxMapClass() {\n return Map;\n }\n /**\n * Create a copy of the MapboxLayer.\n * @param {Object} newOptions Options to override\n * @return {MapboxLayer} A MapboxLayer\n */\n clone(newOptions) {\n return new MaplibreLayer({ ...this.options, ...newOptions });\n }\n}\n", + "static": true, + "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/MaplibreLayer.js", + "access": "public", + "description": null, + "lineNumber": 1 + }, + { + "__docId__": 476, + "kind": "class", + "name": "MaplibreLayer", + "memberof": "build/ol/layers/MaplibreLayer.js", + "static": true, + "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", + "access": "public", + "export": true, + "importPath": "mobility-toolbox-js/build/ol/layers/MaplibreLayer.js", + "importStyle": "MaplibreLayer", + "description": "A class representing MaplibreLayer to display on BasicMap", + "examples": [ + "import { MaplibreLayer } from 'mobility-toolbox-js/ol';\n\nconst layer = new MaplibreLayer({\n url: 'https://maps.geops.io/styles/travic_v2/style.json',\n apikey: 'yourApiKey',\n});" + ], + "lineNumber": 18, + "unknown": [ + { + "tagName": "@classproperty", + "tagValue": "{ol/Map~Map} map - The map where the layer is displayed." + } + ], + "interface": false, + "extends": [ + "Layer" + ] + }, + { + "__docId__": 477, + "kind": "method", + "name": "getOlLayerRender", + "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", + "generator": false, + "async": false, + "static": false, + "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#getOlLayerRender", + "access": "public", + "description": null, + "lineNumber": 19, + "undocument": true, + "params": [], + "return": { + "types": [ + "*" + ] + } + }, + { + "__docId__": 478, + "kind": "method", + "name": "getMapboxMapClass", + "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", + "generator": false, + "async": false, + "static": false, + "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#getMapboxMapClass", + "access": "public", + "description": null, + "lineNumber": 23, + "undocument": true, + "params": [], + "return": { + "types": [ + "*" + ] + } }, { - "__docId__": 529, + "__docId__": 479, "kind": "method", "name": "clone", - "memberof": "build/ol/layers/RealtimeLayer.js~RealtimeLayer", + "memberof": "build/ol/layers/MaplibreLayer.js~MaplibreLayer", "generator": false, "async": false, "static": false, - "longname": "build/ol/layers/RealtimeLayer.js~RealtimeLayer#clone", + "longname": "build/ol/layers/MaplibreLayer.js~MaplibreLayer#clone", "access": "public", - "description": "Create a copy of the RealtimeLayer.", - "lineNumber": 416, + "description": "Create a copy of the MapboxLayer.", + "lineNumber": 31, "params": [ { "nullable": null, @@ -10979,17 +9861,17 @@ "return": { "nullable": null, "types": [ - "RealtimeLayer" + "MapboxLayer" ], "spread": false, - "description": "A RealtimeLayer" + "description": "A MapboxLayer" } }, { - "__docId__": 530, + "__docId__": 480, "kind": "file", "name": "build/ol/layers/RoutingLayer.js", - "content": "import { Circle, Fill, Stroke, Style } from 'ol/style';\nimport { Vector as VectorSource } from 'ol/source';\nimport { Vector } from 'ol/layer';\nimport Layer from './Layer';\n/** @private */\nconst circleStyle = new Circle({\n radius: 6,\n fill: new Fill({\n color: [255, 0, 0, 1],\n }),\n stroke: new Stroke({\n color: [0, 0, 0, 1],\n width: 1,\n }),\n});\n/** @private */\nconst blackBorder = new Style({\n stroke: new Stroke({\n color: [0, 0, 0, 1],\n width: 5,\n }),\n});\n/** @private */\nconst redLine = new Style({\n image: circleStyle,\n stroke: new Stroke({\n color: [255, 0, 0, 1],\n width: 3,\n }),\n});\n/** @private */\nconst dashedRedLine = new Style({\n image: circleStyle,\n stroke: new Stroke({\n color: [255, 0, 0, 1],\n width: 3,\n lineDash: [1, 10],\n }),\n});\n/** @private */\nconst defaultStyleFunction = (feature, resolution) => {\n const minResolution = feature.get('minResolution');\n const maxResolution = feature.get('maxResolution');\n const inRange = resolution <= minResolution && resolution > maxResolution;\n if (minResolution && maxResolution && !inRange) {\n return [];\n }\n const mot = feature.get('mot');\n if (mot !== 'foot') {\n return [blackBorder, redLine];\n }\n return [dashedRedLine];\n};\n/**\n * A class use to display vector data.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass RoutingLayer extends Layer {\n /**\n * Constructor.\n * @param {Object} [options]\n * @param {ol/style/Style~StyleLike} [options.style] Style to be used for routes, uses (ol/StyleLike) [https://openlayers.org/en/latest/apidoc/module-ol_style_Style.html#~StyleLike] instances\n */\n constructor(options) {\n super(options);\n this.options = {};\n this.olLayer =\n options.olLayer ||\n new Vector({\n source: new VectorSource(),\n style: options.style || defaultStyleFunction,\n });\n }\n /**\n * Create a copy of the RoutingLayer.\n * @param {Object} newOptions Options to override\n * @return {RoutingLayer} A RoutingLayer\n */\n clone(newOptions) {\n return new RoutingLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default RoutingLayer;\n", + "content": "import { Circle, Fill, Stroke, Style } from 'ol/style';\nimport { Vector as VectorSource } from 'ol/source';\nimport { Vector } from 'ol/layer';\nimport Layer from './Layer';\n/** @private */\nconst circleStyle = new Circle({\n radius: 6,\n fill: new Fill({\n color: [255, 0, 0, 1],\n }),\n stroke: new Stroke({\n color: [0, 0, 0, 1],\n width: 1,\n }),\n});\n/** @private */\nconst blackBorder = new Style({\n stroke: new Stroke({\n color: [0, 0, 0, 1],\n width: 5,\n }),\n});\n/** @private */\nconst redLine = new Style({\n image: circleStyle,\n stroke: new Stroke({\n color: [255, 0, 0, 1],\n width: 3,\n }),\n});\n/** @private */\nconst dashedRedLine = new Style({\n image: circleStyle,\n stroke: new Stroke({\n color: [255, 0, 0, 1],\n width: 3,\n lineDash: [1, 10],\n }),\n});\n/** @private */\nconst defaultStyleFunction = (feature, resolution) => {\n const minResolution = feature.get('minResolution');\n const maxResolution = feature.get('maxResolution');\n const inRange = resolution <= minResolution && resolution > maxResolution;\n if (minResolution && maxResolution && !inRange) {\n return [];\n }\n const mot = feature.get('mot');\n if (mot !== 'foot') {\n return [blackBorder, redLine];\n }\n return [dashedRedLine];\n};\n/**\n * A class use to display vector data.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass RoutingLayer extends Layer {\n /**\n * Constructor.\n * @param {Object} [options]\n * @param {ol/style/Style~StyleLike} [options.style] Style to be used for routes, uses (ol/StyleLike) [https://openlayers.org/en/latest/apidoc/module-ol_style_Style.html#~StyleLike] instances\n */\n constructor(options) {\n super(options);\n this.options = {};\n this.olLayer =\n options.olLayer ||\n new Vector({\n source: new VectorSource(),\n style: options.style || defaultStyleFunction,\n });\n }\n /**\n * Create a copy of the RoutingLayer.\n * @param {Object} newOptions Options to override\n * @return {RoutingLayer} A RoutingLayer\n */\n clone(newOptions) {\n return new RoutingLayer({ ...this.options, ...newOptions });\n }\n}\nexport default RoutingLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/RoutingLayer.js", "access": "public", @@ -10997,7 +9879,7 @@ "lineNumber": 1 }, { - "__docId__": 531, + "__docId__": 481, "kind": "variable", "name": "circleStyle", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11017,7 +9899,7 @@ "ignore": true }, { - "__docId__": 532, + "__docId__": 482, "kind": "variable", "name": "blackBorder", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11037,7 +9919,7 @@ "ignore": true }, { - "__docId__": 533, + "__docId__": 483, "kind": "variable", "name": "redLine", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11057,7 +9939,7 @@ "ignore": true }, { - "__docId__": 534, + "__docId__": 484, "kind": "variable", "name": "dashedRedLine", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11077,7 +9959,7 @@ "ignore": true }, { - "__docId__": 535, + "__docId__": 485, "kind": "function", "name": "defaultStyleFunction", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11113,7 +9995,7 @@ "ignore": true }, { - "__docId__": 536, + "__docId__": 486, "kind": "class", "name": "RoutingLayer", "memberof": "build/ol/layers/RoutingLayer.js", @@ -11137,7 +10019,7 @@ ] }, { - "__docId__": 537, + "__docId__": 487, "kind": "constructor", "name": "constructor", "memberof": "build/ol/layers/RoutingLayer.js~RoutingLayer", @@ -11172,7 +10054,7 @@ ] }, { - "__docId__": 538, + "__docId__": 488, "kind": "member", "name": "options", "memberof": "build/ol/layers/RoutingLayer.js~RoutingLayer", @@ -11189,7 +10071,7 @@ } }, { - "__docId__": 539, + "__docId__": 489, "kind": "member", "name": "olLayer", "memberof": "build/ol/layers/RoutingLayer.js~RoutingLayer", @@ -11206,7 +10088,7 @@ } }, { - "__docId__": 540, + "__docId__": 490, "kind": "method", "name": "clone", "memberof": "build/ol/layers/RoutingLayer.js~RoutingLayer", @@ -11239,7 +10121,7 @@ } }, { - "__docId__": 541, + "__docId__": 491, "kind": "file", "name": "build/ol/layers/TrackerLayer.js", "content": "\"use strict\";\n// import { Layer as OLLayer, Group, Vector as VectorLayer } from 'ol/layer';\n// import Source from 'ol/source/Source';\n// import { composeCssTransform } from 'ol/transform';\n// import stringify from 'json-stringify-safe';\n// import { Vector as VectorSource } from 'ol/source';\n// import mixin from '../../common/mixins/TrackerLayerMixin';\n// import Layer from './Layer';\n// import Worker from '../../common/tracker.worker';\n// const updateContainerTransform = (layer) => {\n// if (layer.renderedViewState) {\n// const { center, resolution, rotation } =\n// layer.mainThreadFrameState.viewState;\n// const {\n// center: renderedCenter,\n// resolution: renderedResolution,\n// rotation: renderedRotation,\n// } = layer.renderedViewState;\n// const pixelCenterRendered =\n// layer.map.getPixelFromCoordinate(renderedCenter);\n// const pixelCenter = layer.map.getPixelFromCoordinate(center);\n// // eslint-disable-next-line no-param-reassign\n// layer.transformContainer.style.transform = composeCssTransform(\n// pixelCenterRendered[0] - pixelCenter[0],\n// pixelCenterRendered[1] - pixelCenter[1],\n// renderedResolution / resolution,\n// renderedResolution / resolution,\n// rotation - renderedRotation,\n// 0,\n// 0,\n// );\n// }\n// };\n// /**\n// * Responsible for loading tracker data.\n// *\n// * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n// * @extends {Layer}\n// * @implements {TrackerLayerInterface}\n// */\n// class TrackerLayer extends mixin(Layer) {\n// /**\n// * Constructor.\n// *\n// * @param {Object} options\n// * @param {boolean} options.useDelayStyle Set the delay style.\n// * @private\n// */\n// constructor(options = {}) {\n// // We use a group to be able to add custom vector layer in extended class.\n// // For example TrajservLayer use a vectorLayer to display the complete trajectory.\n// super({\n// ...options,\n// });\n// this.firstRender = true;\n// const that = this;\n// // Worker that render trajectories.\n// this.worker = new Worker();\n// // Worker messaging and actions\n// this.worker.onmessage = (message) => {\n// if (message.data.action === 'requestRender') {\n// console.log('icci');\n// // Worker requested a new render frame\n// that.map.render();\n// } else if (that.canvas && message.data.action === 'rendered') {\n// if (\n// that.map.getView().getInteracting() ||\n// that.map.getView().getAnimating()\n// ) {\n// return;\n// }\n// // Worker provides a new render frame\n// // requestAnimationFrame(() => {\n// // if (\n// // !that.renderWhenInteracting(\n// // that.mainThreadFrameState.viewState,\n// // that.renderedViewState,\n// // ) &&\n// // (that.map.getView().getInteracting() ||\n// // that.map.getView().getAnimating())\n// // ) {\n// // return;\n// // }\n// const { imageData, nbRenderedTrajectories } = message.data;\n// this.nbRenderedTrajectories = nbRenderedTrajectories;\n// that.canvas.width = imageData.width;\n// that.canvas.height = imageData.height;\n// that.canvas.style.transform = ``;\n// that.canvas.style.width = `${that.canvas.width / that.pixelRatio}px`;\n// that.canvas.style.height = `${that.canvas.height / that.pixelRatio}px`;\n// that.renderedViewState = message.data.frameState.viewState;\n// updateContainerTransform(that);\n// that.canvas.getContext('2d').drawImage(imageData, 0, 0);\n// // });\n// that.rendering = false;\n// }\n// };\n// /** @ignore */\n// this.olLayer =\n// options.olLayer ||\n// new Group({\n// layers: [\n// new VectorLayer({\n// source: new VectorSource({ features: [] }),\n// }),\n// new OLLayer({\n// source: new Source({}),\n// render: (frameState) => {\n// if (!this.tracker || !this.tracker.canvas) {\n// return null;\n// }\n// if (!this.container) {\n// this.container = document.createElement('div');\n// this.container.style.position = 'absolute';\n// this.container.style.width = '100%';\n// this.container.style.height = '100%';\n// this.transformContainer = document.createElement('div');\n// this.transformContainer.style.position = 'absolute';\n// this.transformContainer.style.width = '100%';\n// this.transformContainer.style.height = '100%';\n// this.container.appendChild(this.transformContainer);\n// this.canvas = document.createElement('canvas');\n// this.canvas.style.position = 'absolute';\n// this.canvas.style.top = '0';\n// this.canvas.style.left = '0';\n// this.canvas.style.transformOrigin = 'top left';\n// this.transformContainer.appendChild(this.canvas);\n// this.context = this.canvas.getContext('2d');\n// }\n// this.mainThreadFrameState = frameState;\n// if (this.renderedViewState) {\n// const { resolution } = frameState.viewState;\n// const { resolution: renderedResolution } =\n// this.renderedViewState;\n// if (renderedResolution / resolution >= 3) {\n// // Avoid having really big points when zooming fast.\n// this.context.clearRect(\n// 0,\n// 0,\n// this.canvas.width,\n// this.canvas.height,\n// );\n// } else {\n// updateContainerTransform(this);\n// }\n// }\n// return this.container;\n// },\n// }),\n// ],\n// });\n// // We store the layer used to highlight the full Trajectory\n// this.vectorLayer = this.olLayer.getLayers().item(0);\n// // Options the last render run did happen. If something changes\n// // we have to render again\n// /** @ignore */\n// this.renderState = {\n// center: [0, 0],\n// zoom: null,\n// rotation: 0,\n// };\n// }\n// init(map) {\n// super.init(map);\n// if (this.map) {\n// this.olListenersKeys.push(\n// this.map.on('moveend', (evt) => {\n// const view = this.map.getView();\n// if (view.getAnimating() || view.getInteracting()) {\n// return;\n// }\n// const zoom = view.getZoom();\n// // Update the interval between render updates\n// if (this.currentZoom !== zoom) {\n// this.onZoomEnd(evt);\n// }\n// this.currentZoom = zoom;\n// this.onMoveEnd(evt);\n// }),\n// );\n// }\n// }\n// /**\n// * Destroy the container of the tracker.\n// */\n// terminate() {\n// super.terminate();\n// this.container = null;\n// }\n// /**\n// * Detect in the canvas if there is data to query at a specific coordinate.\n// * @param {ol/coordinate~Coordinate} coordinate The coordinate to test\n// * @returns\n// */\n// hasFeatureInfoAtCoordinate(coordinate) {\n// if (this.map && this.tracker && this.tracker.canvas) {\n// const context = this.tracker.canvas.getContext('2d');\n// const pixel = this.map.getPixelFromCoordinate(coordinate);\n// return !!context.getImageData(\n// pixel[0] * this.pixelRatio,\n// pixel[1] * this.pixelRatio,\n// 1,\n// 1,\n// ).data[3];\n// }\n// return false;\n// }\n// /**\n// * Render the trajectories using current map's size, resolution and rotation.\n// * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions.\n// * @overrides\n// */\n// renderTrajectories(noInterpolate) {\n// const view = this.map.getView();\n// super.renderTrajectories(\n// {\n// size: this.map.getSize(),\n// center: this.map.getView().getCenter(),\n// extent: view.calculateExtent(),\n// resolution: view.getResolution(),\n// rotation: view.getRotation(),\n// zoom: view.getZoom(),\n// pixelRatio: this.pixelRatio,\n// },\n// noInterpolate,\n// );\n// }\n// /**\n// * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod.\n// * @private\n// * @override\n// */\n// renderTrajectoriesInternal(viewState, noInterpolate) {\n// let isRendered = false;\n// const blockRendering =\n// this.map.getView().getAnimating() || this.map.getView().getInteracting();\n// if (!blockRendering && this.worker && this.mainThreadFrameState) {\n// const frameState = { ...this.mainThreadFrameState };\n// delete frameState.layerStatesArray;\n// delete frameState.viewState.projection;\n// this.worker.postMessage({\n// action: 'render',\n// trajectories: this.trajectories,\n// frameState: JSON.parse(stringify(frameState)),\n// viewState,\n// options: {\n// noInterpolate,\n// iconScale: this.iconScale,\n// hoverVehicleId: this.hoverVehicleId,\n// selectedVehicleId: this.selectedVehicleId,\n// delayDisplay: this.delayDisplay,\n// delayOutlineColor: this.delayOutlineColor,\n// useDelayStyle: this.useDelayStyle,\n// },\n// });\n// } else if (!this.worker) {\n// // Don't render the map when the map is animating or interacting.\n// isRendered = blockRendering\n// ? false\n// : super.renderTrajectoriesInternal(viewState, noInterpolate);\n// // We update the current render state.\n// if (isRendered) {\n// this.renderedViewState = { ...viewState };\n// if (this.transformContainer) {\n// this.transformContainer.style.transform = '';\n// }\n// }\n// }\n// }\n// /**\n// * Return the delay in ms before the next rendering.\n// */\n// getRefreshTimeInMs() {\n// return super.getRefreshTimeInMs(this.map.getView().getZoom());\n// }\n// /**\n// * Returns the vehicle which are at the given coordinates.\n// * Returns null when no vehicle is located at the given coordinates.\n// * @param {ol/coordinate~Coordinate} coordinate\n// * @param {number} nb Number of vehicles to return;\n// * @return {Array
                          } Vehicle feature.\n// * @override\n// */\n// getVehiclesAtCoordinate(coordinate, nb) {\n// const resolution = this.map.getView().getResolution();\n// return super.getVehiclesAtCoordinate(coordinate, resolution, nb);\n// }\n// getFeatureInfoAtCoordinate(coordinate, options = {}) {\n// if (!this.hasFeatureInfoAtCoordinate(coordinate)) {\n// return Promise.resolve({ features: [], layer: this, coordinate });\n// }\n// const resolution = this.map.getView().getResolution();\n// return super.getFeatureInfoAtCoordinate(coordinate, {\n// resolution,\n// ...options,\n// });\n// }\n// /**\n// * Function called on moveend event.\n// * To be defined in inherited classes\n// *\n// * @param {ol/MapEvent~MapEvent} evt Moveend event.\n// * @private\n// */\n// // eslint-disable-next-line no-unused-vars,class-methods-use-this\n// onMoveEnd(evt) {}\n// /**\n// * Function called on moveend event only when the zoom has changed.\n// *\n// * @param {ol/MapEvent~MapEvent} evt Moveend event.\n// * @private\n// * @override\n// */\n// // eslint-disable-next-line no-unused-vars\n// onZoomEnd(evt) {\n// super.onZoomEnd(evt);\n// }\n// /**\n// * Update the cursor style when hovering a vehicle.\n// *\n// * @private\n// * @override\n// */\n// onFeatureHover(features, layer, coordinate) {\n// super.onFeatureHover(features, layer, coordinate);\n// this.map.getTargetElement().style.cursor = features.length\n// ? 'pointer'\n// : 'auto';\n// }\n// /**\n// * Display the complete trajectory of the vehicle.\n// *\n// * @private\n// * @override\n// */\n// onFeatureClick(features, layer, coordinate) {\n// super.onFeatureClick(features, layer, coordinate);\n// if (!features.length && this.vectorLayer) {\n// this.vectorLayer.getSource().clear();\n// }\n// }\n// /**\n// * Create a copy of the TrackerLayer.\n// * @param {Object} newOptions Options to override\n// * @return {TrackerLayer} A TrackerLayer\n// */\n// clone(newOptions) {\n// return new TrackerLayer({ ...this.options, ...newOptions });\n// }\n// }\n// export default TrackerLayer;\n", @@ -11250,10 +10132,10 @@ "lineNumber": 1 }, { - "__docId__": 542, + "__docId__": 492, "kind": "file", "name": "build/ol/layers/VectorLayer.js", - "content": "import Layer from './Layer';\n/**\n * A class use to display vector data.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass VectorLayer extends Layer {\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate the coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n let features = [];\n if (this.map) {\n const pixel = this.map.getPixelFromCoordinate(coordinate);\n features = this.map.getFeaturesAtPixel(pixel, {\n layerFilter: (l) => l === this.olLayer,\n hitTolerance: this.hitTolerance,\n });\n }\n return Promise.resolve({\n features,\n layer: this,\n coordinate,\n });\n }\n /**\n * Create a copy of the VectorLayer.\n * @param {Object} newOptions Options to override\n * @return {VectorLayer} A VectorLayer\n */\n clone(newOptions) {\n return new VectorLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default VectorLayer;\n", + "content": "import Layer from './Layer';\n/**\n * A class use to display vector data.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass VectorLayer extends Layer {\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate the coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n let features = [];\n if (this.map) {\n const pixel = this.map.getPixelFromCoordinate(coordinate);\n features = this.map.getFeaturesAtPixel(pixel, {\n layerFilter: (l) => l === this.olLayer,\n hitTolerance: this.hitTolerance,\n });\n }\n return Promise.resolve({\n features,\n layer: this,\n coordinate,\n });\n }\n /**\n * Create a copy of the VectorLayer.\n * @param {Object} newOptions Options to override\n * @return {VectorLayer} A VectorLayer\n */\n clone(newOptions) {\n return new VectorLayer({ ...this.options, ...newOptions });\n }\n}\nexport default VectorLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/VectorLayer.js", "access": "public", @@ -11261,7 +10143,7 @@ "lineNumber": 1 }, { - "__docId__": 543, + "__docId__": 493, "kind": "class", "name": "VectorLayer", "memberof": "build/ol/layers/VectorLayer.js", @@ -11285,7 +10167,7 @@ ] }, { - "__docId__": 544, + "__docId__": 494, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/ol/layers/VectorLayer.js~VectorLayer", @@ -11318,7 +10200,7 @@ } }, { - "__docId__": 545, + "__docId__": 495, "kind": "method", "name": "clone", "memberof": "build/ol/layers/VectorLayer.js~VectorLayer", @@ -11351,10 +10233,10 @@ } }, { - "__docId__": 546, + "__docId__": 496, "kind": "file", "name": "build/ol/layers/WMSLayer.js", - "content": "import GeoJSON from 'ol/format/GeoJSON';\nimport Layer from './Layer';\n/**\n * Class use to display a WMS layer.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass WMSLayer extends Layer {\n /**\n * @override\n */\n constructor(options) {\n super(options);\n /** @ignore */\n this.abortController = new AbortController();\n /** @ignore */\n this.format = new GeoJSON();\n }\n /**\n * Get features infos' Url.\n * @param {ol/coordinate~Coordinate} coord\n * @return {ol/layer/Layer~Layer}\n */\n getFeatureInfoUrl(coord) {\n var _a, _b, _c, _d, _e, _f;\n if (!this.map) {\n return;\n }\n const projection = this.map.getView().getProjection();\n const resolution = this.map.getView().getResolution();\n if (resolution &&\n projection &&\n ((_b = (_a = this.olLayer) === null || _a === void 0 ? void 0 : _a.getSource()) === null || _b === void 0 ? void 0 : _b.getFeatureInfoUrl)) {\n // eslint-disable-next-line consistent-return\n return (_d = (_c = this.olLayer) === null || _c === void 0 ? void 0 : _c.getSource()) === null || _d === void 0 ? void 0 : _d.getFeatureInfoUrl(coord, resolution, projection, {\n info_format: 'application/json',\n query_layers: (_f = (_e = this.olLayer) === null || _e === void 0 ? void 0 : _e.getSource()) === null || _f === void 0 ? void 0 : _f.getParams().layers,\n });\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n var _a;\n (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n const url = this.getFeatureInfoUrl(coordinate);\n if (!url) {\n // eslint-disable-next-line no-console\n console.error('No url for the WMS layer.');\n // resolve an empty feature array something fails\n return Promise.resolve({\n features: [],\n coordinate,\n layer: this,\n });\n }\n return fetch(url, { signal })\n .then((resp) => resp.json())\n .then((r) => r.features)\n .then((data) => ({\n layer: this,\n coordinate,\n features: data.map((d) => this.format.readFeature(d)),\n }))\n .catch(() => \n // resolve an empty feature array something fails\n Promise.resolve({\n features: [],\n coordinate,\n layer: this,\n }));\n }\n /**\n * Create a copy of the WMSLayer.\n * @param {Object} newOptions Options to override\n * @return {WMSLayer} A WMSLayer\n */\n clone(newOptions) {\n return new WMSLayer(Object.assign(Object.assign({}, this.options), newOptions));\n }\n}\nexport default WMSLayer;\n", + "content": "import GeoJSON from 'ol/format/GeoJSON';\nimport Layer from './Layer';\n/**\n * Class use to display a WMS layer.\n *\n * @classproperty {ol/Map~Map} map - The map where the layer is displayed.\n * @extends {Layer}\n */\nclass WMSLayer extends Layer {\n /**\n * @override\n */\n constructor(options) {\n super(options);\n /** @ignore */\n this.abortController = new AbortController();\n /** @ignore */\n this.format = new GeoJSON();\n }\n /**\n * Get features infos' Url.\n * @param {ol/coordinate~Coordinate} coord\n * @return {ol/layer/Layer~Layer}\n */\n getFeatureInfoUrl(coord) {\n if (!this.map) {\n return;\n }\n const projection = this.map.getView().getProjection();\n const resolution = this.map.getView().getResolution();\n if (resolution &&\n projection &&\n this.olLayer?.getSource()?.getFeatureInfoUrl) {\n // eslint-disable-next-line consistent-return\n return this.olLayer\n ?.getSource()\n ?.getFeatureInfoUrl(coord, resolution, projection, {\n info_format: 'application/json',\n query_layers: this.olLayer?.getSource()?.getParams().layers,\n });\n }\n }\n /**\n * Request feature information for a given coordinate.\n * @param {ol/coordinate~Coordinate} coordinate to request the information at.\n * @return {Promise} Promise with features, layer and coordinate.\n */\n getFeatureInfoAtCoordinate(coordinate) {\n this.abortController?.abort();\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n const url = this.getFeatureInfoUrl(coordinate);\n if (!url) {\n // eslint-disable-next-line no-console\n console.error('No url for the WMS layer.');\n // resolve an empty feature array something fails\n return Promise.resolve({\n features: [],\n coordinate,\n layer: this,\n });\n }\n return fetch(url, { signal })\n .then((resp) => resp.json())\n .then((r) => r.features)\n .then((data) => ({\n layer: this,\n coordinate,\n features: data.map((d) => this.format.readFeature(d)),\n }))\n .catch(() => \n // resolve an empty feature array something fails\n Promise.resolve({\n features: [],\n coordinate,\n layer: this,\n }));\n }\n /**\n * Create a copy of the WMSLayer.\n * @param {Object} newOptions Options to override\n * @return {WMSLayer} A WMSLayer\n */\n clone(newOptions) {\n return new WMSLayer({ ...this.options, ...newOptions });\n }\n}\nexport default WMSLayer;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/WMSLayer.js", "access": "public", @@ -11362,7 +10244,7 @@ "lineNumber": 1 }, { - "__docId__": 547, + "__docId__": 497, "kind": "class", "name": "WMSLayer", "memberof": "build/ol/layers/WMSLayer.js", @@ -11386,7 +10268,7 @@ ] }, { - "__docId__": 548, + "__docId__": 498, "kind": "constructor", "name": "constructor", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11400,7 +10282,7 @@ "override": true }, { - "__docId__": 549, + "__docId__": 499, "kind": "member", "name": "abortController", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11417,7 +10299,7 @@ } }, { - "__docId__": 550, + "__docId__": 500, "kind": "member", "name": "format", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11434,7 +10316,7 @@ } }, { - "__docId__": 551, + "__docId__": 501, "kind": "method", "name": "getFeatureInfoUrl", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11467,7 +10349,7 @@ } }, { - "__docId__": 552, + "__docId__": 502, "kind": "method", "name": "getFeatureInfoAtCoordinate", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11477,7 +10359,7 @@ "longname": "build/ol/layers/WMSLayer.js~WMSLayer#getFeatureInfoAtCoordinate", "access": "public", "description": "Request feature information for a given coordinate.", - "lineNumber": 47, + "lineNumber": 48, "params": [ { "nullable": null, @@ -11500,7 +10382,7 @@ } }, { - "__docId__": 554, + "__docId__": 504, "kind": "method", "name": "clone", "memberof": "build/ol/layers/WMSLayer.js~WMSLayer", @@ -11533,7 +10415,7 @@ } }, { - "__docId__": 555, + "__docId__": 505, "kind": "file", "name": "build/ol/layers/index.js", "content": "export { default as Layer } from './Layer';\nexport { default as MapboxLayer } from './MapboxLayer';\nexport { default as MaplibreLayer } from './MaplibreLayer';\nexport { default as MapboxStyleLayer } from './MapboxStyleLayer';\nexport { default as RoutingLayer } from './RoutingLayer';\nexport { default as RealtimeLayer } from './RealtimeLayer';\nexport { default as VectorLayer } from './VectorLayer';\nexport { default as WMSLayer } from './WMSLayer';\n", @@ -11544,7 +10426,108 @@ "lineNumber": 1 }, { - "__docId__": 556, + "__docId__": 506, + "kind": "file", + "name": "build/ol/layers/tracker2.worker.js", + "content": "import stringify from 'json-stringify-safe';\n// import GeoJSON from 'ol/format/GeoJSON';\nimport { realtimeSimpleStyle } from '../../common/styles';\nimport { renderTrajectories } from '../../common/utils';\nconst debug = false;\nlet renderTimeout;\nlet count = 0;\n// const format = new GeoJSON();\nconst canvas = new OffscreenCanvas(1, 1);\nconst render = (evt) => {\n // eslint-disable-next-line no-console\n if (debug)\n console.time('render');\n // eslint-disable-next-line no-console\n if (debug)\n console.log('render', evt.data.frameState);\n count = 0;\n const { trajectories, frameState, viewState, options } = evt.data;\n const { renderedTrajectories } = renderTrajectories(canvas, Object.values(trajectories), realtimeSimpleStyle, viewState, options);\n if (debug)\n console.timeEnd('render');\n if (debug)\n console.log('NUMBER OF STYLES CREATED', count);\n const imageData = canvas.transferToImageBitmap();\n const state = { ...frameState };\n // @ts-ignore\n delete state.layerStatesArray;\n // @ts-ignore\n delete state.viewState.projection;\n // eslint-disable-next-line no-restricted-globals\n self.postMessage({\n action: 'rendered',\n imageData,\n // transform: rendererTransform,\n renderedTrajectories,\n frameState: JSON.parse(stringify(state)),\n }, [imageData]);\n renderTimeout = null;\n};\n// eslint-disable-next-line no-restricted-globals\nself.onmessage = (evt) => {\n // debugger;\n // if (evt.data.action === 'addTrajectory') {\n // const { trajectory } = evt.data;\n // const id = trajectory.properties.train_id;\n // trajectories[id] = trajectory;\n // trajectories[id].properties.olGeometry = format.readGeometry(\n // trajectory.geometry,\n // );\n // return;\n // }\n // if (evt.data.action === 'removeTrajectory') {\n // delete trajectories[evt.data.trajectoryId];\n // return;\n // }\n // if (evt.data.action === 'sendData') {\n // // eslint-disable-next-line no-console\n // if (debug) console.log('sendData', evt.data);\n // if (debug) console.time('sendData');\n // trajectories = evt.data.trajectories;\n // if (debug) console.timeEnd('sendData');\n // return;\n // }\n if (evt.data.action !== 'render') {\n return;\n }\n if (renderTimeout) {\n clearTimeout(renderTimeout);\n }\n // eslint-disable-next-line no-restricted-globals\n renderTimeout = self.setTimeout(() => {\n render(evt);\n }, 0);\n};\n// eslint-disable-next-line no-restricted-globals\nexport default self;\n", + "static": true, + "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/layers/tracker2.worker.js", + "access": "public", + "description": null, + "lineNumber": 1 + }, + { + "__docId__": 507, + "kind": "variable", + "name": "debug", + "memberof": "build/ol/layers/tracker2.worker.js", + "static": true, + "longname": "build/ol/layers/tracker2.worker.js~debug", + "access": "public", + "export": false, + "importPath": "mobility-toolbox-js/build/ol/layers/tracker2.worker.js", + "importStyle": null, + "description": null, + "lineNumber": 5, + "undocument": true, + "type": { + "types": [ + "boolean" + ] + }, + "ignore": true + }, + { + "__docId__": 508, + "kind": "variable", + "name": "count", + "memberof": "build/ol/layers/tracker2.worker.js", + "static": true, + "longname": "build/ol/layers/tracker2.worker.js~count", + "access": "public", + "export": false, + "importPath": "mobility-toolbox-js/build/ol/layers/tracker2.worker.js", + "importStyle": null, + "description": null, + "lineNumber": 7, + "undocument": true, + "type": { + "types": [ + "number" + ] + }, + "ignore": true + }, + { + "__docId__": 509, + "kind": "variable", + "name": "canvas", + "memberof": "build/ol/layers/tracker2.worker.js", + "static": true, + "longname": "build/ol/layers/tracker2.worker.js~canvas", + "access": "public", + "export": false, + "importPath": "mobility-toolbox-js/build/ol/layers/tracker2.worker.js", + "importStyle": null, + "description": null, + "lineNumber": 9, + "undocument": true, + "type": { + "types": [ + "*" + ] + }, + "ignore": true + }, + { + "__docId__": 510, + "kind": "function", + "name": "render", + "memberof": "build/ol/layers/tracker2.worker.js", + "generator": false, + "async": false, + "static": true, + "longname": "build/ol/layers/tracker2.worker.js~render", + "access": "public", + "export": false, + "importPath": "mobility-toolbox-js/build/ol/layers/tracker2.worker.js", + "importStyle": null, + "description": null, + "lineNumber": 10, + "undocument": true, + "params": [ + { + "name": "evt", + "types": [ + "*" + ] + } + ], + "return": null, + "ignore": true + }, + { + "__docId__": 511, "kind": "file", "name": "build/ol/styles/fullTrajectoryDelayStyle.js", "content": "import { Style, Fill, Stroke, Circle } from 'ol/style';\n/** @private */\nconst stroke = new Style({\n zIndex: 2,\n image: new Circle({\n radius: 5,\n fill: new Fill({\n color: '#000000',\n }),\n }),\n stroke: new Stroke({\n color: '#000000',\n width: 6,\n }),\n});\n/** @private */\nconst fill = new Style({\n zIndex: 3,\n image: new Circle({\n radius: 4,\n fill: new Fill({\n color: '#a0a0a0',\n }),\n }),\n stroke: new Stroke({\n color: '#a0a0a0',\n width: 4,\n }),\n});\nconst fullTrajectoryDelaystyle = () => {\n return [stroke, fill];\n};\nexport default fullTrajectoryDelaystyle;\n", @@ -11555,7 +10538,7 @@ "lineNumber": 1 }, { - "__docId__": 557, + "__docId__": 512, "kind": "variable", "name": "stroke", "memberof": "build/ol/styles/fullTrajectoryDelayStyle.js", @@ -11575,7 +10558,7 @@ "ignore": true }, { - "__docId__": 558, + "__docId__": 513, "kind": "variable", "name": "fill", "memberof": "build/ol/styles/fullTrajectoryDelayStyle.js", @@ -11595,7 +10578,7 @@ "ignore": true }, { - "__docId__": 559, + "__docId__": 514, "kind": "function", "name": "fullTrajectoryDelaystyle", "memberof": "build/ol/styles/fullTrajectoryDelayStyle.js", @@ -11618,10 +10601,10 @@ } }, { - "__docId__": 560, + "__docId__": 515, "kind": "file", "name": "build/ol/styles/fullTrajectoryStyle.js", - "content": "import { Style, Fill, Stroke, Circle } from 'ol/style';\n/** @private */\nconst borderStyle = new Style({\n zIndex: 2,\n image: new Circle({\n radius: 5,\n fill: new Fill({\n color: '#000000',\n }),\n }),\n stroke: new Stroke({\n color: '#000000',\n width: 6,\n }),\n});\nconst fullTrajectorystyle = (feature, resolution, options) => {\n let lineColor = '#ffffff'; // white\n const type = feature.get('type');\n let stroke = feature.get('stroke');\n if (stroke && stroke[0] !== '#') {\n stroke = `#${stroke}`;\n }\n lineColor = stroke || (options === null || options === void 0 ? void 0 : options.getBgColor(type));\n // Don't allow white lines, use red instead.\n lineColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor;\n const style = [\n borderStyle,\n new Style({\n zIndex: 3,\n image: new Circle({\n radius: 4,\n fill: new Fill({\n color: lineColor,\n }),\n }),\n stroke: new Stroke({\n color: lineColor,\n width: 4,\n }),\n }),\n ];\n return style;\n};\nexport default fullTrajectorystyle;\n", + "content": "import { Style, Fill, Stroke, Circle } from 'ol/style';\n/** @private */\nconst borderStyle = new Style({\n zIndex: 2,\n image: new Circle({\n radius: 5,\n fill: new Fill({\n color: '#000000',\n }),\n }),\n stroke: new Stroke({\n color: '#000000',\n width: 6,\n }),\n});\nconst fullTrajectorystyle = (feature, resolution, options) => {\n let lineColor = '#ffffff'; // white\n const type = feature.get('type');\n let stroke = feature.get('stroke');\n if (stroke && stroke[0] !== '#') {\n stroke = `#${stroke}`;\n }\n lineColor = stroke || options?.getBgColor(type);\n // Don't allow white lines, use red instead.\n lineColor = /#ffffff/i.test(lineColor) ? '#ff0000' : lineColor;\n const style = [\n borderStyle,\n new Style({\n zIndex: 3,\n image: new Circle({\n radius: 4,\n fill: new Fill({\n color: lineColor,\n }),\n }),\n stroke: new Stroke({\n color: lineColor,\n width: 4,\n }),\n }),\n ];\n return style;\n};\nexport default fullTrajectorystyle;\n", "static": true, "longname": "/home/olivier/GIT/mobility-toolbox-js/build/ol/styles/fullTrajectoryStyle.js", "access": "public", @@ -11629,7 +10612,7 @@ "lineNumber": 1 }, { - "__docId__": 561, + "__docId__": 516, "kind": "variable", "name": "borderStyle", "memberof": "build/ol/styles/fullTrajectoryStyle.js", @@ -11649,7 +10632,7 @@ "ignore": true }, { - "__docId__": 562, + "__docId__": 517, "kind": "function", "name": "fullTrajectorystyle", "memberof": "build/ol/styles/fullTrajectoryStyle.js", @@ -11691,7 +10674,7 @@ } }, { - "__docId__": 563, + "__docId__": 518, "kind": "file", "name": "build/ol/styles/index.js", "content": "export { default as fullTrajectoryStyle } from './fullTrajectoryStyle';\nexport { default as fullTrajectoryDelayStyle } from './fullTrajectoryDelayStyle';\n", @@ -11711,7 +10694,7 @@ }, { "kind": "packageJSON", - "content": "{\n \"name\": \"mobility-toolbox-js\",\n \"license\": \"MIT\",\n \"description\": \"Toolbox for JavaScript applications in the domains of mobility and logistics.\",\n \"version\": \"2.0.0-beta.68\",\n \"module\": \"index.js\",\n \"exports\": {\n \".\": \"./index.js\",\n \"./api\": \"./api/index.js\",\n \"./mapbox\": \"./mapbox/index.js\",\n \"./ol\": \"./ol/index.js\",\n \"./types\": \"./types/index.d.ts\"\n },\n \"dependencies\": {\n \"@turf/helpers\": \"^6.5.0\",\n \"@turf/transform-rotate\": \"^6.5.0\",\n \"@types/lodash\": \"^4.14.185\",\n \"json-stringify-safe\": \"^5.0.1\",\n \"lodash.debounce\": \"4.0.8\",\n \"lodash.throttle\": \"4.1.1\",\n \"path\": \"^0.12.7\",\n \"turf\": \"^3.0.14\",\n \"uuid\": \"8.3.2\"\n },\n \"peerDependencies\": {\n \"mapbox-gl\": \"1.13.2\",\n \"maplibre-gl\": \"2.4.0\",\n \"ol\": \"6.14.1\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"7.16.12\",\n \"@babel/eslint-parser\": \"7.16.5\",\n \"@babel/plugin-transform-runtime\": \"7.16.10\",\n \"@babel/preset-env\": \"^7.18.10\",\n \"@babel/preset-react\": \"7.16.7\",\n \"@commitlint/cli\": \"17.0.2\",\n \"@commitlint/config-conventional\": \"17.0.2\",\n \"@types/arcgis-js-api\": \"^4.24.0\",\n \"@types/arcgis-rest-api\": \"^10.4.5\",\n \"@types/geojson\": \"^7946.0.10\",\n \"@types/json-stringify-safe\": \"^5.0.0\",\n \"@types/lodash.debounce\": \"^4.0.7\",\n \"@types/lodash.throttle\": \"^4.1.7\",\n \"@types/mapbox-gl\": \"^2.7.5\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@types/topojson\": \"^3.2.3\",\n \"@types/uuid\": \"^8.3.4\",\n \"@typescript-eslint/eslint-plugin\": \"^5.30.5\",\n \"@typescript-eslint/parser\": \"^5.30.5\",\n \"autoprefixer\": \"9.7.6\",\n \"cypress\": \"9.4.1\",\n \"deepmerge\": \"4.2.2\",\n \"esbuild\": \"^0.14.47\",\n \"esbuild-jest\": \"^0.5.0\",\n \"esdoc\": \"1.1.0\",\n \"esdoc-ecmascript-proposal-plugin\": \"1.0.0\",\n \"esdoc-publish-html-plugin\": \"1.1.2\",\n \"esdoc-standard-plugin\": \"1.0.0\",\n \"esdoc-typescript-plugin\": \"^1.0.1\",\n \"eslint\": \"8.8.0\",\n \"eslint-config-airbnb\": \"19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.0.0\",\n \"eslint-config-prettier\": \"8.3.0\",\n \"eslint-plugin-cypress\": \"2.12.1\",\n \"eslint-plugin-import\": \"2.25.4\",\n \"eslint-plugin-jsx-a11y\": \"6.6.1\",\n \"eslint-plugin-prettier\": \"4.0.0\",\n \"eslint-plugin-react\": \"^7.30.1\",\n \"fixpack\": \"3.0.6\",\n \"husky\": \"8.0.1\",\n \"jest\": \"26.6.3\",\n \"jest-canvas-mock\": \"2.3.1\",\n \"jest-fetch-mock\": \"3.0.3\",\n \"jest-serializer-html\": \"7.1.0\",\n \"jest-transformer-svg\": \"^2.0.0\",\n \"jest-websocket-mock\": \"2.3.0\",\n \"lint-staged\": \"12.3.3\",\n \"mapbox-gl\": \"1.13.2\",\n \"maplibre-gl\": \"2.4.0\",\n \"mock-socket\": \"9.1.5\",\n \"next\": \"12.1.6\",\n \"next-transpile-modules\": \"9.0.0\",\n \"ol\": \"6.14.1\",\n \"openapi-typescript\": \"5.4.1\",\n \"postcss\": \"8.3.3\",\n \"postcss-loader\": \"3.0.0\",\n \"prettier\": \"2.5.1\",\n \"raw-loader\": \"4.0.2\",\n \"sass\": \"1.52.2\",\n \"sass-loader\": \"8.0.2\",\n \"sort-json\": \"2.0.1\",\n \"standard-version\": \"9.5.0\",\n \"start-server-and-test\": \"1.14.0\",\n \"stylelint\": \"14.3.0\",\n \"stylelint-config-recommended-scss\": \"5.0.2\",\n \"stylelint-config-standard\": \"24.0.0\",\n \"stylelint-scss\": \"4.1.0\",\n \"typescript\": \"^4.7.4\"\n },\n \"scripts\": {\n \"apidoc\": \"esdoc && cp apidoc/index.json doc/src/components/Esdoc\",\n \"build\": \"yarn build:tsc && yarn esbuild:iife\",\n \"build:tsc\": \"rm -rf build && yarn tsc && cp package.json build/ && cp -R src/types build/ && find build -type f -name '*.test.*' -delete\",\n \"coverage\": \"yarn test --watchAll=false --coverage --coverageDirectory=coverage\",\n \"cy:open\": \"cypress open\",\n \"cy:test\": \"start-server-and-test dev http://localhost:3000 'cypress run --browser chrome'\",\n \"cy:test:chrome\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser chrome'\",\n \"cy:test:edge\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser edge'\",\n \"cy:test:firefox\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser firefox'\",\n \"dev\": \"rm -rf .next && rm -rf doc/.next && yarn doc && yarn dev:examples\",\n \"dev:examples\": \"rm -rf .next && rm -rf doc/.next && cd build && yarn link && cd ../doc && yarn link mobility-toolbox-js && yarn dev\",\n \"doc\": \"yarn build && yarn apidoc && cd doc && rm -rf .next && rm -rf node_modules/mobility-toolbox-js && yarn install --force && yarn build\",\n \"esbuild\": \"yarn esbuild:all && yarn esbuild:iife\",\n \"esbuild:all\": \"esbuild src/index.js src/**/*.js src/**/*.ts src/**/**/*.js src/**/**/*.ts --target=chrome100 --outdir=build/ --loader:.js=jsx\",\n \"esbuild:iife\": \"yarn esbuild:iife:unminify && yarn esbuild:iife:minify\",\n \"esbuild:iife:base\": \"esbuild src/iife.js --bundle --sourcemap --target=chrome100\",\n \"esbuild:iife:minify\": \"yarn esbuild:iife:base --minify --outfile=build/mbt.min.js\",\n \"esbuild:iife:unminify\": \"yarn esbuild:iife:base --outfile=build/mbt.js\",\n \"format\": \"prettier --write 'src/**/*.js' && eslint 'src/**/*.js' --fix && stylelint 'src/**/*.css' 'src/**/*.scss' --fix\",\n \"lib\": \"REACT_APP_LIB_MODE=1 webpack --mode production\",\n \"lib:dev\": \"REACT_APP_LIB_MODE=1 webpack --mode development\",\n \"link2\": \"cmdToAdd=$(node ./scripts/read-pkg-json.js add) && $cmdToAdd && yarn build && cmdToRemove=$(node ./scripts/read-pkg-json.js remove) && $cmdToRemove && cd build && yarn link\",\n \"lint\": \"eslint src/**/*.js src/**/*.ts && stylelint src/**/*.css src/**/*.scss --allow-empty-input\",\n \"publish:beta\": \"HUSKY=0 yarn release -- --prerelease beta --skip.changelog && git push origin HEAD && yarn run build && cd build && HUSKY=0 yarn publish --tag beta && git push --tags \",\n \"publish:beta:dryrun\": \"yarn release -- --prerelease beta --dry-run --skip.changelog\",\n \"publish:public:dryrun\": \"yarn release --dry-run\",\n \"release\": \"standard-version\",\n \"start\": \"yarn doc && cd doc && yarn start\",\n \"start:examples\": \"cd doc && yarn build && yarn start\",\n \"test\": \"TZ='UTC' jest\",\n \"test:watch\": \"yarn test --watchAll\",\n \"tsc\": \"tsc\",\n \"types:backend\": \"openapi-typescript https://developer.geops.io/swagger/routing.json --output src/types/routing.d.ts && openapi-typescript https://developer.geops.io/swagger/stops.json --output src/types/stops.d.ts\"\n },\n \"browserslist\": [\n \">0.2%\",\n \"not dead\",\n \"not op_mini all\",\n \"not ie <= 11\",\n \"not android < 5\"\n ],\n \"keywords\": [\n \"mobility\",\n \"toolbox\"\n ],\n \"lint-staged\": {\n \"(src|__mocks__)/**/*.js\": [\n \"eslint --fix\",\n \"prettier --write\",\n \"git add\",\n \"yarn test --bail --findRelatedTests\"\n ],\n \"package.json\": [\n \"fixpack --sortToTop name --sortToTop license --sortToTop description --sortToTop version --sortToTop author --sortToTop main --sortToTop module --sortToTop files --sortToTop exports --sortToTop proxy --sortToTop dependencies --sortToTop peerDependencies --sortToTop devDependencies --sortToTop resolutions --sortToTop scripts\"\n ],\n \"src/**/*.{css,scss}\": [\n \"stylelint --fix --allow-empty-input\"\n ]\n },\n \"prettier\": {\n \"trailingComma\": \"all\",\n \"singleQuote\": true\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/geops/mobility-toolbox-js\"\n },\n \"sideEffects\": false,\n \"stylelint\": {\n \"plugins\": [\n \"stylelint-scss\"\n ],\n \"extends\": [\n \"stylelint-config-standard\",\n \"stylelint-config-recommended-scss\"\n ]\n }\n}\n", + "content": "{\n \"name\": \"mobility-toolbox-js\",\n \"license\": \"MIT\",\n \"description\": \"Toolbox for JavaScript applications in the domains of mobility and logistics.\",\n \"version\": \"2.0.0-beta.68\",\n \"module\": \"index.js\",\n \"exports\": {\n \".\": \"./index.js\",\n \"./api\": \"./api/index.js\",\n \"./mapbox\": \"./mapbox/index.js\",\n \"./ol\": \"./ol/index.js\",\n \"./types\": \"./types/index.d.ts\"\n },\n \"dependencies\": {\n \"@turf/helpers\": \"^6.5.0\",\n \"@turf/transform-rotate\": \"^6.5.0\",\n \"@types/lodash\": \"^4.14.185\",\n \"json-stringify-safe\": \"^5.0.1\",\n \"lodash.debounce\": \"4.0.8\",\n \"lodash.throttle\": \"4.1.1\",\n \"path\": \"^0.12.7\",\n \"turf\": \"^3.0.14\",\n \"uuid\": \"8.3.2\"\n },\n \"peerDependencies\": {\n \"mapbox-gl\": \"1.13.2\",\n \"maplibre-gl\": \"2.4.0\",\n \"ol\": \"6.14.1\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"7.16.12\",\n \"@babel/eslint-parser\": \"7.16.5\",\n \"@babel/plugin-transform-runtime\": \"7.16.10\",\n \"@babel/preset-env\": \"^7.18.10\",\n \"@babel/preset-react\": \"7.16.7\",\n \"@commitlint/cli\": \"17.0.2\",\n \"@commitlint/config-conventional\": \"17.0.2\",\n \"@types/arcgis-js-api\": \"^4.24.0\",\n \"@types/arcgis-rest-api\": \"^10.4.5\",\n \"@types/geojson\": \"^7946.0.10\",\n \"@types/json-stringify-safe\": \"^5.0.0\",\n \"@types/lodash.debounce\": \"^4.0.7\",\n \"@types/lodash.throttle\": \"^4.1.7\",\n \"@types/mapbox-gl\": \"^2.7.5\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@types/topojson\": \"^3.2.3\",\n \"@types/uuid\": \"^8.3.4\",\n \"@typescript-eslint/eslint-plugin\": \"^5.30.5\",\n \"@typescript-eslint/parser\": \"^5.30.5\",\n \"autoprefixer\": \"9.7.6\",\n \"cypress\": \"9.4.1\",\n \"deepmerge\": \"4.2.2\",\n \"esbuild\": \"^0.14.47\",\n \"esbuild-jest\": \"^0.5.0\",\n \"esdoc\": \"1.1.0\",\n \"esdoc-ecmascript-proposal-plugin\": \"1.0.0\",\n \"esdoc-publish-html-plugin\": \"1.1.2\",\n \"esdoc-standard-plugin\": \"1.0.0\",\n \"esdoc-typescript-plugin\": \"^1.0.1\",\n \"eslint\": \"8.8.0\",\n \"eslint-config-airbnb\": \"19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.0.0\",\n \"eslint-config-prettier\": \"8.3.0\",\n \"eslint-plugin-cypress\": \"2.12.1\",\n \"eslint-plugin-import\": \"2.25.4\",\n \"eslint-plugin-jsx-a11y\": \"6.6.1\",\n \"eslint-plugin-prettier\": \"4.0.0\",\n \"eslint-plugin-react\": \"^7.30.1\",\n \"fixpack\": \"3.0.6\",\n \"husky\": \"8.0.1\",\n \"jest\": \"26.6.3\",\n \"jest-canvas-mock\": \"2.3.1\",\n \"jest-fetch-mock\": \"3.0.3\",\n \"jest-serializer-html\": \"7.1.0\",\n \"jest-transformer-svg\": \"^2.0.0\",\n \"jest-websocket-mock\": \"2.3.0\",\n \"lint-staged\": \"12.3.3\",\n \"mapbox-gl\": \"1.13.2\",\n \"maplibre-gl\": \"2.4.0\",\n \"mock-socket\": \"9.1.5\",\n \"next\": \"12.1.6\",\n \"next-transpile-modules\": \"9.0.0\",\n \"ol\": \"6.14.1\",\n \"openapi-typescript\": \"5.4.1\",\n \"postcss\": \"8.3.3\",\n \"postcss-loader\": \"3.0.0\",\n \"prettier\": \"2.5.1\",\n \"raw-loader\": \"4.0.2\",\n \"sass\": \"1.52.2\",\n \"sass-loader\": \"8.0.2\",\n \"sort-json\": \"2.0.1\",\n \"standard-version\": \"9.5.0\",\n \"start-server-and-test\": \"1.14.0\",\n \"stylelint\": \"14.3.0\",\n \"stylelint-config-recommended-scss\": \"5.0.2\",\n \"stylelint-config-standard\": \"24.0.0\",\n \"stylelint-scss\": \"4.1.0\",\n \"typescript\": \"^4.7.4\"\n },\n \"scripts\": {\n \"apidoc\": \"esdoc && cp apidoc/index.json doc/src/components/Esdoc\",\n \"build\": \"yarn build:tsc && yarn esbuild:iife\",\n \"build:tsc\": \"rm -rf build && yarn tsc && cp package.json build/ && cp -R src/types build/ && find build -type f -name '*.test.*' -delete\",\n \"coverage\": \"yarn test --watchAll=false --coverage --coverageDirectory=coverage\",\n \"cy:open\": \"cypress open\",\n \"cy:test\": \"start-server-and-test dev http://localhost:3000 'cypress run --browser chrome'\",\n \"cy:test:chrome\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser chrome'\",\n \"cy:test:edge\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser edge'\",\n \"cy:test:firefox\": \"yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser firefox'\",\n \"dev\": \"rm -rf .next && rm -rf doc/.next && yarn doc && yarn dev:examples\",\n \"dev:examples\": \"rm -rf .next && rm -rf doc/.next && yarn build && cd build && yarn link && cd ../doc && yarn link mobility-toolbox-js && yarn dev\",\n \"doc\": \"yarn build && yarn apidoc && cd doc && rm -rf .next && rm -rf node_modules/mobility-toolbox-js && yarn install --force && yarn build\",\n \"esbuild\": \"yarn esbuild:all && yarn esbuild:iife\",\n \"esbuild:all\": \"esbuild src/index.js src/**/*.js src/**/*.ts src/**/**/*.js src/**/**/*.ts --target=chrome100 --outdir=build/ --loader:.js=jsx\",\n \"esbuild:iife\": \"yarn esbuild:iife:unminify && yarn esbuild:iife:minify\",\n \"esbuild:iife:base\": \"esbuild src/iife.js --bundle --sourcemap --target=chrome100\",\n \"esbuild:iife:minify\": \"yarn esbuild:iife:base --minify --outfile=build/mbt.min.js\",\n \"esbuild:iife:unminify\": \"yarn esbuild:iife:base --outfile=build/mbt.js\",\n \"format\": \"prettier --write 'src/**/*.js' && eslint 'src/**/*.js' --fix && stylelint 'src/**/*.css' 'src/**/*.scss' --fix\",\n \"lib\": \"REACT_APP_LIB_MODE=1 webpack --mode production\",\n \"lib:dev\": \"REACT_APP_LIB_MODE=1 webpack --mode development\",\n \"link2\": \"cmdToAdd=$(node ./scripts/read-pkg-json.js add) && $cmdToAdd && yarn build && cmdToRemove=$(node ./scripts/read-pkg-json.js remove) && $cmdToRemove && cd build && yarn link\",\n \"lint\": \"eslint src/**/*.js src/**/*.ts && stylelint src/**/*.css src/**/*.scss --allow-empty-input\",\n \"publish:beta\": \"HUSKY=0 yarn release -- --prerelease beta --skip.changelog && git push origin HEAD && yarn run build && cd build && HUSKY=0 yarn publish --tag beta && git push --tags \",\n \"publish:beta:dryrun\": \"yarn release -- --prerelease beta --dry-run --skip.changelog\",\n \"publish:public:dryrun\": \"yarn release --dry-run\",\n \"release\": \"standard-version\",\n \"start\": \"yarn doc && cd doc && yarn start\",\n \"start:examples\": \"cd doc && yarn build && yarn start\",\n \"test\": \"TZ='UTC' jest\",\n \"test:watch\": \"yarn test --watchAll\",\n \"tsc\": \"tsc\",\n \"types:backend\": \"openapi-typescript https://developer.geops.io/swagger/routing.json --output src/types/routing.d.ts && openapi-typescript https://developer.geops.io/swagger/stops.json --output src/types/stops.d.ts\"\n },\n \"browserslist\": [\n \">0.2%\",\n \"not dead\",\n \"not op_mini all\",\n \"not ie <= 11\",\n \"not android < 5\"\n ],\n \"keywords\": [\n \"mobility\",\n \"toolbox\"\n ],\n \"lint-staged\": {\n \"(src|__mocks__)/**/*.js\": [\n \"eslint --fix\",\n \"prettier --write\",\n \"git add\",\n \"yarn test --bail --findRelatedTests\"\n ],\n \"package.json\": [\n \"fixpack --sortToTop name --sortToTop license --sortToTop description --sortToTop version --sortToTop author --sortToTop main --sortToTop module --sortToTop files --sortToTop exports --sortToTop proxy --sortToTop dependencies --sortToTop peerDependencies --sortToTop devDependencies --sortToTop resolutions --sortToTop scripts\"\n ],\n \"src/**/*.{css,scss}\": [\n \"stylelint --fix --allow-empty-input\"\n ]\n },\n \"prettier\": {\n \"trailingComma\": \"all\",\n \"singleQuote\": true\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/geops/mobility-toolbox-js\"\n },\n \"sideEffects\": false,\n \"stylelint\": {\n \"plugins\": [\n \"stylelint-scss\"\n ],\n \"extends\": [\n \"stylelint-config-standard\",\n \"stylelint-config-recommended-scss\"\n ]\n }\n}\n", "longname": "/home/olivier/GIT/mobility-toolbox-js/package.json", "name": "package.json", "static": true, @@ -11967,7 +10950,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#apiKey", "access": "public", "description": "Key used for RoutingApi requests.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "string" @@ -11982,7 +10965,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#stopsApiKey", "access": "public", "description": "Key used for Stop lookup requests (defaults to apiKey).", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "string" @@ -11997,7 +10980,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#stopsApiUrl", "access": "public", "description": "Url used for Stop lookup requests (defaults to https://api.geops.io/stops/v1/lookup/).", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "string" @@ -12012,7 +10995,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#graphs", "access": "public", "description": "Array of routing graphs and min/max zoom levels. If you use the control in combination with the [geOps Maps API](https://developer.geops.io/apis/maps/), you may want to use the optimal level of generalizations: \"[['gen4', 0, 8], ['gen3', 8, 9], ['gen2', 9, 11], ['gen1', 11, 13], ['osm', 13, 99]]\"", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "Array.>" @@ -12027,7 +11010,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#mot", "access": "public", "description": "Mean of transport to be used for routing.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "string" @@ -12042,7 +11025,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#routingApiParams", "access": "public", "description": "object of additional parameters to pass to the routing api request.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "object" @@ -12057,7 +11040,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#snapToClosestStation", "access": "public", "description": "If true, the routing will snap the coordinate to the closest station. Default to false.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "object" @@ -12072,7 +11055,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#useRawViaPoints", "access": "public", "description": "Experimental property. Wen true, it allows the user to add via points using different kind of string. See \"via\" parameter defined by the [geOps Routing API](https://developer.geops.io/apis/routing/). Default to false, only array of coordinates and station's id are supported as via points.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "boolean" @@ -12087,7 +11070,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#routingLayer", "access": "public", "description": "Layer for adding route features.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "RoutingLayer", @@ -12103,7 +11086,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#onRouteError", "access": "public", "description": "Callback on error.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "function" @@ -12118,7 +11101,7 @@ "longname": "build/ol/controls/RoutingControl.js~RoutingControl#loading", "access": "public", "description": "True if the control is requesting the backend.", - "lineNumber": 80, + "lineNumber": 79, "type": { "types": [ "boolean" diff --git a/package.json b/package.json index 6e8bf7f8..c854f340 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "cy:test:edge": "yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser edge'", "cy:test:firefox": "yarn build && start-server-and-test start http://localhost:3000 'cypress run --browser firefox'", "dev": "rm -rf .next && rm -rf doc/.next && yarn doc && yarn dev:examples", - "dev:examples": "rm -rf .next && rm -rf doc/.next && cd build && yarn link && cd ../doc && yarn link mobility-toolbox-js && yarn dev", + "dev:examples": "rm -rf .next && rm -rf doc/.next && yarn build && cd build && yarn link && cd ../doc && yarn link mobility-toolbox-js && yarn dev", "doc": "yarn build && yarn apidoc && cd doc && rm -rf .next && rm -rf node_modules/mobility-toolbox-js && yarn install --force && yarn build", "esbuild": "yarn esbuild:all && yarn esbuild:iife", "esbuild:all": "esbuild src/index.js src/**/*.js src/**/*.ts src/**/**/*.js src/**/**/*.ts --target=chrome100 --outdir=build/ --loader:.js=jsx", diff --git a/src/common/mixins/RealtimeLayerMixin.ts b/src/common/mixins/RealtimeLayerMixin.ts index 114148c0..3cbc4dec 100644 --- a/src/common/mixins/RealtimeLayerMixin.ts +++ b/src/common/mixins/RealtimeLayerMixin.ts @@ -13,6 +13,7 @@ import { EventsKey } from 'ol/events'; import { ObjectEvent } from 'ol/Object'; import { Coordinate } from 'ol/coordinate'; import { Feature } from 'ol'; +import stringify from 'json-stringify-safe'; import realtimeDefaultStyle from '../styles/realtimeDefaultStyle'; import { RealtimeAPI, RealtimeModes } from '../../api'; import renderTrajectories from '../utils/renderTrajectories'; @@ -955,6 +956,10 @@ function RealtimeLayerMixin(Base: T) { this.trajectories = {}; } this.trajectories[trajectory.properties.train_id] = trajectory; + this.worker?.postMessage({ + action: 'addTrajectory', + trajectory, + }); // @ts-ignore the parameter are set by subclasses this.renderTrajectories(); } @@ -969,6 +974,10 @@ function RealtimeLayerMixin(Base: T) { if (this.trajectories) { delete this.trajectories[id]; } + this.worker?.postMessage({ + action: 'removeTrajectory', + trajectoryId: id, + }); } /** @@ -1043,7 +1052,7 @@ function RealtimeLayerMixin(Base: T) { this.map.getView().getProjection(), ), }; - } else { + } else if (!this.worker) { trajectory.properties.olGeometry = this.format.readGeometry(geometry); } diff --git a/src/common/styles/realtimeSimpleStyle.ts b/src/common/styles/realtimeSimpleStyle.ts index ec6ac810..7dee79ba 100644 --- a/src/common/styles/realtimeSimpleStyle.ts +++ b/src/common/styles/realtimeSimpleStyle.ts @@ -1,16 +1,15 @@ -import type { RealtimeStyleFunction } from '../../types'; +import type { AnyCanvas, RealtimeStyleFunction } from '../../types'; +import createCanvas from '../utils/createCanvas'; /** * A very simple tracker style. * Display blue point for each train. */ -let canvas: HTMLCanvasElement; +let canvas: AnyCanvas | null; const realtimeSimpleStyle: RealtimeStyleFunction = () => { if (!canvas) { - canvas = document.createElement('canvas'); - canvas.width = 15; - canvas.height = 15; - const ctx = canvas.getContext('2d'); + canvas = createCanvas(15, 15); + const ctx = canvas?.getContext('2d'); if (ctx) { ctx.arc(8, 8, 5, 0, 2 * Math.PI, false); ctx.fillStyle = '#8ED6FF'; diff --git a/src/common/tracker.worker.ts b/src/common/tracker.worker.ts index ba16dc4c..e936c37b 100644 --- a/src/common/tracker.worker.ts +++ b/src/common/tracker.worker.ts @@ -1,11 +1,21 @@ +/// +/// +/// +/// + import stringify from 'json-stringify-safe'; -// import GeoJSON from 'ol/format/GeoJSON'; +import GeoJSON from 'ol/format/GeoJSON'; import type { FrameState } from 'ol/PluggableMap'; import type { RealtimeStyleOptions, RealtimeTrajectories } from '../types'; -import { realtimeSimpleStyle } from './styles'; -import { renderTrajectories } from './utils'; +import { realtimeSimpleStyle, realtimeDefaultStyle } from './styles'; +import { renderTrajectories, realtimeConfig } from './utils'; const debug = false; +const trajectories: RealtimeTrajectories = {}; + +// Default type of `self` is `WorkerGlobalScope & typeof globalThis` +// https://github.com/microsoft/TypeScript/issues/14877 +declare let self: DedicatedWorkerGlobalScope; export type RealtimeWorkerRenderEvent = { data: { @@ -18,7 +28,7 @@ export type RealtimeWorkerRenderEvent = { let renderTimeout: number | null; let count = 0; -// const format = new GeoJSON(); +const format = new GeoJSON(); const canvas = new OffscreenCanvas(1, 1); const render = (evt: RealtimeWorkerRenderEvent) => { @@ -27,14 +37,21 @@ const render = (evt: RealtimeWorkerRenderEvent) => { // eslint-disable-next-line no-console if (debug) console.log('render', evt.data.frameState); count = 0; - const { trajectories, frameState, viewState, options } = evt.data; + const { frameState, viewState, options } = evt.data; const { renderedTrajectories } = renderTrajectories( canvas, Object.values(trajectories), - realtimeSimpleStyle, + realtimeDefaultStyle, viewState, - options, + { + ...options, + getRadius: realtimeConfig.getRadius, + getTextColor: realtimeConfig.getTextColor, + getBgColor: realtimeConfig.getBgColor, + getDelayColor: realtimeConfig.getDelayColor, + getTextSize: realtimeConfig.getTextSize, + }, ); if (debug) console.timeEnd('render'); @@ -53,7 +70,8 @@ const render = (evt: RealtimeWorkerRenderEvent) => { action: 'rendered', imageData, // transform: rendererTransform, - renderedTrajectories, + renderedTrajectories: [], + nbRenderedTrajectories: renderedTrajectories?.length, frameState: JSON.parse(stringify(state)), }, [imageData], @@ -64,20 +82,20 @@ const render = (evt: RealtimeWorkerRenderEvent) => { // eslint-disable-next-line no-restricted-globals self.onmessage = (evt) => { // debugger; - // if (evt.data.action === 'addTrajectory') { - // const { trajectory } = evt.data; - // const id = trajectory.properties.train_id; - // trajectories[id] = trajectory; - // trajectories[id].properties.olGeometry = format.readGeometry( - // trajectory.geometry, - // ); - // return; - // } + if (evt.data.action === 'addTrajectory') { + const { trajectory } = evt.data; + const id = trajectory.properties.train_id; + trajectories[id] = trajectory; + trajectories[id].properties.olGeometry = format.readGeometry( + trajectory.geometry, + ); + return; + } - // if (evt.data.action === 'removeTrajectory') { - // delete trajectories[evt.data.trajectoryId]; - // return; - // } + if (evt.data.action === 'removeTrajectory') { + delete trajectories[evt.data.trajectoryId]; + return; + } // if (evt.data.action === 'sendData') { // // eslint-disable-next-line no-console @@ -102,5 +120,5 @@ self.onmessage = (evt) => { }, 0); }; -// eslint-disable-next-line no-restricted-globals -export default self; +// We need an export to force this file to act like a module, so TS will let us re-type `self` +// export default null; diff --git a/src/common/tracker2.worker.js b/src/common/tracker2.worker.js deleted file mode 100644 index ef73eb5d..00000000 --- a/src/common/tracker2.worker.js +++ /dev/null @@ -1,95 +0,0 @@ -import stringify from 'json-stringify-safe'; -// import GeoJSON from 'ol/format/GeoJSON'; -import { realtimeSimpleStyle } from './styles'; -import { renderTrajectories } from './utils'; - -const debug = false; - -let renderTimeout; -let count = 0; -// const format = new GeoJSON(); -const canvas = new OffscreenCanvas(1, 1); - -const render = (evt) => { - // eslint-disable-next-line no-console - if (debug) console.time('render'); - // eslint-disable-next-line no-console - if (debug) console.log('render', evt.data.frameState); - count = 0; - const { trajectories, frameState, viewState, options } = evt.data; - - const { renderedTrajectories } = renderTrajectories( - canvas, - Object.values(trajectories), - realtimeSimpleStyle, - viewState, - options, - ); - - if (debug) console.timeEnd('render'); - if (debug) console.log('NUMBER OF STYLES CREATED', count); - - const imageData = canvas.transferToImageBitmap(); - const state = { ...frameState }; - // @ts-ignore - delete state.layerStatesArray; - // @ts-ignore - delete state.viewState.projection; - - // eslint-disable-next-line no-restricted-globals - self.postMessage( - { - action: 'rendered', - imageData, - // transform: rendererTransform, - renderedTrajectories, - frameState: JSON.parse(stringify(state)), - }, - [imageData], - ); - renderTimeout = null; -}; - -// eslint-disable-next-line no-restricted-globals -self.onmessage = (evt) => { - // debugger; - // if (evt.data.action === 'addTrajectory') { - // const { trajectory } = evt.data; - // const id = trajectory.properties.train_id; - // trajectories[id] = trajectory; - // trajectories[id].properties.olGeometry = format.readGeometry( - // trajectory.geometry, - // ); - // return; - // } - - // if (evt.data.action === 'removeTrajectory') { - // delete trajectories[evt.data.trajectoryId]; - // return; - // } - - // if (evt.data.action === 'sendData') { - // // eslint-disable-next-line no-console - // if (debug) console.log('sendData', evt.data); - // if (debug) console.time('sendData'); - // trajectories = evt.data.trajectories; - // if (debug) console.timeEnd('sendData'); - // return; - // } - - if (evt.data.action !== 'render') { - return; - } - - if (renderTimeout) { - clearTimeout(renderTimeout); - } - - // eslint-disable-next-line no-restricted-globals - renderTimeout = self.setTimeout(() => { - render(evt); - }, 0); -}; - -// eslint-disable-next-line no-restricted-globals -export default self; diff --git a/src/common/utils/createCanvas.ts b/src/common/utils/createCanvas.ts index bb583ccf..cfe72871 100644 --- a/src/common/utils/createCanvas.ts +++ b/src/common/utils/createCanvas.ts @@ -14,7 +14,7 @@ const createCanvas = (width: number, height: number): AnyCanvas | null => { return null; } - if (document?.createElement) { + if (typeof document !== 'undefined' && document?.createElement) { canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; diff --git a/src/common/utils/getVehiclePosition.ts b/src/common/utils/getVehiclePosition.ts index 1a791b44..210ba754 100644 --- a/src/common/utils/getVehiclePosition.ts +++ b/src/common/utils/getVehiclePosition.ts @@ -1,5 +1,6 @@ import { Coordinate } from 'ol/coordinate'; import GeomType from 'ol/geom/GeometryType'; +import LineString from 'ol/geom/LineString'; import { RealtimeTrajectory } from '../../api/typedefs'; export type VehiclePosition = { @@ -29,14 +30,20 @@ const getVehiclePosition = ( let coord; let rotation; + const type = geometry.getType ? geometry.getType() : trajectory.geometry.type; + const coordinates = geometry.getCoordinates + ? geometry.getCoordinates() + : trajectory.geometry.coordinates; + if (noInterpolate && coordinate) { coord = coordinate; - } else if (geometry.getType() === GeomType.POINT) { - coord = geometry.getCoordinates(); - } else if (geometry.getType() === GeomType.LINE_STRING) { + } else if (type === GeomType.POINT) { + coord = coordinates; + } else if (type === GeomType.LINE_STRING) { const intervals = timeIntervals || [[]]; const firstInterval = intervals[0]; const lastInterval = intervals[intervals.length - 1]; + const line = new LineString(coordinates); // Between the last time interval of a trajectory event and the beginning // of the new trajectory event, there is few seconds, can be 6 to 30 @@ -47,11 +54,11 @@ const getVehiclePosition = ( if (now < firstInterval[0]) { // Display first position known. [, , rotation] = firstInterval; - coord = geometry.getFirstCoordinate(); + coord = line.getFirstCoordinate(); } else if (now > lastInterval[0]) { // Display last position known. [, , rotation] = lastInterval; - coord = geometry.getLastCoordinate(); + coord = line.getLastCoordinate(); } else { // Interpolate position using time intervals. for (let j = 0; j < intervals.length - 1; j += 1) { @@ -63,7 +70,7 @@ const getVehiclePosition = ( // interpolate position inside the time interval. const timeFrac = Math.min((now - start) / (end - start), 1); const geomFrac = timeFrac * (endFrac - startFrac) + startFrac; - coord = geometry.getCoordinateAt(geomFrac); + coord = line.getCoordinateAt(geomFrac); [, , rotation] = intervals[j]; break; } diff --git a/src/common/utils/realtimeConfig.ts b/src/common/utils/realtimeConfig.ts index e2cd672c..fbc7d262 100644 --- a/src/common/utils/realtimeConfig.ts +++ b/src/common/utils/realtimeConfig.ts @@ -146,13 +146,16 @@ export const getTextSize = ( * @param {boolean} isDelayText true if the color is used for delay text of the symbol. */ export const getDelayColor = ( - delayInMs: number, - cancelled: boolean, - isDelayText: boolean, + delayInMs: number | null, + cancelled?: boolean, + isDelayText?: boolean, ): string => { if (cancelled) { return isDelayText ? '#ff0000' : '#a0a0a0'; // red or gray } + if (delayInMs === null) { + return '#a0a0a0'; // grey { r: 160, g: 160, b: 160, s: '160,160,160' }; + } if (delayInMs >= 3600000) { return '#ed004c'; // pink { r: 237, g: 0, b: 76, s: '237,0,76' }; } @@ -165,9 +168,6 @@ export const getDelayColor = ( if (delayInMs >= 180000) { return '#f7bf00'; // yellow { r: 247, g: 191, b: 0, s: '247,191,0' }; } - if (delayInMs === null) { - return '#a0a0a0'; // grey { r: 160, g: 160, b: 160, s: '160,160,160' }; - } return '#00a00c'; // green { r: 0, g: 160, b: 12, s: '0,160,12' }; }; diff --git a/src/ol/layers/RealtimeLayer.ts b/src/ol/layers/RealtimeLayer.ts index 30cd2152..a90bb784 100644 --- a/src/ol/layers/RealtimeLayer.ts +++ b/src/ol/layers/RealtimeLayer.ts @@ -60,6 +60,9 @@ const updateContainerTransform = (layer: RealtimeLayer) => { } }; +const wworker = new Worker( + new URL('../../common/tracker.worker.js', import.meta.url), +); /** * Responsible for loading and display data from a Realtime service. * @@ -96,14 +99,12 @@ class RealtimeLayer extends mixin(Layer) { }); // Worker that render trajectories. - this.worker = new Worker('../../common/tracker.worker.ts', { - type: 'module', - }); + this.worker = wworker; + console.log('ici'); // Worker messaging and actions const that = this; this.worker.onmessage = (message: any) => { if (message.data.action === 'requestRender') { - console.log('icci'); // Worker requested a new render frame that.map.render(); } else if (that.canvas && message.data.action === 'rendered') { @@ -349,16 +350,24 @@ class RealtimeLayer extends mixin(Layer) { const frameState = { ...this.mainThreadFrameState }; delete frameState.layerStatesArray; delete frameState.viewState.projection; + // console.log(this.trajectories, JSON.parse(stringify(frameState))); this.worker.postMessage({ action: 'render', - trajectories: this.trajectories, + trajectories: {}, frameState: JSON.parse(stringify(frameState)), - viewState, + viewState: { + ...viewState, + pixelRatio: this.pixelRatio || 1, + // time: this.live ? Date.now() : this.time?.getTime(), + }, options: { - noInterpolate, - iconScale: this.iconScale, + noInterpolate: + (viewState.zoom || 0) < this.minZoomInterpolation + ? true + : noInterpolate, hoverVehicleId: this.hoverVehicleId, selectedVehicleId: this.selectedVehicleId, + iconScale: this.iconScale, delayDisplay: this.delayDisplay, delayOutlineColor: this.delayOutlineColor, useDelayStyle: this.useDelayStyle, diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 6165998c..5c5ab7d1 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -2,7 +2,7 @@ import { Feature } from 'ol'; import { Coordinate } from 'ol/coordinate'; import { ObjectEvent } from 'ol/Object'; import { Pixel } from 'ol/pixel'; -import type { RealtimeTrainId } from './realtime'; +import type { RealtimeMot, RealtimeTrainId } from './realtime'; import type { CopyrightControl as MbCopyrightControl, RealtimeLayer as MbRealtimeLayer, @@ -40,17 +40,17 @@ export type RealtimeStyleOptions = { delayDisplay?: number; noInterpolate?: boolean; filter?: FilterFunction; - getRadius?: (type: number, z: number) => number; - getBgColor?: (type: number) => string; + getRadius?: (type: RealtimeMot, z: number) => number; + getBgColor?: (type: RealtimeMot) => string; getDelayColor?: ( delay: number | null, cancelled?: boolean, isDelayText?: boolean, ) => string; getDelayText?: (delay: number | null, cancelled?: boolean) => string; - getTextColor?: (type: number) => string; + getTextColor?: (type: RealtimeMot) => string; getTextSize?: ( - ctx: AnyCanvasContext | null, + ctx: AnyCanvasContext, markerSize: number, name: string, fontSize: number, diff --git a/tsconfig.json b/tsconfig.json index 11d4f4d6..425faabf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "outDir": "./build", "allowJs": true, - "target": "es6", + "target": "es2020", + "module": "es2020", "strict": true, // Generate d.ts files "declaration": true, @@ -15,7 +16,7 @@ "skipLibCheck": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, - "lib": ["webworker", "es6", "dom"] + "lib": ["webworker", "es2020", "dom"] }, "allowSyntheticDefaultImports": true, "include": ["./src/**/*"], From 85c376cf66c3e9917c6c7c1e17961709c54a60fa Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 16 Nov 2022 12:25:09 +0100 Subject: [PATCH 104/109] chore: put same code as in master --- src/common/utils/getVehiclePosition.ts | 19 ++++++------------- src/ol/layers/RealtimeLayer.ts | 1 - 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/common/utils/getVehiclePosition.ts b/src/common/utils/getVehiclePosition.ts index 210ba754..1a791b44 100644 --- a/src/common/utils/getVehiclePosition.ts +++ b/src/common/utils/getVehiclePosition.ts @@ -1,6 +1,5 @@ import { Coordinate } from 'ol/coordinate'; import GeomType from 'ol/geom/GeometryType'; -import LineString from 'ol/geom/LineString'; import { RealtimeTrajectory } from '../../api/typedefs'; export type VehiclePosition = { @@ -30,20 +29,14 @@ const getVehiclePosition = ( let coord; let rotation; - const type = geometry.getType ? geometry.getType() : trajectory.geometry.type; - const coordinates = geometry.getCoordinates - ? geometry.getCoordinates() - : trajectory.geometry.coordinates; - if (noInterpolate && coordinate) { coord = coordinate; - } else if (type === GeomType.POINT) { - coord = coordinates; - } else if (type === GeomType.LINE_STRING) { + } else if (geometry.getType() === GeomType.POINT) { + coord = geometry.getCoordinates(); + } else if (geometry.getType() === GeomType.LINE_STRING) { const intervals = timeIntervals || [[]]; const firstInterval = intervals[0]; const lastInterval = intervals[intervals.length - 1]; - const line = new LineString(coordinates); // Between the last time interval of a trajectory event and the beginning // of the new trajectory event, there is few seconds, can be 6 to 30 @@ -54,11 +47,11 @@ const getVehiclePosition = ( if (now < firstInterval[0]) { // Display first position known. [, , rotation] = firstInterval; - coord = line.getFirstCoordinate(); + coord = geometry.getFirstCoordinate(); } else if (now > lastInterval[0]) { // Display last position known. [, , rotation] = lastInterval; - coord = line.getLastCoordinate(); + coord = geometry.getLastCoordinate(); } else { // Interpolate position using time intervals. for (let j = 0; j < intervals.length - 1; j += 1) { @@ -70,7 +63,7 @@ const getVehiclePosition = ( // interpolate position inside the time interval. const timeFrac = Math.min((now - start) / (end - start), 1); const geomFrac = timeFrac * (endFrac - startFrac) + startFrac; - coord = line.getCoordinateAt(geomFrac); + coord = geometry.getCoordinateAt(geomFrac); [, , rotation] = intervals[j]; break; } diff --git a/src/ol/layers/RealtimeLayer.ts b/src/ol/layers/RealtimeLayer.ts index a90bb784..bfa92134 100644 --- a/src/ol/layers/RealtimeLayer.ts +++ b/src/ol/layers/RealtimeLayer.ts @@ -353,7 +353,6 @@ class RealtimeLayer extends mixin(Layer) { // console.log(this.trajectories, JSON.parse(stringify(frameState))); this.worker.postMessage({ action: 'render', - trajectories: {}, frameState: JSON.parse(stringify(frameState)), viewState: { ...viewState, From 1d2d2311d8b17360e2d0937186ccbff383aa6d62 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 16 Nov 2022 13:45:12 +0100 Subject: [PATCH 105/109] chore: clean code --- doc/public/static/examples/ol-tracker.js | 5 +- src/common/tracker.worker.ts | 17 +- src/ol/layers/RealtimeLayer.ts | 145 ++++----- src/ol/layers/TrackerLayer.js | 381 ----------------------- 4 files changed, 71 insertions(+), 477 deletions(-) delete mode 100644 src/ol/layers/TrackerLayer.js diff --git a/doc/public/static/examples/ol-tracker.js b/doc/public/static/examples/ol-tracker.js index 6fc680b4..044f410a 100644 --- a/doc/public/static/examples/ol-tracker.js +++ b/doc/public/static/examples/ol-tracker.js @@ -29,7 +29,10 @@ export default () => { const tracker = new RealtimeLayer({ url: 'wss://api.geops.io/tracker-ws/v1/', apiKey: window.apiKey, - // allowRenderWhenAnimating: true, + allowRenderWhenAnimating: + new URL(window.location.href)?.searchParams?.get( + 'allowRenderWhenAnimating', + ) === 'true', }); tracker.attachToMap(map); diff --git a/src/common/tracker.worker.ts b/src/common/tracker.worker.ts index e936c37b..da946d10 100644 --- a/src/common/tracker.worker.ts +++ b/src/common/tracker.worker.ts @@ -19,8 +19,6 @@ declare let self: DedicatedWorkerGlobalScope; export type RealtimeWorkerRenderEvent = { data: { - trajectories: RealtimeTrajectories; - frameState: FrameState; viewState: ViewState; options: RealtimeStyleOptions; }; @@ -35,9 +33,9 @@ const render = (evt: RealtimeWorkerRenderEvent) => { // eslint-disable-next-line no-console if (debug) console.time('render'); // eslint-disable-next-line no-console - if (debug) console.log('render', evt.data.frameState); + if (debug) console.log('render', evt.data.viewState); count = 0; - const { frameState, viewState, options } = evt.data; + const { viewState, options } = evt.data; const { renderedTrajectories } = renderTrajectories( canvas, @@ -58,21 +56,14 @@ const render = (evt: RealtimeWorkerRenderEvent) => { if (debug) console.log('NUMBER OF STYLES CREATED', count); const imageData = canvas.transferToImageBitmap(); - const state = { ...frameState }; - // @ts-ignore - delete state.layerStatesArray; - // @ts-ignore - delete state.viewState.projection; // eslint-disable-next-line no-restricted-globals self.postMessage( { action: 'rendered', imageData, - // transform: rendererTransform, - renderedTrajectories: [], - nbRenderedTrajectories: renderedTrajectories?.length, - frameState: JSON.parse(stringify(state)), + viewState, + nbRenderedTRajectories: renderedTrajectories?.length || 0, }, [imageData], ); diff --git a/src/ol/layers/RealtimeLayer.ts b/src/ol/layers/RealtimeLayer.ts index bfa92134..3370c72b 100644 --- a/src/ol/layers/RealtimeLayer.ts +++ b/src/ol/layers/RealtimeLayer.ts @@ -7,7 +7,7 @@ import Feature, { FeatureLike } from 'ol/Feature'; import { MapEvent } from 'ol'; import { Coordinate } from 'ol/coordinate'; import { ObjectEvent } from 'ol/Object'; -import stringify from 'json-stringify-safe'; +import type { State } from 'ol/View'; import Layer from './Layer'; import mixin, { RealtimeLayerMixinOptions, @@ -35,15 +35,18 @@ export type OlRealtimeLayerOptions = RealtimeLayerMixinOptions & { allowRenderWhenAnimating?: boolean; }; -const updateContainerTransform = (layer: RealtimeLayer) => { - if (layer.renderedViewState) { - const { center, resolution, rotation } = - layer.mainThreadFrameState.viewState; +const updateContainerTransform = ( + layer: RealtimeLayer, + renderedViewState: State, + mainThreadViewState: State, +) => { + if (renderedViewState) { + const { center, resolution, rotation } = mainThreadViewState; const { center: renderedCenter, resolution: renderedResolution, rotation: renderedRotation, - } = layer.renderedViewState; + } = renderedViewState; const pixelCenterRendered = layer.map.getPixelFromCoordinate(renderedCenter); const pixelCenter = layer.map.getPixelFromCoordinate(center); @@ -100,52 +103,9 @@ class RealtimeLayer extends mixin(Layer) { // Worker that render trajectories. this.worker = wworker; - console.log('ici'); + // Worker messaging and actions - const that = this; - this.worker.onmessage = (message: any) => { - if (message.data.action === 'requestRender') { - // Worker requested a new render frame - that.map.render(); - } else if (that.canvas && message.data.action === 'rendered') { - if ( - that.map.getView().getInteracting() || - that.map.getView().getAnimating() - ) { - return; - } - // Worker provides a new render frame - // requestAnimationFrame(() => { - // if ( - // !that.renderWhenInteracting( - // that.mainThreadFrameState.viewState, - // that.renderedViewState, - // ) && - // (that.map.getView().getInteracting() || - // that.map.getView().getAnimating()) - // ) { - // return; - // } - const { imageData, nbRenderedTrajectories } = message.data; - this.nbRenderedTrajectories = nbRenderedTrajectories; - that.canvas.width = imageData.width; - that.canvas.height = imageData.height; - if ((that.canvas as HTMLCanvasElement)?.style) { - (that.canvas as HTMLCanvasElement).style.transform = ``; - (that.canvas as HTMLCanvasElement).style.width = `${ - that.canvas.width / (that.pixelRatio || 1) - }px`; - (that.canvas as HTMLCanvasElement).style.height = `${ - that.canvas.height / (that.pixelRatio || 1) - }px`; - } - that.renderedViewState = message.data.frameState.viewState; - updateContainerTransform(that); - that.canvas?.getContext('2d')?.drawImage(imageData, 0, 0); - // }); - that.rendering = false; - } - }; + this.worker.onmessage = this.onWorkerMessage.bind(this); this.allowRenderWhenAnimating = !!options.allowRenderWhenAnimating; @@ -188,19 +148,12 @@ class RealtimeLayer extends mixin(Layer) { } } - this.mainThreadFrameState = frameState; + this.mainThreadViewState = frameState.viewState; if (this.renderedViewState) { - const { - // center, - resolution, - // rotation - } = frameState.viewState; - const { - // center: renderedCenter, - resolution: renderedResolution, - // rotation: renderedRotation, - } = this.renderedViewState; + const { resolution } = frameState.viewState; + const { resolution: renderedResolution } = + this.renderedViewState; if (renderedResolution / resolution >= 3) { // Avoid having really big points when zooming fast. @@ -212,19 +165,11 @@ class RealtimeLayer extends mixin(Layer) { this.canvas?.height as number, ); } else { - updateContainerTransform(this); - // const pixelCenterRendered = - // this.map.getPixelFromCoordinate(renderedCenter); - // const pixelCenter = this.map.getPixelFromCoordinate(center); - // this.transformContainer.style.transform = composeCssTransform( - // pixelCenterRendered[0] - pixelCenter[0], - // pixelCenterRendered[1] - pixelCenter[1], - // renderedResolution / resolution, - // renderedResolution / resolution, - // rotation - renderedRotation, - // 0, - // 0, - // ); + updateContainerTransform( + this, + this.renderedViewState, + frameState.viewState, + ); } } return this.container; @@ -346,17 +291,11 @@ class RealtimeLayer extends mixin(Layer) { : this.map.getView().getAnimating() || this.map.getView().getInteracting(); - if (!blockRendering && this.worker && this.mainThreadFrameState) { - const frameState = { ...this.mainThreadFrameState }; - delete frameState.layerStatesArray; - delete frameState.viewState.projection; - // console.log(this.trajectories, JSON.parse(stringify(frameState))); + if (!blockRendering && this.worker && this.mainThreadViewState) { this.worker.postMessage({ action: 'render', - frameState: JSON.parse(stringify(frameState)), viewState: { ...viewState, - pixelRatio: this.pixelRatio || 1, // time: this.live ? Date.now() : this.time?.getTime(), }, options: { @@ -563,6 +502,48 @@ class RealtimeLayer extends mixin(Layer) { clone(newOptions: OlRealtimeLayerOptions) { return new RealtimeLayer({ ...this.options, ...newOptions }); } + + onWorkerMessage(message: any) { + if (message.data.action === 'requestRender') { + // Worker requested a new render frame + this.map.render(); + } else if (this.canvas && message.data.action === 'rendered') { + if ( + !this.allowRenderWhenAnimating && + (this.map.getView().getInteracting() || + this.map.getView().getAnimating()) + ) { + return; + } + // Worker provides a new render frame + // requestAnimationFrame(() => { + // if ( + // !that.renderWhenInteracting( + // that.mainThreadFrameState.viewState, + // that.renderedViewState, + // ) && + // (that.map.getView().getInteracting() || + // that.map.getView().getAnimating()) + // ) { + // return; + // } + const { imageData, viewState } = message.data; + this.canvas.width = imageData.width; + this.canvas.height = imageData.height; + if ((this.canvas as HTMLCanvasElement)?.style) { + (this.canvas as HTMLCanvasElement).style.transform = ``; + (this.canvas as HTMLCanvasElement).style.width = `${ + this.canvas.width / (this.pixelRatio || 1) + }px`; + (this.canvas as HTMLCanvasElement).style.height = `${ + this.canvas.height / (this.pixelRatio || 1) + }px`; + } + this.renderedViewState = viewState; + updateContainerTransform(this, viewState, this.mainThreadViewState); + this.canvas?.getContext('2d')?.drawImage(imageData, 0, 0); + } + } } export default RealtimeLayer; diff --git a/src/ol/layers/TrackerLayer.js b/src/ol/layers/TrackerLayer.js deleted file mode 100644 index d2c7b882..00000000 --- a/src/ol/layers/TrackerLayer.js +++ /dev/null @@ -1,381 +0,0 @@ -// import { Layer as OLLayer, Group, Vector as VectorLayer } from 'ol/layer'; -// import Source from 'ol/source/Source'; -// import { composeCssTransform } from 'ol/transform'; -// import stringify from 'json-stringify-safe'; -// import { Vector as VectorSource } from 'ol/source'; -// import mixin from '../../common/mixins/TrackerLayerMixin'; - -// import Layer from './Layer'; - -// import Worker from '../../common/tracker.worker'; - -// const updateContainerTransform = (layer) => { -// if (layer.renderedViewState) { -// const { center, resolution, rotation } = -// layer.mainThreadFrameState.viewState; -// const { -// center: renderedCenter, -// resolution: renderedResolution, -// rotation: renderedRotation, -// } = layer.renderedViewState; -// const pixelCenterRendered = -// layer.map.getPixelFromCoordinate(renderedCenter); -// const pixelCenter = layer.map.getPixelFromCoordinate(center); -// // eslint-disable-next-line no-param-reassign -// layer.transformContainer.style.transform = composeCssTransform( -// pixelCenterRendered[0] - pixelCenter[0], -// pixelCenterRendered[1] - pixelCenter[1], -// renderedResolution / resolution, -// renderedResolution / resolution, -// rotation - renderedRotation, -// 0, -// 0, -// ); -// } -// }; - -// /** -// * Responsible for loading tracker data. -// * -// * @classproperty {ol/Map~Map} map - The map where the layer is displayed. -// * @extends {Layer} -// * @implements {TrackerLayerInterface} -// */ -// class TrackerLayer extends mixin(Layer) { -// /** -// * Constructor. -// * -// * @param {Object} options -// * @param {boolean} options.useDelayStyle Set the delay style. -// * @private -// */ -// constructor(options = {}) { -// // We use a group to be able to add custom vector layer in extended class. -// // For example TrajservLayer use a vectorLayer to display the complete trajectory. -// super({ -// ...options, -// }); -// this.firstRender = true; -// const that = this; - -// // Worker that render trajectories. -// this.worker = new Worker(); - -// // Worker messaging and actions -// this.worker.onmessage = (message) => { -// if (message.data.action === 'requestRender') { -// console.log('icci'); -// // Worker requested a new render frame -// that.map.render(); -// } else if (that.canvas && message.data.action === 'rendered') { -// if ( -// that.map.getView().getInteracting() || -// that.map.getView().getAnimating() -// ) { -// return; -// } -// // Worker provides a new render frame -// // requestAnimationFrame(() => { -// // if ( -// // !that.renderWhenInteracting( -// // that.mainThreadFrameState.viewState, -// // that.renderedViewState, -// // ) && -// // (that.map.getView().getInteracting() || -// // that.map.getView().getAnimating()) -// // ) { -// // return; -// // } -// const { imageData, nbRenderedTrajectories } = message.data; -// this.nbRenderedTrajectories = nbRenderedTrajectories; -// that.canvas.width = imageData.width; -// that.canvas.height = imageData.height; -// that.canvas.style.transform = ``; -// that.canvas.style.width = `${that.canvas.width / that.pixelRatio}px`; -// that.canvas.style.height = `${that.canvas.height / that.pixelRatio}px`; -// that.renderedViewState = message.data.frameState.viewState; -// updateContainerTransform(that); -// that.canvas.getContext('2d').drawImage(imageData, 0, 0); -// // }); -// that.rendering = false; -// } -// }; - -// /** @ignore */ -// this.olLayer = -// options.olLayer || -// new Group({ -// layers: [ -// new VectorLayer({ -// source: new VectorSource({ features: [] }), -// }), -// new OLLayer({ -// source: new Source({}), -// render: (frameState) => { -// if (!this.tracker || !this.tracker.canvas) { -// return null; -// } - -// if (!this.container) { -// this.container = document.createElement('div'); -// this.container.style.position = 'absolute'; -// this.container.style.width = '100%'; -// this.container.style.height = '100%'; -// this.transformContainer = document.createElement('div'); -// this.transformContainer.style.position = 'absolute'; -// this.transformContainer.style.width = '100%'; -// this.transformContainer.style.height = '100%'; -// this.container.appendChild(this.transformContainer); -// this.canvas = document.createElement('canvas'); -// this.canvas.style.position = 'absolute'; -// this.canvas.style.top = '0'; -// this.canvas.style.left = '0'; -// this.canvas.style.transformOrigin = 'top left'; -// this.transformContainer.appendChild(this.canvas); -// this.context = this.canvas.getContext('2d'); -// } -// this.mainThreadFrameState = frameState; -// if (this.renderedViewState) { -// const { resolution } = frameState.viewState; -// const { resolution: renderedResolution } = -// this.renderedViewState; - -// if (renderedResolution / resolution >= 3) { -// // Avoid having really big points when zooming fast. -// this.context.clearRect( -// 0, -// 0, -// this.canvas.width, -// this.canvas.height, -// ); -// } else { -// updateContainerTransform(this); -// } -// } -// return this.container; -// }, -// }), -// ], -// }); - -// // We store the layer used to highlight the full Trajectory -// this.vectorLayer = this.olLayer.getLayers().item(0); - -// // Options the last render run did happen. If something changes -// // we have to render again -// /** @ignore */ -// this.renderState = { -// center: [0, 0], -// zoom: null, -// rotation: 0, -// }; -// } - -// init(map) { -// super.init(map); -// if (this.map) { -// this.olListenersKeys.push( -// this.map.on('moveend', (evt) => { -// const view = this.map.getView(); -// if (view.getAnimating() || view.getInteracting()) { -// return; -// } -// const zoom = view.getZoom(); - -// // Update the interval between render updates -// if (this.currentZoom !== zoom) { -// this.onZoomEnd(evt); -// } -// this.currentZoom = zoom; - -// this.onMoveEnd(evt); -// }), -// ); -// } -// } - -// /** -// * Destroy the container of the tracker. -// */ -// terminate() { -// super.terminate(); -// this.container = null; -// } - -// /** -// * Detect in the canvas if there is data to query at a specific coordinate. -// * @param {ol/coordinate~Coordinate} coordinate The coordinate to test -// * @returns -// */ -// hasFeatureInfoAtCoordinate(coordinate) { -// if (this.map && this.tracker && this.tracker.canvas) { -// const context = this.tracker.canvas.getContext('2d'); -// const pixel = this.map.getPixelFromCoordinate(coordinate); -// return !!context.getImageData( -// pixel[0] * this.pixelRatio, -// pixel[1] * this.pixelRatio, -// 1, -// 1, -// ).data[3]; -// } -// return false; -// } - -// /** -// * Render the trajectories using current map's size, resolution and rotation. -// * @param {boolean} noInterpolate if true, renders the vehicles without interpolating theirs positions. -// * @overrides -// */ -// renderTrajectories(noInterpolate) { -// const view = this.map.getView(); -// super.renderTrajectories( -// { -// size: this.map.getSize(), -// center: this.map.getView().getCenter(), -// extent: view.calculateExtent(), -// resolution: view.getResolution(), -// rotation: view.getRotation(), -// zoom: view.getZoom(), -// pixelRatio: this.pixelRatio, -// }, -// noInterpolate, -// ); -// } - -// /** -// * Launch renderTrajectories. it avoids duplicating code in renderTrajectories methhod. -// * @private -// * @override -// */ -// renderTrajectoriesInternal(viewState, noInterpolate) { -// let isRendered = false; - -// const blockRendering = -// this.map.getView().getAnimating() || this.map.getView().getInteracting(); - -// if (!blockRendering && this.worker && this.mainThreadFrameState) { -// const frameState = { ...this.mainThreadFrameState }; -// delete frameState.layerStatesArray; -// delete frameState.viewState.projection; -// this.worker.postMessage({ -// action: 'render', -// trajectories: this.trajectories, -// frameState: JSON.parse(stringify(frameState)), -// viewState, -// options: { -// noInterpolate, -// iconScale: this.iconScale, -// hoverVehicleId: this.hoverVehicleId, -// selectedVehicleId: this.selectedVehicleId, -// delayDisplay: this.delayDisplay, -// delayOutlineColor: this.delayOutlineColor, -// useDelayStyle: this.useDelayStyle, -// }, -// }); -// } else if (!this.worker) { -// // Don't render the map when the map is animating or interacting. -// isRendered = blockRendering -// ? false -// : super.renderTrajectoriesInternal(viewState, noInterpolate); - -// // We update the current render state. -// if (isRendered) { -// this.renderedViewState = { ...viewState }; - -// if (this.transformContainer) { -// this.transformContainer.style.transform = ''; -// } -// } -// } -// } - -// /** -// * Return the delay in ms before the next rendering. -// */ -// getRefreshTimeInMs() { -// return super.getRefreshTimeInMs(this.map.getView().getZoom()); -// } - -// /** -// * Returns the vehicle which are at the given coordinates. -// * Returns null when no vehicle is located at the given coordinates. -// * @param {ol/coordinate~Coordinate} coordinate -// * @param {number} nb Number of vehicles to return; -// * @return {Array
                            } Vehicle feature. -// * @override -// */ -// getVehiclesAtCoordinate(coordinate, nb) { -// const resolution = this.map.getView().getResolution(); -// return super.getVehiclesAtCoordinate(coordinate, resolution, nb); -// } - -// getFeatureInfoAtCoordinate(coordinate, options = {}) { -// if (!this.hasFeatureInfoAtCoordinate(coordinate)) { -// return Promise.resolve({ features: [], layer: this, coordinate }); -// } -// const resolution = this.map.getView().getResolution(); -// return super.getFeatureInfoAtCoordinate(coordinate, { -// resolution, -// ...options, -// }); -// } - -// /** -// * Function called on moveend event. -// * To be defined in inherited classes -// * -// * @param {ol/MapEvent~MapEvent} evt Moveend event. -// * @private -// */ -// // eslint-disable-next-line no-unused-vars,class-methods-use-this -// onMoveEnd(evt) {} - -// /** -// * Function called on moveend event only when the zoom has changed. -// * -// * @param {ol/MapEvent~MapEvent} evt Moveend event. -// * @private -// * @override -// */ -// // eslint-disable-next-line no-unused-vars -// onZoomEnd(evt) { -// super.onZoomEnd(evt); -// } - -// /** -// * Update the cursor style when hovering a vehicle. -// * -// * @private -// * @override -// */ -// onFeatureHover(features, layer, coordinate) { -// super.onFeatureHover(features, layer, coordinate); -// this.map.getTargetElement().style.cursor = features.length -// ? 'pointer' -// : 'auto'; -// } - -// /** -// * Display the complete trajectory of the vehicle. -// * -// * @private -// * @override -// */ -// onFeatureClick(features, layer, coordinate) { -// super.onFeatureClick(features, layer, coordinate); -// if (!features.length && this.vectorLayer) { -// this.vectorLayer.getSource().clear(); -// } -// } - -// /** -// * Create a copy of the TrackerLayer. -// * @param {Object} newOptions Options to override -// * @return {TrackerLayer} A TrackerLayer -// */ -// clone(newOptions) { -// return new TrackerLayer({ ...this.options, ...newOptions }); -// } -// } - -// export default TrackerLayer; From 8ec09453ed2d5e1c57397e795608035531cd9878 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 17 Nov 2022 11:05:43 +0100 Subject: [PATCH 106/109] chore: make map 's size bigger --- doc/pages/example/[example].js | 1 + doc/src/components/CodeSandboxButton.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/pages/example/[example].js b/doc/pages/example/[example].js index 78b01b2f..b3764fbd 100644 --- a/doc/pages/example/[example].js +++ b/doc/pages/example/[example].js @@ -10,6 +10,7 @@ const useStyles = makeStyles({ paddingBottom: 115, margin: 'auto', marginTop: 30, + maxWidth: '100% !important', }, }); diff --git a/doc/src/components/CodeSandboxButton.js b/doc/src/components/CodeSandboxButton.js index 22f457c7..d5357f0f 100644 --- a/doc/src/components/CodeSandboxButton.js +++ b/doc/src/components/CodeSandboxButton.js @@ -41,7 +41,7 @@ function CodeSandboxButton({ html, js, extraFiles, ...props }) { 'mapbox-gl': '1', 'maplibre-gl': '2', 'mobility-toolbox-js': 'latest@beta', - ol: '6.3.1', + ol: '6', }, devDependencies: { '@babel/core': '7.2.0', From 74a557450d299521e93163be250fa970a014967c Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Thu, 17 Nov 2022 11:33:32 +0100 Subject: [PATCH 107/109] chore: fix lint --- src/common/tracker.worker.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/tracker.worker.ts b/src/common/tracker.worker.ts index 69f25620..0f90163e 100644 --- a/src/common/tracker.worker.ts +++ b/src/common/tracker.worker.ts @@ -4,9 +4,8 @@ /// import GeoJSON from 'ol/format/GeoJSON'; -import type { FrameState } from 'ol/PluggableMap'; import type { RealtimeStyleOptions, RealtimeTrajectories } from '../types'; -import { realtimeSimpleStyle, realtimeDefaultStyle } from './styles'; +import { realtimeDefaultStyle } from './styles'; import { renderTrajectories, realtimeConfig } from './utils'; const debug = false; From 77633b6f887604ab2bf5fc48429838850d2b92ec Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Tue, 29 Nov 2022 11:46:20 +0100 Subject: [PATCH 108/109] chore: add exemple --- .../static/examples/ol-tracker-worker.html | 1 + .../static/examples/ol-tracker-worker.js | 46 +++++++++++++++++++ .../static/examples/ol-tracker-worker.md | 1 + 3 files changed, 48 insertions(+) create mode 100644 doc/public/static/examples/ol-tracker-worker.html create mode 100644 doc/public/static/examples/ol-tracker-worker.js create mode 100644 doc/public/static/examples/ol-tracker-worker.md diff --git a/doc/public/static/examples/ol-tracker-worker.html b/doc/public/static/examples/ol-tracker-worker.html new file mode 100644 index 00000000..9aba5a6d --- /dev/null +++ b/doc/public/static/examples/ol-tracker-worker.html @@ -0,0 +1 @@ +
                            diff --git a/doc/public/static/examples/ol-tracker-worker.js b/doc/public/static/examples/ol-tracker-worker.js new file mode 100644 index 00000000..6411c107 --- /dev/null +++ b/doc/public/static/examples/ol-tracker-worker.js @@ -0,0 +1,46 @@ +import { Map, View } from 'ol'; +import { + RealtimeLayer, + MaplibreLayer, + CopyrightControl, +} from 'mobility-toolbox-js/ol'; +import 'ol/ol.css'; + +export default () => { + const map = new Map({ + target: 'map', + view: new View({ + center: [831634, 5933959], + zoom: 13, + minZoom: 5, + }), + controls: [], + }); + + const control = new CopyrightControl(); + control.attachToMap(map); + + const layer = new MaplibreLayer({ + url: 'https://maps.geops.io/styles/travic_v2/style.json', + apiKey: window.apiKey, + }); + layer.attachToMap(map); + + const tracker = new RealtimeLayer({ + url: 'wss://api.geops.io/tracker-ws/v1/', + apiKey: window.apiKey, + allowRenderWhenAnimating: + new URL(window.location.href)?.searchParams?.get( + 'allowRenderWhenAnimating', + ) === 'true', + useWorker: true, + }); + tracker.attachToMap(map); + + tracker.onClick(([feature]) => { + if (feature) { + // eslint-disable-next-line no-console + console.log(feature.getProperties()); + } + }); +}; diff --git a/doc/public/static/examples/ol-tracker-worker.md b/doc/public/static/examples/ol-tracker-worker.md new file mode 100644 index 00000000..26a3f627 --- /dev/null +++ b/doc/public/static/examples/ol-tracker-worker.md @@ -0,0 +1 @@ +Follow [this link](https://developer.geops.io/) to get information about the API and how to get an API key. From 010b2ef03236a29ac228e4fdf2f72bbef953e5e5 Mon Sep 17 00:00:00 2001 From: Olivier Terral Date: Wed, 30 Nov 2022 09:34:55 +0100 Subject: [PATCH 109/109] chore: add options to use worker --- doc/src/examples.js | 11 ++++++++++- src/ol/layers/RealtimeLayer.ts | 17 ++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/doc/src/examples.js b/doc/src/examples.js index bf441717..922fac63 100644 --- a/doc/src/examples.js +++ b/doc/src/examples.js @@ -1,5 +1,6 @@ // We load statically the readme to have the content when the page use server side rendering. import olTrackerReadme from '../public/static/examples/ol-tracker.md'; +import olTrackerWorkerReadme from '../public/static/examples/ol-tracker-worker.md'; import mbTrackerReadme from '../public/static/examples/mb-tracker.md'; import olRoutingReadme from '../public/static/examples/ol-routing.md'; import olStopFinderReadme from '../public/static/examples/ol-stop-finder.md'; @@ -22,7 +23,15 @@ export default [ name: 'Live tracker with OpenLayers', key: 'ol-tracker', description: - 'Use the [geOps Realtime API](https://developer.geops.io/apis/realtime/) to show moving trains in a Mapbox map.', + 'Use the [geOps Realtime API](https://developer.geops.io/apis/realtime/) to show moving trains in a Openlayers map.', + img: '/static/img/live_tracker_ol.jpg', + readme: olTrackerReadme, + }, + { + name: 'Live tracker with OpenLayers using web worker', + key: 'ol-tracker-worker', + description: + 'Use the [geOps Realtime API](https://developer.geops.io/apis/realtime/) to show moving trains in a Openlayers map.', img: '/static/img/live_tracker_ol.jpg', readme: olTrackerReadme, }, diff --git a/src/ol/layers/RealtimeLayer.ts b/src/ol/layers/RealtimeLayer.ts index 3370c72b..faf63e68 100644 --- a/src/ol/layers/RealtimeLayer.ts +++ b/src/ol/layers/RealtimeLayer.ts @@ -32,6 +32,7 @@ export type OlRealtimeLayerOptions = RealtimeLayerMixinOptions & { resolution: number, options: any, ) => void; + useWorker?: boolean; allowRenderWhenAnimating?: boolean; }; @@ -63,9 +64,6 @@ const updateContainerTransform = ( } }; -const wworker = new Worker( - new URL('../../common/tracker.worker.js', import.meta.url), -); /** * Responsible for loading and display data from a Realtime service. * @@ -101,11 +99,16 @@ class RealtimeLayer extends mixin(Layer) { ...options, }); - // Worker that render trajectories. - this.worker = wworker; + if (options.useWorker) { + const wworker = new Worker( + new URL('../../common/tracker.worker.js', import.meta.url), + ); + // Worker that render trajectories. + this.worker = wworker; - // Worker messaging and actions - this.worker.onmessage = this.onWorkerMessage.bind(this); + // Worker messaging and actions + this.worker.onmessage = this.onWorkerMessage.bind(this); + } this.allowRenderWhenAnimating = !!options.allowRenderWhenAnimating;