From bf66036b7b5586b31a3a8d9fa9611529f65b4f8b Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 31 Aug 2023 16:36:34 +0200 Subject: [PATCH 1/2] Make sure we load all data before downloading it fix #980 --- umap/static/umap/js/umap.controls.js | 22 +++++++++++++++---- umap/static/umap/js/umap.js | 32 +++++++++++++++++----------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index b5d729d9..42a6dd67 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -986,9 +986,10 @@ L.U.Map.include({ const status = this.permissions.getShareStatusDisplay() name.textContent = this.getDisplayName() // status is not set until map is saved once - if (status) share_status.textContent = L._('Visibility: {status}', { - status: status, - }) + if (status) + share_status.textContent = L._('Visibility: {status}', { + status: status, + }) } update() this.once('saved', L.bind(update, this)) @@ -1129,10 +1130,23 @@ L.U.Map.include({ toggleCaveat() const download = L.DomUtil.create('a', 'button', container) download.textContent = L._('Download data') - L.DomEvent.on(download, 'click', () => this.download(typeInput.value), this) + L.DomEvent.on( + download, + 'click', + () => { + if (typeInput.value === 'umap') this.fullDownload() + else this.download(typeInput.value) + } + ) this.ui.openPanel({ data: { html: container } }) }, + fullDownload: function () { + // Make sure all data is loaded before downloading + this.once('dataloaded', () => this.download()) + this.loadDatalayers(true) // Force load + }, + download: function (mode) { const type = this.EXPORT_TYPES[mode || 'umap'] const content = type.formatter(this) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 742e3aab..ec412835 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -379,33 +379,41 @@ L.U.Map.include({ }, initDatalayers: function () { - let toload = (dataToload = seen = this.options.datalayers.length) - const self = this + for (let j = 0; j < this.options.datalayers.length; j++) { + this.createDataLayer(this.options.datalayers[j]) + } + this.loadDatalayers() + }, + + loadDatalayers: function (force) { + force = force || L.Util.queryString('download') // In case we are in download mode, let's go strait to loading all data + let toload = (dataToload = total = this.datalayers_index.length) let datalayer const loaded = () => { - self.datalayersLoaded = true - self.fire('datalayersloaded') + this.datalayersLoaded = true + this.fire('datalayersloaded') } const decrementToLoad = () => { toload-- if (toload === 0) loaded() } const dataLoaded = () => { - self.dataLoaded = true - self.fire('dataloaded') + this.dataLoaded = true + this.fire('dataloaded') } const decrementDataToLoad = () => { dataToload-- if (dataToload === 0) dataLoaded() } - for (let j = 0; j < this.options.datalayers.length; j++) { - datalayer = this.createDataLayer(this.options.datalayers[j]) - if (datalayer.displayedOnLoad()) datalayer.onceLoaded(decrementToLoad) + this.eachDataLayer(function (datalayer) { + if (force && !datalayer.hasDataLoaded()) datalayer.show() + if (datalayer.displayedOnLoad() || force) datalayer.onceLoaded(decrementToLoad) else decrementToLoad() - if (datalayer.displayedOnLoad()) datalayer.onceDataLoaded(decrementDataToLoad) + if (datalayer.displayedOnLoad() || force) + datalayer.onceDataLoaded(decrementDataToLoad) else decrementDataToLoad() - } - if (seen === 0) { + }) + if (total === 0) { // no datalayer loaded() dataLoaded() From 562f516272c8b4a469aaafe95cf20128c022edec Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 31 Aug 2023 17:48:59 +0200 Subject: [PATCH 2/2] Minimap exports tests --- umap/static/umap/js/umap.controls.js | 12 +- umap/static/umap/js/umap.js | 4 +- umap/static/umap/test/Map.Export.js | 230 +++++++++++++++++++++++++++ umap/static/umap/test/Polyline.js | 2 +- umap/static/umap/test/index.html | 3 + 5 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 umap/static/umap/test/Map.Export.js diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 42a6dd67..e0c1f051 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -1147,15 +1147,21 @@ L.U.Map.include({ this.loadDatalayers(true) // Force load }, - download: function (mode) { + format: function (mode) { const type = this.EXPORT_TYPES[mode || 'umap'] const content = type.formatter(this) let name = this.options.name || 'data' name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + const filename = name + type.ext + return {content, filetype: type.filetype, filename} + }, + + download: function (mode) { + const {content, filetype, filename} = this.format(mode) + const blob = new Blob([content], { type: filetype }) window.URL = window.URL || window.webkitURL - const blob = new Blob([content], { type: type.filetype }) const el = document.createElement('a') - el.download = name + type.ext + el.download = filename el.href = window.URL.createObjectURL(blob) el.style.display = 'none' document.body.appendChild(el) diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index ec412835..7848add6 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -1136,9 +1136,11 @@ L.U.Map.include({ }, serialize: function () { + // Do not use local path during unit tests + const uri = window.location.protocol === 'file:' ? null : window.location.href const umapfile = { type: 'umap', - uri: window.location.href, + uri: uri, properties: this.exportOptions(), geometry: this.geometry(), layers: [], diff --git a/umap/static/umap/test/Map.Export.js b/umap/static/umap/test/Map.Export.js new file mode 100644 index 00000000..1c17a803 --- /dev/null +++ b/umap/static/umap/test/Map.Export.js @@ -0,0 +1,230 @@ +describe('L.U.Map.Export', function () { + before(function () { + this.server = sinon.fakeServer.create() + this.server.respondWith( + /\/datalayer\/62\/\?.*/, + JSON.stringify(RESPONSES.datalayer62_GET) + ) + this.options = { + umap_id: 99, + } + this.map = initMap({ umap_id: 99 }) + this.server.respond() + this.datalayer = this.map.getDataLayerByUmapId(62) + }) + after(function () { + this.server.restore() + clickCancel() + resetMap() + }) + + describe('#formatters()', function () { + it('should export to geojson', function () { + const { content, filetype, filename } = this.map.format('geojson') + assert.equal(filetype, 'application/json') + assert.equal(filename, 'name_of_the_map.geojson') + assert.deepEqual(JSON.parse(content), { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + name: 'name poly', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [11.25, 53.585984], + [10.151367, 52.975108], + [12.689209, 52.167194], + [14.084473, 53.199452], + [12.634277, 53.618579], + [11.25, 53.585984], + [11.25, 53.585984], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + _umap_options: { + color: 'OliveDrab', + }, + name: 'test', + }, + geometry: { + type: 'Point', + coordinates: [-0.274658, 52.57635], + }, + }, + { + type: 'Feature', + properties: { + _umap_options: { + fill: false, + }, + name: 'test', + }, + geometry: { + type: 'LineString', + coordinates: [ + [-0.571289, 54.476422], + [0.439453, 54.610255], + [1.724854, 53.448807], + [4.163818, 53.988395], + [5.306396, 53.533778], + [6.591797, 53.709714], + [7.042236, 53.350551], + ], + }, + }, + ], + }) + }) + + it('should export to gpx', function () { + const { content, filetype, filename } = this.map.format('gpx') + assert.equal(filetype, 'application/gpx+xml') + assert.equal(filename, 'name_of_the_map.gpx') + const expected = + 'testname=testname polyname=name polytestname=test' + assert.equal(content, expected) + }) + + it('should export to gpx', function () { + const { content, filetype, filename } = this.map.format('kml') + assert.equal(filetype, 'application/vnd.google-earth.kml+xml') + assert.equal(filename, 'name_of_the_map.kml') + const expected = + 'name polyname poly11.25,53.585984 10.151367,52.975108 12.689209,52.167194 14.084473,53.199452 12.634277,53.618579 11.25,53.585984 11.25,53.585984test[object Object]test-0.274658,52.57635test[object Object]test-0.571289,54.476422 0.439453,54.610255 1.724854,53.448807 4.163818,53.988395 5.306396,53.533778 6.591797,53.709714 7.042236,53.350551' + assert.equal(content, expected) + }) + + it('should export to umap', function () { + const { content, filetype, filename } = this.map.format('umap') + assert.equal(filetype, 'application/json') + assert.equal(filename, 'name_of_the_map.umap') + const expected = { + type: 'umap', + uri: null, + properties: { + easing: false, + embedControl: true, + fullscreenControl: true, + searchControl: true, + datalayersControl: true, + zoomControl: true, + permanentCreditBackground: true, + slideshow: {}, + captionMenus: true, + captionBar: false, + limitBounds: {}, + overlay: null, + tilelayer: { + attribution: 'HOT and friends', + name: 'HOT OSM-fr server', + url_template: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', + rank: 99, + minZoom: 0, + maxZoom: 20, + id: 2, + }, + licence: '', + description: 'The description of the map', + name: 'name of the map', + displayPopupFooter: false, + miniMap: false, + moreControl: true, + scaleControl: true, + scrollWheelZoom: true, + zoom: 6, + }, + geometry: { + type: 'Point', + coordinates: [5.0592041015625, 52.05924589011585], + }, + layers: [ + { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + name: 'name poly', + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [11.25, 53.585984], + [10.151367, 52.975108], + [12.689209, 52.167194], + [14.084473, 53.199452], + [12.634277, 53.618579], + [11.25, 53.585984], + [11.25, 53.585984], + ], + ], + }, + }, + { + type: 'Feature', + properties: { + _umap_options: { + color: 'OliveDrab', + }, + name: 'test', + }, + geometry: { + type: 'Point', + coordinates: [-0.274658, 52.57635], + }, + }, + { + type: 'Feature', + properties: { + _umap_options: { + fill: false, + }, + name: 'test', + }, + geometry: { + type: 'LineString', + coordinates: [ + [-0.571289, 54.476422], + [0.439453, 54.610255], + [1.724854, 53.448807], + [4.163818, 53.988395], + [5.306396, 53.533778], + [6.591797, 53.709714], + [7.042236, 53.350551], + ], + }, + }, + ], + _umap_options: { + displayOnLoad: true, + browsable: true, + iconClass: 'Default', + name: 'Elephants', + id: 62, + pictogram_url: null, + opacity: null, + weight: null, + fillColor: '', + color: '', + stroke: true, + smoothFactor: null, + dashArray: '', + fillOpacity: null, + fill: true, + }, + }, + ], + } + assert.deepEqual(JSON.parse(content), expected) + }) + }) +}) diff --git a/umap/static/umap/test/Polyline.js b/umap/static/umap/test/Polyline.js index 8e8d4607..dfc1393c 100644 --- a/umap/static/umap/test/Polyline.js +++ b/umap/static/umap/test/Polyline.js @@ -1,4 +1,4 @@ -describe('L.Utorage.Polyline', function () { +describe('L.U.Polyline', function () { var p2ll, map before(function () { diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index b8ed9b10..cf4928f2 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -23,6 +23,8 @@ + + @@ -68,6 +70,7 @@ +