From 824aab006db2d76d6670ba7846b458ce3349a9db Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Thu, 23 May 2024 14:01:30 +0200 Subject: [PATCH] PB-505: Added error when external WMTS layer don't support WGS84 For this I needed to be able to add/remove errors per layer as this error is only for 3D. --- src/api/layers/AbstractLayer.class.js | 31 ++++++- src/modules/i18n/locales/de.json | 3 +- src/modules/i18n/locales/en.json | 3 +- src/modules/i18n/locales/fr.json | 3 +- src/modules/i18n/locales/it.json | 3 +- src/modules/i18n/locales/rm.json | 3 +- .../map/components/cesium/CesiumWMTSLayer.vue | 52 +++++++++--- .../cesium/utils/addImageryLayer-mixins.js | 4 +- .../activeLayers/MenuActiveLayersListItem.vue | 2 +- src/store/modules/layers.store.js | 81 ++++++++++++++++--- src/store/plugins/external-layers.plugin.js | 2 +- .../load-geojson-style-and-data.plugin.js | 3 +- src/store/plugins/load-gpx-data.plugin.js | 2 +- src/store/plugins/load-kml-data.plugin.js | 2 +- 14 files changed, 155 insertions(+), 39 deletions(-) diff --git a/src/api/layers/AbstractLayer.class.js b/src/api/layers/AbstractLayer.class.js index 0f2eb8be0..fa4984e96 100644 --- a/src/api/layers/AbstractLayer.class.js +++ b/src/api/layers/AbstractLayer.class.js @@ -1,3 +1,5 @@ +import { cloneDeep } from 'lodash' + import { InvalidLayerDataError } from '@/api/layers/InvalidLayerData.error' /** @@ -121,15 +123,36 @@ export default class AbstractLayer { this.isLoading = isLoading this.hasDescription = hasDescription this.hasLegend = hasLegend - this.errorKey = null + this.errorKeys = new Set() this.hasError = false this.timeConfig = timeConfig this.hasMultipleTimestamps = this.timeConfig?.timeEntries?.length > 1 } + hasErrorKey(key) { + return this.errorKeys.has(key) + } + + getFirstErrorKey() { + return this.errorKeys.values().next().value + } + + addErrorKey(key) { + this.errorKeys.add(key) + this.hasError = true + } + + removeErrorKey(key) { + this.errorKeys.delete(key) + this.hasError = !!this.errorKeys.size + } + + clearErrorKeys() { + this.errorKeys.clear() + this.hasError = false + } + clone() { - let clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this) - clone.attributions = this.attributions.map((attribution) => attribution.clone()) - return clone + return cloneDeep(this) } } diff --git a/src/modules/i18n/locales/de.json b/src/modules/i18n/locales/de.json index db2abe8e4..5407c4f2e 100644 --- a/src/modules/i18n/locales/de.json +++ b/src/modules/i18n/locales/de.json @@ -681,5 +681,6 @@ "3d_vegetation": "Vegetationen", "3d_constructions": "Gebäude und Konstruktionen", "webmapviewer_live_disclaimer": "Grosse Veränderungen stehen bevor, erfahren Sie mehr", - "drawing_too_large": "Deine Zeichnung ist zu gross, entferne einige Details." + "drawing_too_large": "Deine Zeichnung ist zu gross, entferne einige Details.", + "3d_unsupported_projection": "Dieser Datensatz (externe Quelle) kann wegen fehlender Unterstützung der Projektion EPSG:4326 nicht in 3D dargestellt werden" } diff --git a/src/modules/i18n/locales/en.json b/src/modules/i18n/locales/en.json index 0ac92b384..a4a87dedc 100644 --- a/src/modules/i18n/locales/en.json +++ b/src/modules/i18n/locales/en.json @@ -681,5 +681,6 @@ "3d_vegetation": "Vegetations", "3d_constructions": "Buildings and constructions", "webmapviewer_live_disclaimer": "Big changes are coming soon, learn more", - "drawing_too_large": "Your drawing is too large, remove some features" + "drawing_too_large": "Your drawing is too large, remove some features", + "3d_unsupported_projection": "This map provided by external source can't be displayed in 3d because it doesn't support the projection EPSG:4326" } diff --git a/src/modules/i18n/locales/fr.json b/src/modules/i18n/locales/fr.json index 0b30a095e..872980c82 100644 --- a/src/modules/i18n/locales/fr.json +++ b/src/modules/i18n/locales/fr.json @@ -681,5 +681,6 @@ "3d_vegetation": "Végétations", "3d_constructions": "Bâtiments et constructions", "webmapviewer_live_disclaimer": "De grands changements sont à venir, en savoir plus", - "drawing_too_large": "Ton dessin est trop grand, enlève quelques éléments" + "drawing_too_large": "Ton dessin est trop grand, enlève quelques éléments", + "3d_unsupported_projection": "La carte ne peut pas être affichées en 3d parce qu'elle ne supporte pas la projection EPSG:4326" } diff --git a/src/modules/i18n/locales/it.json b/src/modules/i18n/locales/it.json index 4d8dfd5ba..ac03c4b1d 100644 --- a/src/modules/i18n/locales/it.json +++ b/src/modules/i18n/locales/it.json @@ -681,5 +681,6 @@ "3d_vegetation": "Vegetazione", "3d_constructions": "Edifici e costruzioni", "webmapviewer_live_disclaimer": "Grandi cambiamenti in arrivo, per saperne di più", - "drawing_too_large": "Il tuo disegno è troppo grande, rimuovi alcune caratteristiche" + "drawing_too_large": "Il tuo disegno è troppo grande, rimuovi alcune caratteristiche", + "3d_unsupported_projection": "La mappa non può essere visualizzata in 3D perché non supporta la proiezione EPSG:4326" } diff --git a/src/modules/i18n/locales/rm.json b/src/modules/i18n/locales/rm.json index 277691540..6c758982f 100644 --- a/src/modules/i18n/locales/rm.json +++ b/src/modules/i18n/locales/rm.json @@ -679,5 +679,6 @@ "3d_vegetation": "Vegietaziun", "3d_constructions": "Edifizis ed installaziuns", "webmapviewer_live_disclaimer": "Grondas midadas vegnan prest, emprender dapli", - "drawing_too_large": "Tes dissegn è memia grond, allontanescha intgins detagls" + "drawing_too_large": "Tes dissegn è memia grond, allontanescha intgins detagls", + "3d_unsupported_projection": "La carta na po betg vegnir mussada en 3D perquai ch'ella na sustegna betg la projecziun EPSG:4326" } diff --git a/src/modules/map/components/cesium/CesiumWMTSLayer.vue b/src/modules/map/components/cesium/CesiumWMTSLayer.vue index 022a89fd2..e955c6e21 100644 --- a/src/modules/map/components/cesium/CesiumWMTSLayer.vue +++ b/src/modules/map/components/cesium/CesiumWMTSLayer.vue @@ -11,6 +11,8 @@ import { UrlTemplateImageryProvider, WebMapTileServiceImageryProvider, } from 'cesium' +import { isEqual } from 'lodash' +import { mapActions } from 'vuex' import ExternalWMTSLayer, { WMTSEncodingTypes } from '@/api/layers/ExternalWMTSLayer.class' import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' @@ -23,8 +25,12 @@ import log from '@/utils/logging' import addImageryLayerMixins from './utils/addImageryLayer-mixins' +const dispatcher = { dispatcher: 'CesiumWMTSLayer.vue' } + const MAXIMUM_LEVEL_OF_DETAILS = 18 +const threeDErrorKey = '3d_unsupported_projection' + export default { mixins: [addImageryLayerMixins], props: { @@ -72,6 +78,11 @@ export default { log.error( `External layer ${this.wmtsLayerConfig.id} does not support ${this.projection.epsg}` ) + this.addLayerErrorKey({ + layerId: this.wmtsLayerConfig.id, + errorKey: threeDErrorKey, + ...dispatcher, + }) } return set }, @@ -108,17 +119,30 @@ export default { }, }, watch: { - dimensions() { - this.updateLayer() + dimensions(newDimension, oldDimension) { + if (!isEqual(newDimension, oldDimension)) { + log.debug(`layer dimension have been updated`, oldDimension, newDimension) + this.updateLayer() + } }, }, + unmounted() { + if (this.wmtsLayerConfig.hasErrorKey(threeDErrorKey)) { + this.removeLayerErrorKey({ + layerId: this.wmtsLayerConfig.id, + errorKey: threeDErrorKey, + ...dispatcher, + }) + } + }, methods: { + ...mapActions(['addLayerErrorKey', 'removeLayerErrorKey']), createImagery(url) { const options = { show: this.wmtsLayerConfig.visible, alpha: this.opacity, } - if (this.wmtsLayerConfig instanceof ExternalWMTSLayer) { + if (this.wmtsLayerConfig instanceof ExternalWMTSLayer && this.tileMatrixSetId) { return new ImageryLayer( new WebMapTileServiceImageryProvider({ url: @@ -132,17 +156,19 @@ export default { }), options ) + } else if (this.wmtsLayerConfig instanceof GeoAdminWMTSLayer) { + return new ImageryLayer( + new UrlTemplateImageryProvider({ + rectangle: Rectangle.fromDegrees( + ...DEFAULT_PROJECTION.getBoundsAs(WGS84).flatten + ), + maximumLevel: MAXIMUM_LEVEL_OF_DETAILS, + url: url, + }), + options + ) } - return new ImageryLayer( - new UrlTemplateImageryProvider({ - rectangle: Rectangle.fromDegrees( - ...DEFAULT_PROJECTION.getBoundsAs(WGS84).flatten - ), - maximumLevel: MAXIMUM_LEVEL_OF_DETAILS, - url: url, - }), - options - ) + return null }, }, } diff --git a/src/modules/map/components/cesium/utils/addImageryLayer-mixins.js b/src/modules/map/components/cesium/utils/addImageryLayer-mixins.js index c7babdcbb..573971ab8 100644 --- a/src/modules/map/components/cesium/utils/addImageryLayer-mixins.js +++ b/src/modules/map/components/cesium/utils/addImageryLayer-mixins.js @@ -34,7 +34,9 @@ const addImageryLayerMixins = { const index = viewer.scene.imageryLayers.indexOf(this.layer) viewer.scene.imageryLayers.remove(this.layer) this.layer = this.createImagery(this.url) - viewer.scene.imageryLayers.add(this.layer, index) + if (this.layer) { + viewer.scene.imageryLayers.add(this.layer, index) + } }, }, watch: { diff --git a/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue b/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue index f02017ad4..54e696068 100644 --- a/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue +++ b/src/modules/menu/components/activeLayers/MenuActiveLayersListItem.vue @@ -155,7 +155,7 @@ function duplicateLayer() { (layerId) => state.activeLayers.filter((layer) => layer.id === layerId), + /** + * Retrieves layer(s) by ID. + * + * Search in active layer and in preview layer + * + * @param {string} layerId ID of the layer(s) to retrieve + * @returns {[AbstractLayer]} All active layers matching the ID + */ + getLayersById: (state) => (layerId) => { + const layers = state.activeLayers.filter((layer) => layer.id === layerId) + if (state.previewLayer?.id === layerId) { + layers.push(state.previewLayer) + } + return layers + }, + /** * Retrieves active layer by index * @@ -539,24 +555,24 @@ const actions = { }, /** - * Set a layer error translation key. + * Add a layer error translation key. * * NOTE: This set the error key to all layers matching the ID. * * @param {string} layerId Layer ID of the layer to set the error + * @param {string} errorKey Error translation key to add * @param {string} dispatcher Action dispatcher name */ - setLayerErrorKey({ commit, getters }, { layerId, errorKey, dispatcher }) { - const layers = getters.getActiveLayersById(layerId) + addLayerErrorKey({ commit, getters }, { layerId, errorKey, dispatcher }) { + const layers = getters.getLayersById(layerId) if (layers.length === 0) { throw new Error( - `Failed to update layer error key "${layerId}", layer not found in active layers` + `Failed to add layer error key "${layerId}", layer not found in active layers` ) } const updatedLayers = layers.map((layer) => { const clone = layer.clone() - clone.errorKey = errorKey - clone.hasError = !!errorKey + clone.addErrorKey(errorKey) if (clone.isLoading) { clone.isLoading = false } @@ -565,6 +581,53 @@ const actions = { commit('updateLayers', { layers: updatedLayers, dispatcher }) }, + /** + * Remove a layer error translation key. + * + * NOTE: This set the error key to all layers matching the ID. + * + * @param {string} layerId Layer ID of the layer to set the error + * @param {string} errorKey Error translation key to remove + * @param {string} dispatcher Action dispatcher name + */ + removeLayerErrorKey({ commit, getters }, { layerId, errorKey, dispatcher }) { + const layers = getters.getLayersById(layerId) + if (layers.length === 0) { + throw new Error( + `Failed to remove layer error key "${layerId}", layer not found in active layers` + ) + } + const updatedLayers = layers.map((layer) => { + const clone = layer.clone() + clone.removeErrorKey(errorKey) + return clone + }) + commit('updateLayers', { layers: updatedLayers, dispatcher }) + }, + + /** + * Remove all layer error translation keys. + * + * NOTE: This set the error key to all layers matching the ID. + * + * @param {string} layerId Layer ID of the layer to clear the error keys + * @param {string} dispatcher Action dispatcher name + */ + clearLayerErrorKeys({ commit, getters }, { layerId, dispatcher }) { + const layers = getters.getLayerById(layerId) + if (layers.length === 0) { + throw new Error( + `Failed to clear layer error keys "${layerId}", layer not found in active layers` + ) + } + const updatedLayers = layers.map((layer) => { + const clone = layer.clone() + clone.clearErrorKeys() + return clone + }) + commit('updateLayers', { layers: updatedLayers, dispatcher }) + }, + /** * Set KML/GPX layer(s) with its data and metadata. * @@ -599,11 +662,9 @@ const actions = { clone.isLoading = false if (!extent) { - clone.errorKey = 'kml_gpx_file_empty' - clone.hasError = true + clone.addErrorKey('kml_gpx_file_empty') } else if (!getExtentForProjection(rootState.position.projection, extent)) { - clone.errorKey = 'kml_gpx_file_out_of_bounds' - clone.hasError = true + clone.addErrorKey('kml_gpx_file_out_of_bounds') } } if (metadata) { diff --git a/src/store/plugins/external-layers.plugin.js b/src/store/plugins/external-layers.plugin.js index 8044216aa..ce858fe3b 100644 --- a/src/store/plugins/external-layers.plugin.js +++ b/src/store/plugins/external-layers.plugin.js @@ -110,7 +110,7 @@ async function updateExternalLayer(store, capabilities, layer, projection) { return updated } catch (error) { log.error(`Failed to update external layer ${layer.id}: `, error) - store.dispatch('setLayerErrorKey', { + store.dispatch('addLayerErrorKey', { layerId: layer.id, errorKey: error.key ? error.key : 'error', ...dispatcher, diff --git a/src/store/plugins/load-geojson-style-and-data.plugin.js b/src/store/plugins/load-geojson-style-and-data.plugin.js index 66496b898..433da35fe 100644 --- a/src/store/plugins/load-geojson-style-and-data.plugin.js +++ b/src/store/plugins/load-geojson-style-and-data.plugin.js @@ -92,8 +92,7 @@ function loadDataAndStyle(geoJsonLayer) { ) const clone = geoJsonLayer.clone() clone.isLoading = false - clone.errorKey = 'loading_error_network_failure' - clone.hasError = true + clone.addErrorKey('loading_error_network_failure') return clone }), } diff --git a/src/store/plugins/load-gpx-data.plugin.js b/src/store/plugins/load-gpx-data.plugin.js index e5d553164..119503121 100644 --- a/src/store/plugins/load-gpx-data.plugin.js +++ b/src/store/plugins/load-gpx-data.plugin.js @@ -31,7 +31,7 @@ async function loadGpx(store, gpxLayer) { }) } catch (error) { log.error(`Error while fetching GPX data for layer ${gpxLayer?.id}`) - store.dispatch('setLayerErrorKey', { + store.dispatch('addLayerErrorKey', { layerId: gpxLayer.id, errorKey: `loading_error_network_failure`, ...dispatcher, diff --git a/src/store/plugins/load-kml-data.plugin.js b/src/store/plugins/load-kml-data.plugin.js index 23b5c3282..2120d499e 100644 --- a/src/store/plugins/load-kml-data.plugin.js +++ b/src/store/plugins/load-kml-data.plugin.js @@ -44,7 +44,7 @@ async function loadData(store, kmlLayer) { }) } catch (error) { log.error(`Error while fetching KML data for layer ${kmlLayer?.id}: ${error}`) - store.dispatch('setLayerErrorKey', { + store.dispatch('addLayerErrorKey', { layerId: kmlLayer.id, errorKey: `loading_error_network_failure`, ...dispatcher,