From fe9f4b4a486d0f3df31aa2ad6caf397f6000b522 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 11:40:52 +0100 Subject: [PATCH 1/6] WIP: POC of using Leaflet.IconLayers as tilelayers switcher --- package-lock.json | 43 +++++++++++++++++++++++----- package.json | 1 + scripts/vendorsjs.sh | 1 + umap/static/umap/js/umap.controls.js | 17 ++++++++++- umap/static/umap/js/umap.js | 30 +++++++++---------- umap/static/umap/map.css | 22 ++++++++++++++ umap/static/umap/test/index.html | 2 ++ umap/templates/umap/css.html | 2 ++ umap/templates/umap/js.html | 1 + 9 files changed, 96 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 557d3962..47608090 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,11 @@ "leaflet-contextmenu": "^1.4.0", "leaflet-editable": "^1.2.0", "leaflet-editinosm": "0.2.3", - "leaflet-formbuilder": "0.2.7", + "leaflet-formbuilder": "0.2.9", "leaflet-fullscreen": "1.0.2", "leaflet-hash": "0.2.1", "leaflet-i18n": "0.3.3", + "leaflet-iconlayers": "^0.2.0", "leaflet-loading": "0.1.24", "leaflet-measurable": "0.1.0", "leaflet-minimap": "^3.6.1", @@ -1384,9 +1385,9 @@ "integrity": "sha1-8HFmTEpSe3b3uPm87HRLJIiVwHE=" }, "node_modules/leaflet-formbuilder": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/leaflet-formbuilder/-/leaflet-formbuilder-0.2.7.tgz", - "integrity": "sha512-5/QXEPmlSPNzl5r8rNlhcQOfI2Bx9vo/FBaBCV7o37MmZZ2jyA4aRu+6j91CnyRmKXfU5f/42E0yJva/Dwnqcw==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/leaflet-formbuilder/-/leaflet-formbuilder-0.2.9.tgz", + "integrity": "sha512-6akc6pqVMDqoKxTfXvurK4tfDMqnAYTxqxS8GVkOXipEXumsP9qScLFYEzhuZcMeOJ+AeKZrq8ZCsNti1syusg==" }, "node_modules/leaflet-fullscreen": { "version": "1.0.2", @@ -1406,6 +1407,19 @@ "resolved": "https://registry.npmjs.org/leaflet-i18n/-/leaflet-i18n-0.3.3.tgz", "integrity": "sha512-bl1HkR/8ZbQ9/S2x736rBQL/40P+5zSItMAW5lOpuZSrM5PK0ezsux8znug8JJQtE+2QkBBsWIngfVlGERN0AA==" }, + "node_modules/leaflet-iconlayers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/leaflet-iconlayers/-/leaflet-iconlayers-0.2.0.tgz", + "integrity": "sha512-iSa5v+Lrovt2wLY3wQnzudatb+l7SEMUOvrmDrky2TkhUjJscrJKj8doP16xG94E2rHQaQmqxUWKGv6JNUfIOQ==", + "dependencies": { + "leaflet": "^0.7.3" + } + }, + "node_modules/leaflet-iconlayers/node_modules/leaflet": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-0.7.7.tgz", + "integrity": "sha512-H1lR7J5VxhvQJQHlW2UywtxO63zilLrnwVsDvjKeyfntffj63Ml94gCk9YPYWBkiQgxisdiA8aJ30Zoou4VhEA==" + }, "node_modules/leaflet-loading": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/leaflet-loading/-/leaflet-loading-0.1.24.tgz", @@ -3584,9 +3598,9 @@ "integrity": "sha1-8HFmTEpSe3b3uPm87HRLJIiVwHE=" }, "leaflet-formbuilder": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/leaflet-formbuilder/-/leaflet-formbuilder-0.2.7.tgz", - "integrity": "sha512-5/QXEPmlSPNzl5r8rNlhcQOfI2Bx9vo/FBaBCV7o37MmZZ2jyA4aRu+6j91CnyRmKXfU5f/42E0yJva/Dwnqcw==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/leaflet-formbuilder/-/leaflet-formbuilder-0.2.9.tgz", + "integrity": "sha512-6akc6pqVMDqoKxTfXvurK4tfDMqnAYTxqxS8GVkOXipEXumsP9qScLFYEzhuZcMeOJ+AeKZrq8ZCsNti1syusg==" }, "leaflet-fullscreen": { "version": "1.0.2", @@ -3603,6 +3617,21 @@ "resolved": "https://registry.npmjs.org/leaflet-i18n/-/leaflet-i18n-0.3.3.tgz", "integrity": "sha512-bl1HkR/8ZbQ9/S2x736rBQL/40P+5zSItMAW5lOpuZSrM5PK0ezsux8znug8JJQtE+2QkBBsWIngfVlGERN0AA==" }, + "leaflet-iconlayers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/leaflet-iconlayers/-/leaflet-iconlayers-0.2.0.tgz", + "integrity": "sha512-iSa5v+Lrovt2wLY3wQnzudatb+l7SEMUOvrmDrky2TkhUjJscrJKj8doP16xG94E2rHQaQmqxUWKGv6JNUfIOQ==", + "requires": { + "leaflet": "^0.7.3" + }, + "dependencies": { + "leaflet": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-0.7.7.tgz", + "integrity": "sha512-H1lR7J5VxhvQJQHlW2UywtxO63zilLrnwVsDvjKeyfntffj63Ml94gCk9YPYWBkiQgxisdiA8aJ30Zoou4VhEA==" + } + } + }, "leaflet-loading": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/leaflet-loading/-/leaflet-loading-0.1.24.tgz", diff --git a/package.json b/package.json index 14288c65..bad0c918 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "leaflet-fullscreen": "1.0.2", "leaflet-hash": "0.2.1", "leaflet-i18n": "0.3.3", + "leaflet-iconlayers": "^0.2.0", "leaflet-loading": "0.1.24", "leaflet-measurable": "0.1.0", "leaflet-minimap": "^3.6.1", diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh index 793fbeb5..07c33cd7 100755 --- a/scripts/vendorsjs.sh +++ b/scripts/vendorsjs.sh @@ -28,5 +28,6 @@ mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.l mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.js umap/static/umap/vendors/dompurify/ mkdir -p umap/static/umap/vendors/colorbrewer/ && cp node_modules/colorbrewer/index.js umap/static/umap/vendors/colorbrewer/colorbrewer.js mkdir -p umap/static/umap/vendors/simple-statistics/ && cp node_modules/simple-statistics/dist/simple-statistics.min.js umap/static/umap/vendors/simple-statistics/ +mkdir -p umap/static/umap/vendors/iconlayers/ && cp node_modules/leaflet-iconlayers/dist/* umap/static/umap/vendors/iconlayers/ echo 'Done!' diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index c2bd29df..f9099fa6 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1178,7 +1178,22 @@ L.U.Map.include({ }, }) -L.U.TileLayerControl = L.Control.extend({ +L.U.TileLayerControl = L.Control.IconLayers.extend({ + initialize: function (map, options) { + const layers = [] + for (const layer of map.tilelayers) { + layers.push({ + title: layer.options.name, + layer: layer, + icon: L.Util.template(layer.options.url_template, map.demoTileInfos), + }) + } + L.Control.IconLayers.prototype.initialize.call(this, layers, {position: 'topleft'}) + } +}) + + +L.U.TileLayerChooser = L.Control.extend({ options: { position: 'topleft', }, diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index f4149bd2..da3b7a0d 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -60,8 +60,8 @@ L.U.Map.include({ 'measure', 'editinosm', 'datalayers', - 'tilelayers', 'star', + 'tilelayers', ], initialize: function (el, geojson) { @@ -326,7 +326,7 @@ L.U.Map.include({ }) this._controls.search = new L.U.SearchControl() this._controls.embed = new L.Control.Embed(this, this.options.embedOptions) - this._controls.tilelayers = new L.U.TileLayerControl(this) + this._controls.tilelayersChooser = new L.U.TileLayerChooser(this) this._controls.star = new L.U.StarControl(this) this._controls.editinosm = new L.Control.EditInOSM({ position: 'topleft', @@ -345,6 +345,8 @@ L.U.Map.include({ this.browser = new L.U.Browser(this) this.importer = new L.U.Importer(this) this.drop = new L.U.DropControl(this) + this._controls.tilelayers = new L.U.TileLayerControl(this) + this.renderControls() }, @@ -581,17 +583,15 @@ L.U.Map.include({ initTileLayers: function () { this.tilelayers = [] - for (const i in this.options.tilelayers) { - if (this.options.tilelayers.hasOwnProperty(i)) { - this.tilelayers.push(this.createTileLayer(this.options.tilelayers[i])) - if ( - this.options.tilelayer && - this.options.tilelayer.url_template === - this.options.tilelayers[i].url_template - ) { - // Keep control over the displayed attribution for non custom tilelayers - this.options.tilelayer.attribution = this.options.tilelayers[i].attribution - } + for (const props of this.options.tilelayers) { + let layer = this.createTileLayer(props) + this.tilelayers.push(layer) + if ( + this.options.tilelayer && + this.options.tilelayer.url_template === props.url_template + ) { + // Keep control over the displayed attribution for non custom tilelayers + this.options.tilelayer.attribution = props.attribution } } if ( @@ -816,8 +816,8 @@ L.U.Map.include({ self.options.tilelayer = tilelayer.toJSON() self.isDirty = true } - if (this._controls.tilelayers) - this._controls.tilelayers.openSwitcher({ callback: callback, className: 'dark' }) + if (this._controls.tilelayersChooser) + this._controls.tilelayersChooser.openSwitcher({ callback: callback, className: 'dark' }) }, manageDatalayers: function () { diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 1cdf7412..b3a6141b 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -161,6 +161,28 @@ background-image: url('./img/16.svg'); background-position: 0px 0px; } +.leaflet-iconLayers-layer { + width: 38px; + height: 38px; + box-shadow: none; + border: 1px solid #bbb; + border-radius: 4px; +} +.leaflet-iconLayers-layerTitleContainer { + display: none; + left: 0; +} +.leaflet-iconLayers-layerTitle { + text-align: center; + display: inline-block; +} +.leaflet-iconLayers:hover .leaflet-iconLayers-layer { + width: 80px; + height: 80px; +} +.leaflet-iconLayers:hover .leaflet-iconLayers-layerTitleContainer { + display: initial; +} diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index 4f11268b..bc371d16 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -21,6 +21,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/umap/templates/umap/css.html b/umap/templates/umap/css.html index e6bd3627..120ff967 100644 --- a/umap/templates/umap/css.html +++ b/umap/templates/umap/css.html @@ -18,6 +18,8 @@ href="{{ STATIC_URL }}umap/vendors/fullscreen/leaflet.fullscreen.css" /> + diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index 6a5b29af..131c1a02 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -20,6 +20,7 @@ + From afd57d6806bd5f50b0fe361f3540a98b70bc57dd Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 16:16:53 +0100 Subject: [PATCH 2/6] Control the tilelayer change, instead of letting iconLayers do it We want to control min/max zoom and such. --- umap/static/umap/js/umap.controls.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index f9099fa6..b97fa58b 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1178,21 +1178,26 @@ L.U.Map.include({ }, }) +/* Used in view mode to define the current tilelayer */ L.U.TileLayerControl = L.Control.IconLayers.extend({ initialize: function (map, options) { const layers = [] - for (const layer of map.tilelayers) { + map.eachTileLayer((layer) => { layers.push({ title: layer.options.name, layer: layer, icon: L.Util.template(layer.options.url_template, map.demoTileInfos), }) - } - L.Control.IconLayers.prototype.initialize.call(this, layers, {position: 'topleft'}) - } + }) + L.Control.IconLayers.prototype.initialize.call(this, layers.slice(0, 10), { + position: 'topleft', + manageLayers: false + }) + this.on('activelayerchange', (e) => map.selectTileLayer(e.layer)) + }, }) - +/* Used in edit mode to define the default tilelayer */ L.U.TileLayerChooser = L.Control.extend({ options: { position: 'topleft', From 34d3beef25ef9cbfee7083fb09e7418e3767eab9 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 16:19:15 +0100 Subject: [PATCH 3/6] Make sure custom tilelayer is displayed first, if any --- umap/static/umap/js/umap.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index da3b7a0d..ca123317 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -648,19 +648,14 @@ L.U.Map.include({ eachTileLayer: function (method, context) { const urls = [] - for (const i in this.tilelayers) { - if (this.tilelayers.hasOwnProperty(i)) { - method.call(context, this.tilelayers[i]) - urls.push(this.tilelayers[i]._url) - } - } - if ( - this.customTilelayer && - Array.prototype.indexOf && - urls.indexOf(this.customTilelayer._url) === -1 - ) { - method.call(context || this, this.customTilelayer) + const call = (layer) => { + const url = layer.options.url_template + if (urls.indexOf(url) !== -1) return + method.call(context, layer) + urls.push(url) } + if (this.customTilelayer) call(this.customTilelayer) + this.tilelayers.forEach(call) }, setOverlay: function () { From b6b6139c79a493545370df6541a2c99b19a0f31c Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 16:19:31 +0100 Subject: [PATCH 4/6] Hide overflow from tilelayer title for now --- umap/static/umap/map.css | 1 + 1 file changed, 1 insertion(+) diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index b3a6141b..4c14bfc6 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -171,6 +171,7 @@ .leaflet-iconLayers-layerTitleContainer { display: none; left: 0; + overflow: hidden; } .leaflet-iconLayers-layerTitle { text-align: center; From 49c17d6fd1ed9e4190009e0215070fb69518c825 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 17:05:00 +0100 Subject: [PATCH 5/6] Simplify eachTilelayer method Having a custom layer also in the predefined layers list is a edge case we can deal with. --- umap/static/umap/js/umap.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index ca123317..159328ee 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -646,16 +646,9 @@ L.U.Map.include({ this.setOverlay() }, - eachTileLayer: function (method, context) { - const urls = [] - const call = (layer) => { - const url = layer.options.url_template - if (urls.indexOf(url) !== -1) return - method.call(context, layer) - urls.push(url) - } - if (this.customTilelayer) call(this.customTilelayer) - this.tilelayers.forEach(call) + eachTileLayer: function (callback, context) { + if (this.customTilelayer) callback.call(context, this.customTilelayer) + this.tilelayers.forEach((layer) => callback.call(context, layer)) }, setOverlay: function () { From 3ffa29802e322149a7ed609cc0d1ecf144495979 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Fri, 15 Dec 2023 17:11:49 +0100 Subject: [PATCH 6/6] Use a variable to make explicit max number of shown layers --- umap/static/umap/js/umap.controls.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index b97fa58b..67baa3dc 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1189,7 +1189,8 @@ L.U.TileLayerControl = L.Control.IconLayers.extend({ icon: L.Util.template(layer.options.url_template, map.demoTileInfos), }) }) - L.Control.IconLayers.prototype.initialize.call(this, layers.slice(0, 10), { + const maxShown = 10 + L.Control.IconLayers.prototype.initialize.call(this, layers.slice(0, maxShown), { position: 'topleft', manageLayers: false })