From 01fe218e8f4ff10de4f7e6f3d821fb3e14ed1b6d Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 22 Nov 2023 08:01:11 +0100 Subject: [PATCH 001/377] BGDIINF_SB-3180: Rework the WMS and WMTS capabilities parser Avoid also duplicate code in usage and improved interface. --- scripts/check-external-layers-providers.js | 18 ++++--- src/api/layers/WMSCapabilitiesParser.class.js | 49 ++++++++++++++----- .../layers/WMTSCapabilitiesParser.class.js | 44 +++++++++++++---- .../infobox/components/ImportContent.vue | 8 +-- src/store/plugins/external-layers.plugin.js | 14 +----- 5 files changed, 89 insertions(+), 44 deletions(-) diff --git a/scripts/check-external-layers-providers.js b/scripts/check-external-layers-providers.js index 2f7c3a9c4..3a0ef6d3f 100755 --- a/scripts/check-external-layers-providers.js +++ b/scripts/check-external-layers-providers.js @@ -154,9 +154,12 @@ function checkProviderResponseContent(provider, url, response, result) { if (isWmsGetCap(content)) { try { const capabilities = parseWmsCapabilities(content, url) - const layers = capabilities.Capability.Layer.Layer.map((layer) => - capabilities.getExternalLayerObject(layer, LV95, 1, true, false /* ignoreError */) - ).filter((layer) => !!layer) + const layers = capabilities.getAllExternalLayerObjects( + LV95, + 1 /* opacity */, + true /* visible */, + false /* ignoreError */ + ) if (layers.length === 0) { throw new Error(`No valid WMS layers found`) } @@ -173,9 +176,12 @@ function checkProviderResponseContent(provider, url, response, result) { } else if (isWmtsGetCap(content)) { try { const capabilities = parseWmtsCapabilities(content, url) - const layers = capabilities.Contents.Layer.map((layer) => - capabilities.getExternalLayerObject(layer, LV95, 1, true, false /* ignoreError */) - ).filter((layer) => !!layer) + const layers = capabilities.getAllExternalLayerObjects( + LV95, + 1 /* opacity */, + true /* visible */, + false /* ignoreError */ + ) if (layers.length === 0) { throw new Error(`No valid WMTS layers found`) } diff --git a/src/api/layers/WMSCapabilitiesParser.class.js b/src/api/layers/WMSCapabilitiesParser.class.js index de6c9d668..ca57f2372 100644 --- a/src/api/layers/WMSCapabilitiesParser.class.js +++ b/src/api/layers/WMSCapabilitiesParser.class.js @@ -19,15 +19,15 @@ export default class WMSCapabilitiesParser { * Find recursively in the capabilities the matching layer ID node * * @param {string} layerId Layer ID to search for - * @param {WMSCapabilities.Capability.Layer} startFrom Layer node to start from * @returns {WMSCapabilities.Capability.Layer} Capability layer node */ - findLayer(layerId, startFrom = null) { + findLayer(layerId) { + return this._findLayer(layerId, this.Capability.Layer.Layer) + } + + _findLayer(layerId, startFrom) { let layer = null - let layers = this.Capability.Layer.Layer - if (startFrom) { - layers = startFrom - } + const layers = startFrom for (let i = 0; i < layers.length && !layer; i++) { if (layers[i].Name === layerId) { @@ -35,7 +35,7 @@ export default class WMSCapabilitiesParser { } else if (!layers[i].Name && layers[i].Title === layerId) { layer = layers[i] } else if (layers[i].Layer?.length > 0) { - layer = this.findLayer(layerId, layers[i].Layer) + layer = this._findLayer(layerId, layers[i].Layer) } } return layer @@ -88,9 +88,9 @@ export default class WMSCapabilitiesParser { } /** - * Get ExternalWMSLayer object from capabilities + * Get ExternalWMSLayer object from capabilities for the given layer ID * - * @param {WMSCapabilities.Layer} layer WMS Capabilities layer object + * @param {string} layerId Layer ID of the layer to retrieve * @param {CoordinateSystem} projection Projection currently used by the application * @param {number} opacity * @param {boolean} visible @@ -98,7 +98,34 @@ export default class WMSCapabilitiesParser { * value or null * @returns {ExternalWMSLayer | null} ExternalWMSLayer object or nul in case of error */ - getExternalLayerObject(layer, projection, opacity = 1, visible = true, ignoreError = true) { + getExternalLayerObject(layerId, projection, opacity = 1, visible = true, ignoreError = true) { + const layer = this.findLayer(layerId) + if (!layer) { + throw new Error( + `No WMS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` + ) + } + return this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + } + + /** + * Get all ExternalWMSLayer objects from capabilities + * + * @param {CoordinateSystem} projection Projection currently used by the application + * @param {number} opacity + * @param {boolean} visible + * @param {boolean} ignoreError Don't throw exception in case of error, but return a default + * value or null + * @returns {[ExternalWMSLayer | ExternalGroupOfLayers]} List of + * ExternalWMSLayer|ExternalGroupOfLayers objects + */ + getAllExternalLayerObjects(projection, opacity = 1, visible = true, ignoreError = true) { + return this.Capability.Layer.Layer.map((layer) => + this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + ).filter((layer) => !!layer) + } + + _getExternalLayerObject(layer, projection, opacity, visible, ignoreError) { const { layerId, title, url, version, abstract, attributions, extent } = this.getLayerAttributes(layer, projection, ignoreError) @@ -110,7 +137,7 @@ export default class WMSCapabilitiesParser { // Go through the child to get valid layers if (layer.Layer?.length) { const layers = layer.Layer.map((l) => - this.getExternalLayerObject(l, projection, opacity, visible, ignoreError) + this._getExternalLayerObject(l, projection, opacity, visible, ignoreError) ) return new ExternalGroupOfLayers( title, diff --git a/src/api/layers/WMTSCapabilitiesParser.class.js b/src/api/layers/WMTSCapabilitiesParser.class.js index fa34701e8..478b3197b 100644 --- a/src/api/layers/WMTSCapabilitiesParser.class.js +++ b/src/api/layers/WMTSCapabilitiesParser.class.js @@ -17,15 +17,15 @@ export default class WMTSCapabilitiesParser { * Find recursively in the capabilities the matching layer ID node * * @param {string} layerId Layer ID to search for - * @param {WMTSCapabilities.Contents.Layer} startFrom Layer node to start from * @returns {WMTSCapabilities.Contents.Layer} Capability layer node */ - findLayer(layerId, startFrom = null) { + findLayer(layerId) { + return this._findLayer(layerId, this.Contents.Layer) + } + + _findLayer(layerId, startFrom) { let layer = null - let layers = this.Contents.Layer - if (startFrom) { - layers = startFrom - } + const layers = startFrom for (let i = 0; i < layers.length && !layer; i++) { if (layers[i].Identifier === layerId) { @@ -78,9 +78,9 @@ export default class WMTSCapabilitiesParser { } /** - * Get ExternalWMTSLayer object from the capabilities + * Get ExternalWMTSLayer object from the capabilities for the given layer ID * - * @param {WMTSCapabilities.Contents.Layer} layer WMTS Capabilities layer object + * @param {string} layerId WMTS Capabilities layer ID to retrieve * @param {CoordinateSystem} projection Projection currently used by the application * @param {number} opacity * @param {boolean} visible @@ -88,7 +88,33 @@ export default class WMTSCapabilitiesParser { * value or null * @returns {ExternalWMTSLayer | null} ExternalWMTSLayer object */ - getExternalLayerObject(layer, projection, opacity = 1, visible = true, ignoreError = true) { + getExternalLayerObject(layerId, projection, opacity = 1, visible = true, ignoreError = true) { + const layer = this.findLayer(layerId) + if (!layer) { + throw new Error( + `No WMTS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` + ) + } + return this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + } + + /** + * Get all ExternalWMTSLayer objects from capabilities + * + * @param {CoordinateSystem} projection Projection currently used by the application + * @param {number} opacity + * @param {boolean} visible + * @param {boolean} ignoreError Don't throw exception in case of error, but return a default + * value or null + * @returns {[ExternalWMTSLayer]} List of ExternalWMTSLayer objects + */ + getAllExternalLayerObjects(projection, opacity = 1, visible = true, ignoreError = true) { + return this.Contents.Layer.map((layer) => + this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + ).filter((layer) => !!layer) + } + + _getExternalLayerObject(layer, projection, opacity, visible, ignoreError) { const attributes = this.getLayerAttributes(layer, projection, ignoreError) if (!attributes) { diff --git a/src/modules/infobox/components/ImportContent.vue b/src/modules/infobox/components/ImportContent.vue index e04d63fd3..51fbdb1f7 100644 --- a/src/modules/infobox/components/ImportContent.vue +++ b/src/modules/infobox/components/ImportContent.vue @@ -352,14 +352,10 @@ export default { height: capabilities.Service.MaxHeight, } } - this.importedLayers = capabilities.Capability.Layer.Layer.map((layer) => - capabilities.getExternalLayerObject(layer, this.projection, 1, true) - ).filter((layer) => !!layer) + this.importedLayers = capabilities.getAllExternalLayerObjects(this.projection, 1, true) }, handleWmts(capabilities) { - this.importedLayers = capabilities.Contents.Layer.map((layer) => - capabilities.getExternalLayerObject(layer, this.projection, 1, true) - ).filter((layer) => !!layer) + this.importedLayers = capabilities.getAllExternalLayerObjects(this.projection, 1, true) }, async handleFileUrl() { this.wmsMaxSize = undefined diff --git a/src/store/plugins/external-layers.plugin.js b/src/store/plugins/external-layers.plugin.js index 109fad33f..359bc7499 100644 --- a/src/store/plugins/external-layers.plugin.js +++ b/src/store/plugins/external-layers.plugin.js @@ -58,13 +58,8 @@ async function updateExternalLayer(store, externalLayer, projection) { async function updatedWMSLayerAttributes(externalLayer, projection) { const capabilities = await readWmsCapabilities(externalLayer.baseURL) - - const layer = capabilities.findLayer(externalLayer.externalLayerId) - if (!layer) { - throw new Error(`No layer ${externalLayer.externalLayerId} found in Capabilities`) - } const newObject = capabilities.getExternalLayerObject( - layer, + externalLayer.externalLayerId, projection, externalLayer.opacity, externalLayer.visible @@ -79,13 +74,8 @@ async function updatedWMSLayerAttributes(externalLayer, projection) { async function updatedWMTSLayerAttributes(externalLayer, projection) { const capabilities = await readWmtsCapabilities(externalLayer.baseURL) - - const layer = capabilities.findLayer(externalLayer.externalLayerId) - if (!layer) { - throw new Error(`No layer ${externalLayer.externalLayerId} found in Capabilities`) - } const newObject = capabilities.getExternalLayerObject( - layer, + externalLayer.externalLayerId, projection, externalLayer.opacity, externalLayer.visible From 2b87f290a810012173d98b0920f2113d94337621 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 22 Nov 2023 14:37:21 +0100 Subject: [PATCH 002/377] BGDIINF_SB-3180: Fixed WMS to version 1.3.0 Unfortunately it turns out that openlayer WMSCapabilities parser only support the WMS version 1.3.0. E.g. it doesn't support the SRS attribute of version 1.1.0. Therefore we force to use the version 1.3.0 and raise an error if the GetCap returns another version. --- src/api/layers/layers-external.api.js | 2 ++ src/config.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/layers/layers-external.api.js b/src/api/layers/layers-external.api.js index 3ceecf2da..49ea8d60f 100644 --- a/src/api/layers/layers-external.api.js +++ b/src/api/layers/layers-external.api.js @@ -17,6 +17,8 @@ export function setWmsGetCapParams(url, language) { // Mandatory params url.searchParams.set('SERVICE', 'WMS') url.searchParams.set('REQUEST', 'GetCapabilities') + // Currently openlayers only supports version 1.3.0 ! + url.searchParams.set('VERSION', '1.3.0') // Optional params url.searchParams.set('FORMAT', 'text/xml') if (language) { diff --git a/src/config.js b/src/config.js index d73ad6ff4..6540a5711 100644 --- a/src/config.js +++ b/src/config.js @@ -281,4 +281,4 @@ export const DISABLE_DRAWING_MENU_FOR_LEGACY_ON_HOSTNAMES = [ * * @type {String[]} */ -export const WMS_SUPPORTED_VERSIONS = ['1.3.0', '1.1.1', '1.1.0', '1.0.0'] +export const WMS_SUPPORTED_VERSIONS = ['1.3.0'] From 98f3428401413a8d2df4c3ab7febc7c5e298a82c Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 22 Nov 2023 14:54:41 +0100 Subject: [PATCH 003/377] BGDIINF_SB-3180: Full support for external WMS and WMTS layer extent Try to supports all possibles way to define a layer extent based on the WMS and WMTS specifications. This is required by some of our providers. --- src/api/layers/WMSCapabilitiesParser.class.js | 199 +++++++++++------- .../layers/WMTSCapabilitiesParser.class.js | 169 ++++++++++----- 2 files changed, 246 insertions(+), 122 deletions(-) diff --git a/src/api/layers/WMSCapabilitiesParser.class.js b/src/api/layers/WMSCapabilitiesParser.class.js index ca57f2372..12280bdca 100644 --- a/src/api/layers/WMSCapabilitiesParser.class.js +++ b/src/api/layers/WMSCapabilitiesParser.class.js @@ -2,7 +2,7 @@ import { WMS_SUPPORTED_VERSIONS } from '@/config' import { LayerAttribution } from '@/api/layers/AbstractLayer.class' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' -import allCoordinateSystems from '@/utils/coordinates/coordinateSystems' +import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' import { WMSCapabilities } from 'ol/format' import proj4 from 'proj4' @@ -11,7 +11,13 @@ import proj4 from 'proj4' export default class WMSCapabilitiesParser { constructor(content, originUrl) { const parser = new WMSCapabilities() - Object.assign(this, parser.read(content)) + try { + Object.assign(this, parser.read(content)) + } catch (error) { + log.error(`Failed to parse capabilities of ${originUrl}`, error) + throw new Error(`Failed to parse WMTS Capabilities: invalid content`) + } + this.originUrl = new URL(originUrl) } @@ -19,72 +25,29 @@ export default class WMSCapabilitiesParser { * Find recursively in the capabilities the matching layer ID node * * @param {string} layerId Layer ID to search for - * @returns {WMSCapabilities.Capability.Layer} Capability layer node + * @returns {{ + * layer: WMSCapabilities.Capability.Layer + * parents: [WMSCapabilities.Capability.Layer] + * }} + * Capability layer node and its parents or an empty object if not found */ findLayer(layerId) { - return this._findLayer(layerId, this.Capability.Layer.Layer) + return this._findLayer(layerId, this.Capability.Layer.Layer, [this.Capability.Layer]) } - _findLayer(layerId, startFrom) { - let layer = null + _findLayer(layerId, startFrom, parents) { + let found = {} const layers = startFrom - for (let i = 0; i < layers.length && !layer; i++) { - if (layers[i].Name === layerId) { - layer = layers[i] - } else if (!layers[i].Name && layers[i].Title === layerId) { - layer = layers[i] + for (let i = 0; i < layers.length && !found.layer; i++) { + if (layers[i].Name === layerId || layers[i].Title === layerId) { + found.layer = layers[i] + found.parents = parents } else if (layers[i].Layer?.length > 0) { - layer = this._findLayer(layerId, layers[i].Layer) + found = this._findLayer(layerId, layers[i].Layer, [layers[i], ...parents]) } } - return layer - } - - /** - * Get Layer attributes from the WMS Capabilities to be used in ExternalWMSLayer - * - * @param {WMSCapabilities.Layer} layer WMS Capabilities layer object - * @param {CoordinateSystem} projection Projection currently used by the application - * @param {boolean} ignoreError Don't throw exception in case of error, but return a default - * value or null - * @returns {Object | null} Layer attributes - */ - getLayerAttributes(layer, projection, ignoreError = true) { - let layerId = layer.Name - // Some WMS only have a Title and no Name, therefore in this case take the Title as layerId - if (!layerId && layer.Title) { - // if we don't have a name use the title as name - layerId = layer.Title - } - if (!layerId) { - const msg = `No layerId found in WMS capabilities for layer` - log.error(msg, layer) - if (ignoreError) { - return {} - } - throw new Error(msg) - } - - if (!this.version) { - throw new Error(`No WMS version found in Capabilities of ${this.originUrl.toString()}`) - } - if (!WMS_SUPPORTED_VERSIONS.includes(this.version)) { - throw new Error( - `WMS version ${this.version} of ${this.originUrl.toString()} not supported` - ) - } - return { - layerId: layerId, - title: layer.Title, - url: - this.Capability?.Request?.GetMap?.DCPType[0]?.HTTP?.Get?.OnlineResource || - this.originUrl.toString(), - version: this.version, - abstract: layer.Abstract, - attributions: this.getLayerAttribution(layer), - extent: this.getLayerExtent(layerId, layer, projection, ignoreError), - } + return found } /** @@ -99,13 +62,20 @@ export default class WMSCapabilitiesParser { * @returns {ExternalWMSLayer | null} ExternalWMSLayer object or nul in case of error */ getExternalLayerObject(layerId, projection, opacity = 1, visible = true, ignoreError = true) { - const layer = this.findLayer(layerId) + const { layer, parents } = this.findLayer(layerId) if (!layer) { throw new Error( `No WMS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` ) } - return this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + return this._getExternalLayerObject( + layer, + parents, + projection, + opacity, + visible, + ignoreError + ) } /** @@ -121,13 +91,20 @@ export default class WMSCapabilitiesParser { */ getAllExternalLayerObjects(projection, opacity = 1, visible = true, ignoreError = true) { return this.Capability.Layer.Layer.map((layer) => - this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) + this._getExternalLayerObject( + layer, + [this.Capability.Layer], + projection, + opacity, + visible, + ignoreError + ) ).filter((layer) => !!layer) } - _getExternalLayerObject(layer, projection, opacity, visible, ignoreError) { + _getExternalLayerObject(layer, parents, projection, opacity, visible, ignoreError) { const { layerId, title, url, version, abstract, attributions, extent } = - this.getLayerAttributes(layer, projection, ignoreError) + this._getLayerAttributes(layer, parents, projection, ignoreError) if (!layerId) { // without layerId we can do nothing @@ -137,8 +114,15 @@ export default class WMSCapabilitiesParser { // Go through the child to get valid layers if (layer.Layer?.length) { const layers = layer.Layer.map((l) => - this._getExternalLayerObject(l, projection, opacity, visible, ignoreError) - ) + this._getExternalLayerObject( + l, + [layer, ...parents], + projection, + opacity, + visible, + ignoreError + ) + ).filter((layer) => !!layer) return new ExternalGroupOfLayers( title, opacity, @@ -167,15 +151,56 @@ export default class WMSCapabilitiesParser { ) } - getLayerExtent(layerId, layer, projection, ignoreError = true) { + _getLayerAttributes(layer, parents, projection, ignoreError = true) { + let layerId = layer.Name + // Some WMS only have a Title and no Name, therefore in this case take the Title as layerId + if (!layerId && layer.Title) { + // if we don't have a name use the title as name + layerId = layer.Title + } + if (!layerId) { + // Without layerID we cannot use the layer in our viewer + const msg = `No layerId found in WMS capabilities for layer in ${this.originUrl.toString()}` + log.error(msg, layer) + if (ignoreError) { + return {} + } + throw new Error(msg) + } + + if (!this.version) { + throw new Error(`No WMS version found in Capabilities of ${this.originUrl.toString()}`) + } + if (!WMS_SUPPORTED_VERSIONS.includes(this.version)) { + throw new Error( + `WMS version ${this.version} of ${this.originUrl.toString()} not supported` + ) + } + return { + layerId: layerId, + title: layer.Title, + url: + this.Capability?.Request?.GetMap?.DCPType[0]?.HTTP?.Get?.OnlineResource || + this.originUrl.toString(), + version: this.version, + abstract: layer.Abstract, + attributions: this._getLayerAttribution(layer), + extent: this._getLayerExtent(layerId, layer, parents, projection, ignoreError), + } + } + + _getLayerExtent(layerId, layer, parents, projection, ignoreError = true) { let layerExtent = null const matchedBbox = layer.BoundingBox?.find((bbox) => bbox.crs === projection.epsg) + // First try to find a matching extent from the BoundingBox if (matchedBbox) { layerExtent = [ [matchedBbox.extent[0], matchedBbox.extent[1]], [matchedBbox.extent[2], matchedBbox.extent[3]], ] - } else { + } + // Then try to find a supported CRS extent from the BoundingBox + if (!layerExtent) { const bbox = layer.BoundingBox?.find((bbox) => allCoordinateSystems.find((projection) => projection.epsg === bbox.crs) ) @@ -186,10 +211,35 @@ export default class WMSCapabilitiesParser { ] } } + // Fallback to the EX_GeographicBoundingBox + if (!layerExtent && layer.EX_GeographicBoundingBox) { + const bbox = layer.EX_GeographicBoundingBox + if (projection !== WGS84) { + layerExtent = [ + proj4(WGS84.epsg, projection.epsg, [bbox[0], bbox[1]]), + proj4(WGS84.epsg, projection.epsg, [bbox[2], bbox[3]]), + ] + } else { + layerExtent = [ + [bbox[0], bbox[1]], + [bbox[2], bbox[3]], + ] + } + } + // Finally search the extent in the parents + if (!layerExtent && parents.length > 0) { + return this._getLayerExtent( + layerId, + parents[0], + parents.slice(1), + projection, + ignoreError + ) + } if (!layerExtent) { - const msg = `No layer extent found for ${layerId}` - log.error(msg) + const msg = `No layer extent found for ${layerId} in ${this.originUrl.toString()}` + log.error(msg, layer, parents) if (!ignoreError) { throw Error(msg) } @@ -198,15 +248,18 @@ export default class WMSCapabilitiesParser { return layerExtent } - getLayerAttribution(layer) { + _getLayerAttribution(layer) { let title = null let url = null if (layer.Attribution || this.Capability.Layer.Attribution) { const attribution = layer.Attribution || this.Capability.Layer.Attribution url = attribution.OnlineResource - title = attribution.Title || new URL(attribution.OnlineResource).hostname + title = + attribution.Title || new URL(attribution.OnlineResource || this.originUrl).hostname } else { - title = this.Service.Title || new URL(this.Service.OnlineResource).hostname + title = + this.Service?.Title || + new URL(this.Service?.OnlineResource || this.originUrl).hostname } return [new LayerAttribution(title, url)] diff --git a/src/api/layers/WMTSCapabilitiesParser.class.js b/src/api/layers/WMTSCapabilitiesParser.class.js index 478b3197b..67d6d34a7 100644 --- a/src/api/layers/WMTSCapabilitiesParser.class.js +++ b/src/api/layers/WMTSCapabilitiesParser.class.js @@ -2,14 +2,26 @@ import { LayerAttribution } from '@/api/layers/AbstractLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' import log from '@/utils/logging' import WMTSCapabilities from 'ol/format/WMTSCapabilities' -import { WGS84 } from '@/utils/coordinates/coordinateSystems' +import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems' import proj4 from 'proj4' +export function parseCrs(crs) { + let epsgNumber = crs?.split(':').pop() + if (/84/.test(epsgNumber)) { + epsgNumber = '4326' + } + return allCoordinateSystems.find((system) => system.epsg === `EPSG:${epsgNumber}`) +} + /** Wrapper around the OpenLayer WMSCapabilities to add more functionalities */ export default class WMTSCapabilitiesParser { constructor(content, originUrl) { const parser = new WMTSCapabilities() - Object.assign(this, parser.read(content)) + const capabilities = parser.read(content) + if (!capabilities.version) { + throw new Error(`Failed to parse WMTS Capabilities: invalid content`) + } + Object.assign(this, capabilities) this.originUrl = new URL(originUrl) } @@ -37,46 +49,6 @@ export default class WMTSCapabilitiesParser { return layer } - /** - * Get Layer attributes from the WMTS Capabilities to be used in ExternalWMTSLayer - * - * @param {WMTSCapabilities.Contents.Layer} layer WMTS Capabilities layer object - * @param {CoordinateSystem} projection Projection currently used by the application - * @param {boolean} ignoreError Don't throw exception in case of error, but return a default - * value or null - * @returns {Object} Layer attributes - */ - getLayerAttributes(layer, projection, ignoreError = true) { - let layerId = layer.Identifier - if (!layerId) { - // fallback to Title - layerId = layer.Title - } - if (!layerId) { - const msg = `No layer identifier available` - log.error(msg, layer) - if (ignoreError) { - return {} - } - throw new Error(msg) - } - const title = layer.Title || layerId - - const getCapUrl = - this.OperationsMetadata?.GetCapabilities?.DCP?.HTTP?.Get[0]?.href || - this.originUrl.toString() - - return { - layerId: layerId, - title: title, - url: getCapUrl, - version: this.version, - abstract: layer.Abstract, - attributions: this.getLayerAttribution(layerId), - extent: this.getLayerExtent(layerId, layer, projection, ignoreError), - } - } - /** * Get ExternalWMTSLayer object from the capabilities for the given layer ID * @@ -115,7 +87,7 @@ export default class WMTSCapabilitiesParser { } _getExternalLayerObject(layer, projection, opacity, visible, ignoreError) { - const attributes = this.getLayerAttributes(layer, projection, ignoreError) + const attributes = this._getLayerAttributes(layer, projection, ignoreError) if (!attributes) { return null @@ -128,19 +100,118 @@ export default class WMTSCapabilitiesParser { attributes.url, attributes.layerId, attributes.attributions, - attributes.Abstract, + attributes.abstract, attributes.extent, false ) } - getLayerExtent(layerId, layer, projection, ignoreError = true) { + _getLayerAttributes(layer, projection, ignoreError = true) { + let layerId = layer.Identifier + if (!layerId) { + // fallback to Title + layerId = layer.Title + } + if (!layerId) { + const msg = `No layer identifier found in Capabilities ${this.originUrl.toString()}` + log.error(msg, layer) + if (ignoreError) { + return {} + } + throw new Error(msg) + } + const title = layer.Title || layerId + + const getCapUrl = + this.OperationsMetadata?.GetCapabilities?.DCP?.HTTP?.Get[0]?.href || + this.originUrl.toString() + + return { + layerId: layerId, + title: title, + url: getCapUrl, + version: this.version, + abstract: layer.Abstract, + attributions: this._getLayerAttribution(layerId), + extent: this._getLayerExtent(layerId, layer, projection, ignoreError), + } + } + + _findTileMatrixSetFromLinks(links) { + let tileMatrixSet = null + links?.forEach((link) => { + tileMatrixSet = this.Contents.TileMatrixSet?.find( + (set) => set.Identifier === link.TileMatrixSet + ) + if (tileMatrixSet) { + return + } + }) + return tileMatrixSet + } + + _getLayerExtent(layerId, layer, projection, ignoreError) { let layerExtent = null + let extentEpsg = null + // First try to get the extent from the default bounding box if (layer.WGS84BoundingBox?.length) { - const extent = layer.WGS84BoundingBox layerExtent = [ - proj4(WGS84.epsg, projection.epsg, [extent[0], extent[1]]), - proj4(WGS84.epsg, projection.epsg, [extent[2], extent[3]]), + [layer.WGS84BoundingBox[0], layer.WGS84BoundingBox[1]], + [layer.WGS84BoundingBox[2], layer.WGS84BoundingBox[3]], + ] + extentEpsg = WGS84.epsg + } + // Some provider don't uses the WGS84BoundingBox, but uses the BoundingBox instead + else if (layer.BoundingBox) { + // search for a matching crs bounding box + const matching = layer.BoundingBox.find((bbox) => parseCrs(bbox.crs) === projection) + if (matching) { + layerExtent = [ + [matching.extent[0], matching.extent[1]], + [matching.extent[2], matching.extent[3]], + ] + } else if (layer.BoundingBox.length === 1 && !layer.BoundingBox[0].crs) { + // if we have only one bounding box without CRS, then take it searching the CRS + // fom the TileMatrixSet + const tileMatrixSet = this._findTileMatrixSetFromLinks(layer.TileMatrixSetLink) + extentEpsg = parseCrs(tileMatrixSet?.SupportedCRS)?.epsg + if (extentEpsg) { + layerExtent = [ + [layer.BoundingBox[0].extent[0], layer.BoundingBox[0].extent[1]], + [layer.BoundingBox[0].extent[2], layer.BoundingBox[0].extent[3]], + ] + } + } else { + // if we have multiple bounding box search for the one that specify a supported CRS + const supported = layer.BoundingBox.find((bbox) => parseCrs(bbox.crs)) + if (supported) { + extentEpsg = parseCrs(supported.crs).epsg + layerExtent = [ + [supported.extent[0], supported.extent[1]], + [supported.extent[2], supported.extent[3]], + ] + } + } + } + // If we didn't find a valid and supported bounding box in the layer then fallback to the + // linked TileMatrixSet. NOTE: some provider don't specify the bounding box at the layer + // level but on the TileMatrixSet + if (!layerExtent && this.Contents.TileMatrixSet) { + const tileMatrixSet = this._findTileMatrixSetFromLinks(layer.TileMatrixSetLink) + const system = parseCrs(tileMatrixSet?.SupportedCRS) + if (tileMatrixSet && system && tileMatrixSet.BoundingBox) { + layerExtent = [ + [tileMatrixSet.BoundingBox[0], tileMatrixSet.BoundingBox[1]], + [tileMatrixSet.BoundingBox[2], tileMatrixSet.BoundingBox[3]], + ] + extentEpsg = system.epsg + } + } + // Convert the extent if needed + if (layerExtent && extentEpsg && projection.epsg !== extentEpsg) { + layerExtent = [ + proj4(extentEpsg, projection.epsg, layerExtent[0]), + proj4(extentEpsg, projection.epsg, layerExtent[1]), ] } if (!layerExtent) { @@ -153,7 +224,7 @@ export default class WMTSCapabilitiesParser { return layerExtent } - getLayerAttribution(layerId) { + _getLayerAttribution(layerId) { let title = this.ServiceProvider?.ProviderName let url = this.ServiceProvider?.ProviderSite From 123c85c28fea1af09db6509b5da3f27077a985be Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Thu, 30 Nov 2023 09:22:32 +0100 Subject: [PATCH 004/377] BGDIINF_SB-3180: Code improvement based on review --- src/api/layers/WMSCapabilitiesParser.class.js | 92 +++++++++++-------- .../layers/WMTSCapabilitiesParser.class.js | 41 +++++---- 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/api/layers/WMSCapabilitiesParser.class.js b/src/api/layers/WMSCapabilitiesParser.class.js index 12280bdca..7fc21dec2 100644 --- a/src/api/layers/WMSCapabilitiesParser.class.js +++ b/src/api/layers/WMSCapabilitiesParser.class.js @@ -7,6 +7,21 @@ import log from '@/utils/logging' import { WMSCapabilities } from 'ol/format' import proj4 from 'proj4' +function findLayer(layerId, startFrom, parents) { + let found = {} + const layers = startFrom + + for (let i = 0; i < layers.length && !found.layer; i++) { + if (layers[i].Name === layerId || layers[i].Title === layerId) { + found.layer = layers[i] + found.parents = parents + } else if (layers[i].Layer?.length > 0) { + found = findLayer(layerId, layers[i].Layer, [layers[i], ...parents]) + } + } + return found +} + /** Wrapper around the OpenLayer WMSCapabilities to add more functionalities */ export default class WMSCapabilitiesParser { constructor(content, originUrl) { @@ -15,7 +30,7 @@ export default class WMSCapabilitiesParser { Object.assign(this, parser.read(content)) } catch (error) { log.error(`Failed to parse capabilities of ${originUrl}`, error) - throw new Error(`Failed to parse WMTS Capabilities: invalid content`) + throw new Error(`Failed to parse WMTS Capabilities: invalid content: ${error}`) } this.originUrl = new URL(originUrl) @@ -32,22 +47,7 @@ export default class WMSCapabilitiesParser { * Capability layer node and its parents or an empty object if not found */ findLayer(layerId) { - return this._findLayer(layerId, this.Capability.Layer.Layer, [this.Capability.Layer]) - } - - _findLayer(layerId, startFrom, parents) { - let found = {} - const layers = startFrom - - for (let i = 0; i < layers.length && !found.layer; i++) { - if (layers[i].Name === layerId || layers[i].Title === layerId) { - found.layer = layers[i] - found.parents = parents - } else if (layers[i].Layer?.length > 0) { - found = this._findLayer(layerId, layers[i].Layer, [layers[i], ...parents]) - } - } - return found + return findLayer(layerId, this.Capability.Layer.Layer, [this.Capability.Layer]) } /** @@ -64,9 +64,12 @@ export default class WMSCapabilitiesParser { getExternalLayerObject(layerId, projection, opacity = 1, visible = true, ignoreError = true) { const { layer, parents } = this.findLayer(layerId) if (!layer) { - throw new Error( - `No WMS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` - ) + const msg = `No WMS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` + log.error(msg) + if (ignoreError) { + return null + } + throw new Error(msg) } return this._getExternalLayerObject( layer, @@ -168,14 +171,20 @@ export default class WMSCapabilitiesParser { throw new Error(msg) } - if (!this.version) { - throw new Error(`No WMS version found in Capabilities of ${this.originUrl.toString()}`) - } - if (!WMS_SUPPORTED_VERSIONS.includes(this.version)) { - throw new Error( - `WMS version ${this.version} of ${this.originUrl.toString()} not supported` - ) + if (!this.version || !WMS_SUPPORTED_VERSIONS.includes(this.version)) { + let msg = '' + if (!this.version) { + msg = `No WMS version found in Capabilities of ${this.originUrl.toString()}` + } else { + msg = `WMS version ${this.version} of ${this.originUrl.toString()} not supported` + } + log.error(msg, layer) + if (ignoreError) { + return {} + } + throw new Error(msg) } + return { layerId: layerId, title: layer.Title, @@ -184,7 +193,7 @@ export default class WMSCapabilitiesParser { this.originUrl.toString(), version: this.version, abstract: layer.Abstract, - attributions: this._getLayerAttribution(layer), + attributions: this._getLayerAttribution(layerId, layer, ignoreError), extent: this._getLayerExtent(layerId, layer, parents, projection, ignoreError), } } @@ -248,18 +257,25 @@ export default class WMSCapabilitiesParser { return layerExtent } - _getLayerAttribution(layer) { + _getLayerAttribution(layerId, layer, ignoreError) { let title = null let url = null - if (layer.Attribution || this.Capability.Layer.Attribution) { - const attribution = layer.Attribution || this.Capability.Layer.Attribution - url = attribution.OnlineResource - title = - attribution.Title || new URL(attribution.OnlineResource || this.originUrl).hostname - } else { - title = - this.Service?.Title || - new URL(this.Service?.OnlineResource || this.originUrl).hostname + try { + if (layer.Attribution || this.Capability.Layer.Attribution) { + const attribution = layer.Attribution || this.Capability.Layer.Attribution + url = attribution.OnlineResource + title = attribution.Title || new URL(attribution.OnlineResource).hostname + } else { + title = this.Service?.Title || new URL(this.Service?.OnlineResource).hostname + } + } catch (error) { + const msg = `Failed to get an attribution title/url for ${layerId}: ${error}` + log.error(msg, layer, error) + if (!ignoreError) { + throw new Error(msg) + } + title = new URL(this.originUrl).hostname + url = null } return [new LayerAttribution(title, url)] diff --git a/src/api/layers/WMTSCapabilitiesParser.class.js b/src/api/layers/WMTSCapabilitiesParser.class.js index 67d6d34a7..1b2043f39 100644 --- a/src/api/layers/WMTSCapabilitiesParser.class.js +++ b/src/api/layers/WMTSCapabilitiesParser.class.js @@ -5,7 +5,7 @@ import WMTSCapabilities from 'ol/format/WMTSCapabilities' import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems' import proj4 from 'proj4' -export function parseCrs(crs) { +function parseCrs(crs) { let epsgNumber = crs?.split(':').pop() if (/84/.test(epsgNumber)) { epsgNumber = '4326' @@ -13,6 +13,20 @@ export function parseCrs(crs) { return allCoordinateSystems.find((system) => system.epsg === `EPSG:${epsgNumber}`) } +function findLayer(layerId, startFrom) { + let layer = null + const layers = startFrom + + for (let i = 0; i < layers.length && !layer; i++) { + if (layers[i].Identifier === layerId) { + layer = layers[i] + } else if (!layers[i].Identifier && layers[i].Title === layerId) { + layer = layers[i] + } + } + return layer +} + /** Wrapper around the OpenLayer WMSCapabilities to add more functionalities */ export default class WMTSCapabilitiesParser { constructor(content, originUrl) { @@ -32,21 +46,7 @@ export default class WMTSCapabilitiesParser { * @returns {WMTSCapabilities.Contents.Layer} Capability layer node */ findLayer(layerId) { - return this._findLayer(layerId, this.Contents.Layer) - } - - _findLayer(layerId, startFrom) { - let layer = null - const layers = startFrom - - for (let i = 0; i < layers.length && !layer; i++) { - if (layers[i].Identifier === layerId) { - layer = layers[i] - } else if (!layers[i].Identifier && layers[i].Title === layerId) { - layer = layers[i] - } - } - return layer + return findLayer(layerId, this.Contents.Layer) } /** @@ -63,9 +63,12 @@ export default class WMTSCapabilitiesParser { getExternalLayerObject(layerId, projection, opacity = 1, visible = true, ignoreError = true) { const layer = this.findLayer(layerId) if (!layer) { - throw new Error( - `No WMTS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` - ) + const msg = `No WMTS layer ${layerId} found in Capabilities ${this.originUrl.toString()}` + log.error(msg) + if (!ignoreError) { + throw new Error(msg) + } + return null } return this._getExternalLayerObject(layer, projection, opacity, visible, ignoreError) } From 550af74c86f2231476cb7b2649fb68cd7d21dfdd Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 29 Nov 2023 16:27:35 +0100 Subject: [PATCH 005/377] BGDIINF_SB-3180: Added unit tests for external WMTS and WMS parsing --- .../WMSCapabitliesParser.class.spec.js | 687 ++++++++++++++++++ .../WMTSCapabitliesParser.class.spec.js | 223 ++++++ .../layers/__tests__/wms-geoadmin-sample.xml | 417 +++++++++++ src/api/layers/__tests__/wmts-ogc-sample.xml | 641 ++++++++++++++++ 4 files changed, 1968 insertions(+) create mode 100644 src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js create mode 100644 src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js create mode 100644 src/api/layers/__tests__/wms-geoadmin-sample.xml create mode 100644 src/api/layers/__tests__/wmts-ogc-sample.xml diff --git a/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js new file mode 100644 index 000000000..bdaa334c3 --- /dev/null +++ b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js @@ -0,0 +1,687 @@ +import WMSCapabilitiesParser from '../WMSCapabilitiesParser.class' +import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' +import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' +import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' + +import { readFile } from 'fs/promises' +import { describe, it, expect, beforeAll, expectTypeOf } from 'vitest' + +describe('WMSCapabilitiesParser - invalid', () => { + it('Throw Error on invalid input', () => { + const invalidContent = 'Invalid input' + + expect( + () => new WMSCapabilitiesParser(invalidContent, 'https://wms.geo.admin.ch') + ).toThrowError(/failed/i) + }) +}) + +describe('WMSCapabilitiesParser of wms-geoadmin-sample.xml', () => { + let capabilities + beforeAll(async () => { + const content = await readFile(`${__dirname}/wms-geoadmin-sample.xml`, 'utf8') + capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + }) + it('Parse Capabilities', async () => { + expect(capabilities.version).toBe('1.3.0') + expect(capabilities.Capability).toBeTypeOf('object') + expect(capabilities.Service).toBeTypeOf('object') + expect(capabilities.originUrl).toBeInstanceOf(URL) + expect(capabilities.originUrl.toString()).toBe('https://wms.geo.admin.ch/') + }) + it('Parse layer attributes', () => { + // General layer + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expect(layer.name).toBe('OpenData-AV') + expect(layer.abstract).toBe('The official survey (AV).') + expect(layer.baseURL).toBe('https://wms.geo.admin.ch/?') + + // Layer without .Name + layer = capabilities.getExternalLayerObject('Periodic-Tracking', WGS84) + expect(layer.externalLayerId).toBe('Periodic-Tracking') + expect(layer.name).toBe('Periodic-Tracking') + expect(layer.abstract).toBe('Layer without Name element should use the Title') + expect(layer.baseURL).toBe('https://wms.geo.admin.ch/?') + }) + it('Parse layer attribution', () => { + // Attribution in root layer + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('The federal geoportal') + expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution') + + // Attribution in layer + layer = capabilities.getExternalLayerObject('Periodic-Tracking', WGS84) + expect(layer.externalLayerId).toBe('Periodic-Tracking') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('BGDI') + expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution-bgdi') + }) + it('Get Layer Extent in LV95', () => { + const externalLayers = capabilities.getAllExternalLayerObjects(LV95) + // Extent from matching CRS BoundingBox + expect(externalLayers[0].externalLayerId).toBe('ch.swisstopo-vd.official-survey') + let expected = [ + [2100000, 1030000], + [2900000, 1400000], + ] + // Here we should not do any re-projection therefore do an exact match + expect(externalLayers[0].extent).toEqual(expected) + + // Extent from non matching CRS BoundingBox + expect(externalLayers[1].externalLayerId).toBe('Periodic-Tracking') + expected = [ + [2485071.58, 1075346.3], + [2828515.82, 1299941.79], + ] + expect(externalLayers[1].extent.length).toBe(2) + expect(externalLayers[1].extent[0].length).toBe(2) + expect(externalLayers[1].extent[1].length).toBe(2) + expect(externalLayers[1].extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(externalLayers[1].extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(externalLayers[1].extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(externalLayers[1].extent[1][1]).toBeCloseTo(expected[1][1], 1) + }) +}) + +describe('WMSCapabilitiesParser - layer attributes', () => { + it('Parse layer url attribute', () => { + // URL from origin + let content = ` + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.baseURL).toBe('https://wms.geo.admin.ch/') + + // URL from Capability + content = ` + + + + + text/xml + + + + + + + + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.baseURL).toBe('https://wms.geo.admin.ch/map?') + }) +}) + +describe('WMSCapabilitiesParser - attributions', () => { + it('Parse layer attribution - no attribution', () => { + let content = ` + + + WMS + WMS BGDI + + + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // No attribution, use Service + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('WMS BGDI') + expect(layer.attributions[0].url).toBeNull() + + // No attribution and Service without Title + content = ` + + + WMS + + + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // Attribution in service + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('wms.geo.admin.ch') + expect(layer.attributions[0].url).toBeNull() + + // No attribution and no service + content = ` + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // Attribution in service + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('wms.geo.admin.ch') + expect(layer.attributions[0].url).toBeNull() + }) + + it('Parse layer attribution - attribution in root layer', () => { + const content = ` + + + WMS + WMS BGDI + + + + + + WMS BGDI + + The federal geoportal + + + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + const capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + + const layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('The federal geoportal') + expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution') + }) + + it('Parse layer attribution - attribution in layer', () => { + let content = ` + + + WMS + WMS BGDI + + + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + BGDI Layer + + + image/png + + + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // Attribution in layer + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('BGDI Layer') + expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution-bgdi') + + // Attribution without title in layer + content = ` + + + WMS + WMS BGDI + + + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + + + + ` + capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('www.geo.admin.ch') + expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution-bgdi') + }) +}) + +describe('WMSCapabilitiesParser - layer extent', () => { + it('Parse layer layer extent from EX_GeographicBoundingBox', () => { + const content = ` + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + 5.96 + 10.49 + 45.82 + 47.81 + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // LV95 + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', LV95) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + let expected = [ + [2485071.58, 1075346.3], + [2828515.82, 1299941.79], + ] + expect(layer.extent.length).toBe(2) + expect(layer.extent[0].length).toBe(2) + expect(layer.extent[1].length).toBe(2) + expect(layer.extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(layer.extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(layer.extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(layer.extent[1][1]).toBeCloseTo(expected[1][1], 1) + + // WGS84 + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + expected = [ + [5.96, 45.82], + [10.49, 47.81], + ] + // No re-projection expected therefore do an exact match + expect(layer.extent).toEqual(expected) + + // Web mercator + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WEBMERCATOR) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + expected = [ + [663464.17, 5751550.86], + [1167741.46, 6075303.61], + ] + expect(layer.extent.length).toBe(2) + expect(layer.extent[0].length).toBe(2) + expect(layer.extent[1].length).toBe(2) + expect(layer.extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(layer.extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(layer.extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(layer.extent[1][1]).toBeCloseTo(expected[1][1], 1) + }) + it('Parse layer layer extent from parent layer EX_GeographicBoundingBox', () => { + const content = ` + + + + WMS BGDI + + 5.96 + 10.49 + 45.82 + 47.81 + + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // LV95 + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', LV95) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + let expected = [ + [2485071.58, 1075346.3], + [2828515.82, 1299941.79], + ] + expect(layer.extent.length).toBe(2) + expect(layer.extent[0].length).toBe(2) + expect(layer.extent[1].length).toBe(2) + expect(layer.extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(layer.extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(layer.extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(layer.extent[1][1]).toBeCloseTo(expected[1][1], 1) + + // WGS84 + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + expected = [ + [5.96, 45.82], + [10.49, 47.81], + ] + // No re-projection expected therefore do an exact match + expect(layer.extent).toEqual(expected) + + // Web mercator + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WEBMERCATOR) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.extent).toBeArray() + expected = [ + [663464.17, 5751550.86], + [1167741.46, 6075303.61], + ] + expect(layer.extent.length).toBe(2) + expect(layer.extent[0].length).toBe(2) + expect(layer.extent[1].length).toBe(2) + expect(layer.extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(layer.extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(layer.extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(layer.extent[1][1]).toBeCloseTo(expected[1][1], 1) + }) +}) + +describe('EX_GeographicBoundingBox - Group of layers', () => { + it('Parse group of layers - single hierarchy', () => { + const content = ` + + + + WMS BGDI + + 5.96 + 10.49 + 45.82 + 47.81 + + + ch.swisstopo-vd.official-survey + OpenData-AV + + ch.swisstopo-vd.official-survey-1 + OpenData-AV 1 + + + ch.swisstopo-vd.official-survey-2 + OpenData-AV 2 + + + ch.swisstopo-vd.official-survey-3 + OpenData-AV 3 + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // LV95 + let layers = capabilities.getAllExternalLayerObjects(LV95) + expect(layers.length).toBe(1) + expect(layers[0].externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expect(layers[0]).toBeInstanceOf(ExternalGroupOfLayers) + expect(layers[0].layers.length).toBe(3) + expect(layers[0].layers[0]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[0].externalLayerId).toBe('ch.swisstopo-vd.official-survey-1') + + expect(layers[0].layers[1]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[1].externalLayerId).toBe('ch.swisstopo-vd.official-survey-2') + + expect(layers[0].layers[2]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[2].externalLayerId).toBe('ch.swisstopo-vd.official-survey-3') + }) + it('Parse group of layers - multiple hierarchy', () => { + const content = ` + + + + WMS BGDI + + 5.96 + 10.49 + 45.82 + 47.81 + + + ch.swisstopo-vd.official-survey + OpenData-AV + + ch.swisstopo-vd.official-survey-1 + OpenData-AV 1 + + + ch.swisstopo-vd.official-survey-2 + OpenData-AV 2 + + + ch.swisstopo-vd.official-survey-3 + OpenData-AV 3 + + ch.swisstopo-vd.official-survey-3-sub-1 + OpenData-AV 3.1 + + + ch.swisstopo-vd.official-survey-3-sub-2 + OpenData-AV 3.2 + + ch.swisstopo-vd.official-survey-3-sub-2-1 + OpenData-AV 3.2.1 + + + ch.swisstopo-vd.official-survey-3-sub-2-2 + OpenData-AV 3.2.2 + + + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // LV95 + let layers = capabilities.getAllExternalLayerObjects(LV95) + expect(layers.length).toBe(1) + expect(layers[0].externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expect(layers[0]).toBeInstanceOf(ExternalGroupOfLayers) + expect(layers[0].layers.length).toBe(3) + expect(layers[0].layers[0]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[0].externalLayerId).toBe('ch.swisstopo-vd.official-survey-1') + + expect(layers[0].layers[1]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[1].externalLayerId).toBe('ch.swisstopo-vd.official-survey-2') + + expect(layers[0].layers[2]).toBeInstanceOf(ExternalGroupOfLayers) + expect(layers[0].layers[2].externalLayerId).toBe('ch.swisstopo-vd.official-survey-3') + + expect(layers[0].layers[2].layers[0]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[2].layers[0].externalLayerId).toBe( + 'ch.swisstopo-vd.official-survey-3-sub-1' + ) + + expect(layers[0].layers[2].layers[1]).toBeInstanceOf(ExternalGroupOfLayers) + expect(layers[0].layers[2].layers[1].externalLayerId).toBe( + 'ch.swisstopo-vd.official-survey-3-sub-2' + ) + + expect(layers[0].layers[2].layers[1].layers[0]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[2].layers[1].layers[0].externalLayerId).toBe( + 'ch.swisstopo-vd.official-survey-3-sub-2-1' + ) + + expect(layers[0].layers[2].layers[1].layers[1]).toBeInstanceOf(ExternalWMSLayer) + expect(layers[0].layers[2].layers[1].layers[1].externalLayerId).toBe( + 'ch.swisstopo-vd.official-survey-3-sub-2-2' + ) + }) + it('Search layer in multiple hierarchy', () => { + const content = ` + + + + WMS BGDI + + 5.96 + 10.49 + 45.82 + 47.81 + + + ch.swisstopo-vd.official-survey + OpenData-AV + + ch.swisstopo-vd.official-survey-1 + OpenData-AV 1 + + + ch.swisstopo-vd.official-survey-2 + OpenData-AV 2 + + + ch.swisstopo-vd.official-survey-3 + OpenData-AV 3 + + ch.swisstopo-vd.official-survey-3-sub-1 + OpenData-AV 3.1 + + + ch.swisstopo-vd.official-survey-3-sub-2 + OpenData-AV 3.2 + + ch.swisstopo-vd.official-survey-3-sub-2-1 + OpenData-AV 3.2.1 + + + OpenData-AV 3.2.2 + + + ch.swisstopo-vd.official-survey-3-sub-2-3 + OpenData-AV 3.2.3 + + + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + + // search root layer + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', LV95) + expect(layer).not.toBeNull() + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + + // search first hierarchy layer + layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey-2', LV95) + expect(layer).not.toBeNull() + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey-2') + + // Search sublayer + layer = capabilities.getExternalLayerObject( + 'ch.swisstopo-vd.official-survey-3-sub-2-1', + LV95 + ) + expect(layer).not.toBeNull() + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey-3-sub-2-1') + + // Search sublayer without name + layer = capabilities.getExternalLayerObject('OpenData-AV 3.2.2', LV95) + expect(layer).not.toBeNull() + expect(layer.externalLayerId).toBe('OpenData-AV 3.2.2') + }) +}) diff --git a/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js b/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js new file mode 100644 index 000000000..68f97b4ad --- /dev/null +++ b/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js @@ -0,0 +1,223 @@ +import WMTSCapabilitiesParser from '../WMTSCapabilitiesParser.class' +import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' + +import { readFile } from 'fs/promises' +import { describe, it, expect, beforeAll, expectTypeOf } from 'vitest' + +describe('WMTSCapabilitiesParser of wmts-ogc-sample.xml', () => { + let capabilities + beforeAll(async () => { + const content = await readFile(`${__dirname}/wmts-ogc-sample.xml`, 'utf8') + capabilities = new WMTSCapabilitiesParser(content, 'https://example.com') + }) + it('Throw Error on invalid input', () => { + const invalidContent = 'Invalid input' + + expect( + () => new WMTSCapabilitiesParser(invalidContent, 'https://example.com') + ).toThrowError(/failed/i) + }) + it('Parse Capabilities', async () => { + expect(capabilities.version).toBe('1.0.0') + expect(capabilities.Contents).toBeTypeOf('object') + expect(capabilities.OperationsMetadata).toBeTypeOf('object') + expect(capabilities.ServiceIdentification).toBeTypeOf('object') + expect(capabilities.ServiceProvider).toBeTypeOf('object') + expect(capabilities.originUrl).toBeInstanceOf(URL) + expect(capabilities.originUrl.toString()).toBe('https://example.com/') + }) + it('Parse layer attributes', () => { + // General layer + let layer = capabilities.getExternalLayerObject('BlueMarbleSecondGenerationAG', WGS84) + expect(layer.externalLayerId).toBe('BlueMarbleSecondGenerationAG') + expect(layer.name).toBe('Blue Marble Second Generation - AG') + expect(layer.abstract).toBe('Blue Marble Second Generation Canton Aargau Product') + expect(layer.baseURL).toBe('http://maps.example.com/cgi-bin/map.cgi?') + + // Layer without .Identifier + layer = capabilities.getExternalLayerObject('BlueMarbleThirdGenerationZH', WGS84) + expect(layer.externalLayerId).toBe('BlueMarbleThirdGenerationZH') + expect(layer.name).toBe('BlueMarbleThirdGenerationZH') + expect(layer.abstract).toBe('Blue Marble Third Generation Canton Zürich Product') + expect(layer.baseURL).toBe('http://maps.example.com/cgi-bin/map.cgi?') + }) + it('Parse layer attribution', () => { + // General layer + let layer = capabilities.getExternalLayerObject('BlueMarbleSecondGenerationAG', WGS84) + expect(layer.externalLayerId).toBe('BlueMarbleSecondGenerationAG') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('Example') + expect(layer.attributions[0].url).toBe('http://www.example.com') + }) + it('Get Layer Extent in LV95', () => { + const externalLayers = capabilities.getAllExternalLayerObjects(LV95) + // Extent from WGS84BoundingBox + expect(externalLayers[0].externalLayerId).toBe('BlueMarbleNextGenerationCH') + let expected = [ + [2485071.58, 1075346.31], + [2828515.82, 1299941.79], + ] + expect(externalLayers[0].extent.length).toBe(2) + expect(externalLayers[0].extent[0].length).toBe(2) + expect(externalLayers[0].extent[1].length).toBe(2) + expect(externalLayers[0].extent[0][0]).toBeCloseTo(expected[0][0], 2) + expect(externalLayers[0].extent[0][1]).toBeCloseTo(expected[0][1], 2) + expect(externalLayers[0].extent[1][0]).toBeCloseTo(expected[1][0], 2) + expect(externalLayers[0].extent[1][1]).toBeCloseTo(expected[1][1], 2) + + // Extent from BoundingBox in WGS84 + expect(externalLayers[1].externalLayerId).toBe('BlueMarbleSecondGenerationAG') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expected = [ + // [2627438.37, 1215506.64], + // [2677504.99, 1277102.76], + // ] + // expect(externalLayers[1].extent.length).toBe(2) + // expect(externalLayers[1].extent[0].length).toBe(2) + // expect(externalLayers[1].extent[1].length).toBe(2) + // expect(externalLayers[1].extent[0][0]).toBeCloseTo(expected[0][0], 2) + // expect(externalLayers[1].extent[0][1]).toBeCloseTo(expected[0][1], 2) + // expect(externalLayers[1].extent[1][0]).toBeCloseTo(expected[1][0], 2) + // expect(externalLayers[1].extent[1][1]).toBeCloseTo(expected[1][1], 2) + + // Extent from BoundingBox without CRS + expect(externalLayers[2].externalLayerId).toBe('BlueMarbleThirdGenerationZH') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expected = [ + // [2665255.25, 1229142.44], + // [2720879.67, 1287842.18], + // ] + // expect(externalLayers[2].extent.length).toBe(2) + // expect(externalLayers[2].extent[0].length).toBe(2) + // expect(externalLayers[2].extent[1].length).toBe(2) + // expect(externalLayers[2].extent[0][0]).toBeCloseTo(expected[0][0], 2) + // expect(externalLayers[2].extent[0][1]).toBeCloseTo(expected[0][1], 2) + // expect(externalLayers[2].extent[1][0]).toBeCloseTo(expected[1][0], 2) + // expect(externalLayers[2].extent[1][1]).toBeCloseTo(expected[1][1], 2) + + // Extent from the TileMatrixSet + expect(externalLayers[3].externalLayerId).toBe('BlueMarbleFourthGenerationJU') + expected = [ + [2552296.05, 1218970.79], + [2609136.96, 1266593.74], + ] + expect(externalLayers[3].extent.length).toBe(2) + expect(externalLayers[3].extent[0].length).toBe(2) + expect(externalLayers[3].extent[1].length).toBe(2) + expect(externalLayers[3].extent[0][0]).toBeCloseTo(expected[0][0], 2) + expect(externalLayers[3].extent[0][1]).toBeCloseTo(expected[0][1], 2) + expect(externalLayers[3].extent[1][0]).toBeCloseTo(expected[1][0], 2) + expect(externalLayers[3].extent[1][1]).toBeCloseTo(expected[1][1], 2) + + // Extent from matching BoundingBox + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expect(externalLayers[4].externalLayerId).toBe('BlueMarbleFifthGenerationGE') + // expect(externalLayers[4].extent).toEqual([ + // [2484928.06, 1108705.32], + // [2514614.27, 1130449.26], + // ]) + }) + + it('Get Layer Extent in Web Mercator', () => { + const externalLayers = capabilities.getAllExternalLayerObjects(WEBMERCATOR) + // Extent from WGS84BoundingBox + expect(externalLayers[0].externalLayerId).toBe('BlueMarbleNextGenerationCH') + let expected = [ + [663464.16, 5751550.86], + [1167741.46, 6075303.61], + ] + expect(externalLayers[0].extent.length).toBe(2) + expect(externalLayers[0].extent[0].length).toBe(2) + expect(externalLayers[0].extent[1].length).toBe(2) + expect(externalLayers[0].extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(externalLayers[0].extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(externalLayers[0].extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(externalLayers[0].extent[1][1]).toBeCloseTo(expected[1][1], 1) + + // Extent from BoundingBox in WGS84 + expect(externalLayers[1].externalLayerId).toBe('BlueMarbleSecondGenerationAG') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expected = [ + // [868292.03, 5956776.76], + // [942876.09, 6047171.27], + // ] + // expect(externalLayers[1].extent.length).toBe(2) + // expect(externalLayers[1].extent[0].length).toBe(2) + // expect(externalLayers[1].extent[1].length).toBe(2) + // expect(externalLayers[1].extent[0][0]).toBeCloseTo(expected[0][0], 1) + // expect(externalLayers[1].extent[0][1]).toBeCloseTo(expected[0][1], 1) + // expect(externalLayers[1].extent[1][0]).toBeCloseTo(expected[1][0], 1) + // expect(externalLayers[1].extent[1][1]).toBeCloseTo(expected[1][1], 1) + + // Extent from BoundingBox without CRS + expect(externalLayers[2].externalLayerId).toBe('BlueMarbleThirdGenerationZH') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expected = [ + // [923951.77, 5976419.03], + // [1007441.39, 6062053.42], + // ] + // expect(externalLayers[2].extent.length).toBe(2) + // expect(externalLayers[2].extent[0].length).toBe(2) + // expect(externalLayers[2].extent[1].length).toBe(2) + // expect(externalLayers[2].extent[0][0]).toBeCloseTo(expected[0][0], 1) + // expect(externalLayers[2].extent[0][1]).toBeCloseTo(expected[0][1], 1) + // expect(externalLayers[2].extent[1][0]).toBeCloseTo(expected[1][0], 1) + // expect(externalLayers[2].extent[1][1]).toBeCloseTo(expected[1][1], 1) + + // Extent from the TileMatrixSet + expect(externalLayers[3].externalLayerId).toBe('BlueMarbleFourthGenerationJU') + expected = [ + [758085.73, 5961683.17], + [841575.35, 6032314.73], + ] + expect(externalLayers[3].extent.length).toBe(2) + expect(externalLayers[3].extent[0].length).toBe(2) + expect(externalLayers[3].extent[1].length).toBe(2) + expect(externalLayers[3].extent[0][0]).toBeCloseTo(expected[0][0], 1) + expect(externalLayers[3].extent[0][1]).toBeCloseTo(expected[0][1], 1) + expect(externalLayers[3].extent[1][0]).toBeCloseTo(expected[1][0], 1) + expect(externalLayers[3].extent[1][1]).toBeCloseTo(expected[1][1], 1) + }) + + it('Get Layer Extent in WGS84', () => { + const externalLayers = capabilities.getAllExternalLayerObjects(WGS84) + // Extent from WGS84BoundingBox + expect(externalLayers[0].externalLayerId).toBe('BlueMarbleNextGenerationCH') + expect(externalLayers[0].extent).toEqual([ + [5.96, 45.82], + [10.49, 47.81], + ]) + + // Extent from BoundingBox in WGS84 + expect(externalLayers[1].externalLayerId).toBe('BlueMarbleSecondGenerationAG') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expect(externalLayers[1].extent).toEqual([ + // [7.8, 47.09], + // [8.47, 47.64], + // ]) + + // Extent from BoundingBox without CRS + expect(externalLayers[2].externalLayerId).toBe('BlueMarbleThirdGenerationZH') + // TODO: uncomment this test when the following openlayer bug has been released + // https://github.com/openlayers/openlayers/issues/15363 + // expect(externalLayers[2].extent).toEqual([ + // [8.30, 47.21], + // [9.05, 47.73], + // ]) + + // Extent from the TileMatrixSet + expect(externalLayers[3].externalLayerId).toBe('BlueMarbleFourthGenerationJU') + expect(externalLayers[3].extent).toEqual([ + [6.81, 47.12], + [7.56, 47.55], + ]) + }) +}) diff --git a/src/api/layers/__tests__/wms-geoadmin-sample.xml b/src/api/layers/__tests__/wms-geoadmin-sample.xml new file mode 100644 index 000000000..37c96c25a --- /dev/null +++ b/src/api/layers/__tests__/wms-geoadmin-sample.xml @@ -0,0 +1,417 @@ + + + + + WMS + WMS BGDI + Öffentliche Daten der Bundes Geodaten-Infrastruktur (BGDI) + + BGDI Geodaten + + + + + webgis@swisstopo.ch + Bundesamt für Landestopografie swisstopo + + + text/html +
Seftigenstrasse 264
+ Wabern + Kanton Bern + 3084 + Schweiz +
+ +41 (0)58 / 000 00 00 + +41 (0)58 / 000 00 01 +
+ none + 10000 + 10000 +
+ + + + + text/xml + + + + + + + + + + + + + image/jpeg + image/png + image/pnga + image/png; mode=32bit + image/png; mode=8bit + image/tiff + + + + + + + + + + + + + application/json + application/json; subtype=geojson + application/vnd.ogc.gml + text/plain + text/xml + text/xml; subtype=gml/3.1.1 + text/xml; subtype=gml/3.2.1 + + + + + + + + + + + + + text/xml + + + + + + + + + + + + + image/png + image/jpeg + + + + + + + + + + + + + text/xml + + + + + + + + + + + + + + XML + INIMAGE + BLANK + + + + WMS BGDI + Öffentliche Daten der Bundes Geodaten-Infrastruktur (BGDI) + + BGDI Geodaten + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 0.659866 + 11.514 + 45.2401 + 48.7511 + + + + The federal geoportal + + + image/png + + + + + ch.swisstopo-vd.official-survey + OpenData-AV + The official survey (AV). + + ch.swisstopo-vd.official-survey.wms_ows_keywordlist + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 0.659866 + 11.514 + 45.2401 + 48.7511 + + + + text/xml + + + + + + Periodic-Tracking + Layer without Name element should use the Title + + ch.swisstopo-vd.geometa-periodische_nachfuehrung.wms_ows_keywordlist + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 5.96 + 10.49 + 45.82 + 47.81 + + + + BGDI + + + image/png + + + + + text/xml + + + + + + ch.swisstopo-vd.ortschaftenverzeichnis_plz + PLZ und Ortschaften + Mit Artikel 24 der Verordnung über die geografischen Namen (GeoNV) wurde das Bundesamt für Landestopografie swisstopo beauftragt, das neue amtliche Ortschaftenverzeichnis mit Postleitzahl und Perimeter zu erstellen, zu verwalten und zu veröffentlichen. Dieser Datensatz wird zentral bei swisstopo geführt und entspricht mit Ausnahme des entfernten Identifikators `IDENT PLZ, Zusatzziffern` in der Tabelle `PLZ` dem TOPIC `PLZOrtschaft` der amtlichen Vermessung. Der Datensatz ist flächendeckend über die ganze Schweiz und kann kostenlos bezogen werden (Datensatz wird monatlich nachgeführt). + + ch.swisstopo-vd.ortschaftenverzeichnis_plz.wms_ows_keywordlist + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 0.659866 + 11.514 + 45.2401 + 48.7511 + + + + text/xml + + + + + + ch.swisstopo-vd.spannungsarme-gebiete + Spannungsarme Gebiete + Bei Feldarbeiten in der amtlichen Vermessung muss jeweils eine lokale Einpassung durchgeführt werden oder zumindest der Nachweis erbracht werden, dass auf eine solche verzichtet werden kann. In spannungsarmen Gebieten erübrigt sich eine lokale Einpassung, weil die geometrische Genauigkeit erhöhten Qualitätskriterien entspricht. In der praktischen Anwendung erleichtert die Kenntnis solcher spannungsarmen Gebiete die Arbeiten mit satellitengestützten Messmethoden, insbesondere mit Positionierungsdiensten wie zum Beispiel swipos. + + ch.swisstopo-vd.spannungsarme-gebiete.wms_ows_keywordlist + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 0.659866 + 11.514 + 45.2401 + 48.7511 + + + + text/xml + + + + + + ch.swisstopo-vd.stand-oerebkataster + Verfügbarkeit des ÖREB-Katasters + Der Datensatz zeigt pro Gemeinde, ob der Kataster der öffentlich-rechtlichen Eigentumsbeschränkungen (ÖREB-Kataster) verfügbar ist, ermöglicht den direkten Link zum kantonalen Geodatenportal sowie die direkte Bestellung des statischen Auszugs (PDF) zum ausgewählten Grundstück via WebService. Ausserdem finden Sie den Namen und die Adresse der zuständigen kantonalen Fachstelle. + + + ch.swisstopo-vd.stand-oerebkataster + Verfügbarkeit des ÖREB-Katasters + Der Datensatz zeigt pro Gemeinde, ob der Kataster der öffentlich-rechtlichen Eigentumsbeschränkungen (ÖREB-Kataster) verfügbar ist, ermöglicht den direkten Link zum kantonalen Geodatenportal sowie die direkte Bestellung des statischen Auszugs (PDF) zum ausgewählten Grundstück via WebService. Ausserdem finden Sie den Namen und die Adresse der zuständigen kantonalen Fachstelle. + + ch.swisstopo-vd.stand-oerebkataster.wms_ows_keywordlist + + EPSG:2056 + EPSG:21781 + EPSG:4326 + EPSG:3857 + EPSG:3034 + EPSG:3035 + EPSG:4258 + EPSG:25832 + EPSG:25833 + EPSG:31467 + EPSG:32632 + EPSG:32633 + EPSG:900913 + + 0.659866 + 11.514 + 45.2401 + 48.7511 + + + + text/xml + + + + + + +
diff --git a/src/api/layers/__tests__/wmts-ogc-sample.xml b/src/api/layers/__tests__/wmts-ogc-sample.xml new file mode 100644 index 000000000..0cb809b38 --- /dev/null +++ b/src/api/layers/__tests__/wmts-ogc-sample.xml @@ -0,0 +1,641 @@ + + + + Web Map Tile Service + Service that constrains the map + access interface to some TileMatrixSets + + tile + tile matrix set + map + + OGC WMTS + 1.0.0 + none + none + + + Example + + + John Doe + Senior Software Engineer + + + +00 00 000 0000 + +00 00 000 0001 + + + Mainstreet + Bern + Bern + 3000 + Switzerland + john.doe@example.com + + + + + + + + + + + + KVP + SOAP + + + + + + + + + + + + + KVP + + + + + + + + + + Blue Marble Next Generation in Switzerland + Blue Marble Next Generation NASA Product + + + 5.96 45.82 + 10.49 47.81 + + BlueMarbleNextGenerationCH + + + image/jpeg + image/gif + + BigWorldPixel + + + google3857 + + + google3857subset + + + + + Time + 20110805 + 20110805 + 20081024 + + + + Blue Marble Second Generation - AG + Blue Marble Second Generation Canton Aargau Product + + 7.8 47.09 + 8.47 47.64 + + BlueMarbleSecondGenerationAG + + + image/jpeg + image/gif + + BigWorldPixel + + + google3857 + + + google3857subset + + + + + Time + 20110805 + 20110805 + 20081024 + + + + BlueMarbleThirdGenerationZH + Blue Marble Third Generation Canton Zürich Product + + 8.30 47.21 + 9.05 47.73 + + + + image/jpeg + image/gif + + BigWorldPixel + + + + + Time + 20110805 + 20110805 + 20081024 + + + + Blue Marble Fourth Generation - JU + Blue Marble Fourth Generation Canton Jura Product + BlueMarbleFourthGenerationJU + + + image/jpeg + image/gif + + JURA + + + + + Time + 20110805 + 20110805 + 20081024 + + + + Blue Marble Fifth Generation - GE + Blue Marble Fifth Generation Canton Geneva Product + + 5.95 46.12 + 6.33 46.32 + + + 662350.97 5799600.20 + 704652.38 5831778.58 + + + 2484928.06 1108705.32 + 2514614.27 1130449.26 + + BlueMarbleFifthGenerationGE + + + image/jpeg + image/gif + + BigWorldPixel + + + google3857 + + + google3857subset + + + + + Time + 20110805 + 20110805 + 20081024 + + + + + google3857 + + 1799448.394855 6124949.747770 + 1848250.442089 6162571.828177 + + urn:ogc:def:crs:EPSG:6.18:3:3857 + urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible + + 0 + 559082264.029 + -20037508.3428 20037508.3428 + 256 + 256 + 1 + 1 + + + 1 + 279541132.015 + -20037508.3428 20037508.3428 + 256 + 256 + 2 + 2 + + + 2 + 139770566.007 + -20037508.3428 20037508.3428 + 256 + 256 + 4 + 4 + + + 3 + 69885283.0036 + -20037508.3428 20037508.3428 + 256 + 256 + 8 + 8 + + + 4 + 34942641.5018 + -20037508.3428 20037508.3428 + 256 + 256 + 16 + 16 + + + 5 + 17471320.7509 + -20037508.3428 20037508.3428 + 256 + 256 + 32 + 32 + + + 6 + 8735660.37545 + -20037508.3428 20037508.3428 + 256 + 256 + 64 + 64 + + + 7 + 4367830.18773 + -20037508.3428 20037508.3428 + 256 + 256 + 128 + 128 + + + 8 + 2183915.09386 + -20037508.3428 20037508.3428 + 256 + 256 + 256 + 256 + + + 9 + 1091957.54693 + -20037508.3428 20037508.3428 + 256 + 256 + 512 + 512 + + + 10 + 545978.773466 + -20037508.3428 20037508.3428 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.386733 + -20037508.3428 20037508.3428 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.693366 + -20037508.3428 20037508.3428 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.3466832 + -20037508.3428 20037508.3428 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.6733416 + -20037508.3428 20037508.3428 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.8366708 + -20037508.3428 20037508.3428 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.91833540 + -20037508.3428 20037508.3428 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.45916770 + -20037508.3428 20037508.3428 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.72958385 + -20037508.3428 20037508.3428 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.36479193 + -20037508.3428 20037508.3428 + 256 + 256 + 524288 + 524288 + + + + BigWorldPixel + urn:ogc:def:crs:OGC:1.3:CRS84 + + -180 -65 + 180 65 + + urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Pixel + + 10000m + 33130800.83133142 + -180 90 + 640 + 480 + 7 + 5 + + + 20000m + 66261601.66266284 + -180 90 + 640 + 480 + 4 + 3 + + + 40000m + 132523203.3253257 + -180 90 + 640 + 480 + 2 + 2 + + + 60000m + 198784804.9879885 + -180 90 + 640 + 480 + 1 + 1 + + + 120000m + 397569609.9759771 + -180 90 + 640 + 480 + 1 + 1 + + + 240000m + 795139219.9519541 + -180 90 + 640 + 480 + 1 + 1 + + + + BigWorld + urn:ogc:def:crs:OGC:1.3:CRS84 + + 1e6 + 1e6 + -180 84 + 256 + 256 + 60000 + 50000 + + + 2.5e6 + 2.5e6 + -180 84 + 256 + 256 + 9000 + 7000 + + + + JURA + urn:ogc:def:crs:OGC:1.3:CRS84 + + 6.81 47.12 + 7.56 47.55 + + + 1e6 + 1e6 + -180 84 + 256 + 256 + 60000 + 50000 + + + 2.5e6 + 2.5e6 + -180 84 + 256 + 256 + 9000 + 7000 + + + + + google3857subset + urn:ogc:def:crs:EPSG:6.18:3:3857 + + 18 + 2132.72958385 + -10000000 10000000 + 256 + 256 + 1 + 1 + + + 18 + 1066.36479193 + -10000000 10000000 + 256 + 256 + 2 + 2 + + + + + From 46ef1eafb0878aa44c1e356b7eae4b1f0a4f1d70 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Thu, 30 Nov 2023 09:31:26 +0100 Subject: [PATCH 006/377] BGDIINF_SB-3180: Added WMS attribution test edge case --- .../WMSCapabitliesParser.class.spec.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js index bdaa334c3..064581ab4 100644 --- a/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js +++ b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js @@ -342,6 +342,37 @@ describe('WMSCapabilitiesParser - attributions', () => { expect(layer.attributions[0].name).toBe('www.geo.admin.ch') expect(layer.attributions[0].url).toBe('https://www.geo.admin.ch/attribution-bgdi') }) + + it('Parse layer attribution - invalid attribution URL', () => { + let content = ` + + + + WMS BGDI + + ch.swisstopo-vd.official-survey + OpenData-AV + + + + + + + + ` + let capabilities = new WMSCapabilitiesParser(content, 'https://wms.geo.admin.ch') + // No attribution, use Service + let layer = capabilities.getExternalLayerObject('ch.swisstopo-vd.official-survey', WGS84) + expect(layer.externalLayerId).toBe('ch.swisstopo-vd.official-survey') + expectTypeOf(layer.attributions).toBeArray() + expect(layer.attributions.length).toBe(1) + expectTypeOf(layer.attributions[0]).toEqualTypeOf({ name: 'string', url: 'string' }) + expect(layer.attributions[0].name).toBe('wms.geo.admin.ch') + expect(layer.attributions[0].url).toBeNull() + }) }) describe('WMSCapabilitiesParser - layer extent', () => { From 6a75c63f6daa90b40c188fe4a301c930d5757f5b Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Thu, 30 Nov 2023 07:49:18 +0100 Subject: [PATCH 007/377] BGDIINF_SB-3194: Moved LayerCatalogue out of topics Because this component will be reused by the import tool move it out of topics. --- src/modules/menu/components/{topics => }/LayerCatalogue.vue | 2 +- src/modules/menu/components/{topics => }/LayerCatalogueItem.vue | 0 src/modules/menu/components/topics/MenuTopicSection.vue | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/modules/menu/components/{topics => }/LayerCatalogue.vue (89%) rename src/modules/menu/components/{topics => }/LayerCatalogueItem.vue (100%) diff --git a/src/modules/menu/components/topics/LayerCatalogue.vue b/src/modules/menu/components/LayerCatalogue.vue similarity index 89% rename from src/modules/menu/components/topics/LayerCatalogue.vue rename to src/modules/menu/components/LayerCatalogue.vue index 987d98806..5783df506 100644 --- a/src/modules/menu/components/topics/LayerCatalogue.vue +++ b/src/modules/menu/components/LayerCatalogue.vue @@ -1,5 +1,5 @@ + + diff --git a/src/modules/drawing/components/DrawingToolbox.vue b/src/modules/drawing/components/DrawingToolbox.vue index 84270684d..a68c2e958 100644 --- a/src/modules/drawing/components/DrawingToolbox.vue +++ b/src/modules/drawing/components/DrawingToolbox.vue @@ -122,6 +122,7 @@ import DrawingToolboxButton from '@/modules/drawing/components/DrawingToolboxBut import SharePopup from '@/modules/drawing/components/SharePopup.vue' import ModalWithBackdrop from '@/utils/ModalWithBackdrop.vue' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import { useI18n } from 'vue-i18n' import { mapGetters } from 'vuex' import { DrawingState } from '../lib/export-utils' import DrawingHeader from './DrawingHeader.vue' @@ -158,6 +159,12 @@ export default { }, }, emits: ['close', 'setDrawingMode', 'export', 'clearDrawing', 'deleteLastPoint'], + setup() { + const i18n = useI18n() + return { + i18n, + } + }, data() { return { drawingModes: Object.values(EditableFeatureTypes), @@ -180,13 +187,13 @@ export default { drawingStateMessage() { switch (this.drawingState) { case DrawingState.SAVING: - return this.$i18n.t('draw_file_saving') + return this.i18n.t('draw_file_saving') case DrawingState.SAVED: - return this.$i18n.t('draw_file_saved') + return this.i18n.t('draw_file_saved') case DrawingState.SAVE_ERROR: - return this.$i18n.t('draw_file_load_error') + return this.i18n.t('draw_file_load_error') case DrawingState.LOAD_ERROR: - return this.$i18n.t('draw_file_save_error') + return this.i18n.t('draw_file_save_error') default: return null } diff --git a/src/modules/drawing/components/DrawingTooltip.vue b/src/modules/drawing/components/DrawingTooltip.vue index 0f71017b1..08034cc4c 100644 --- a/src/modules/drawing/components/DrawingTooltip.vue +++ b/src/modules/drawing/components/DrawingTooltip.vue @@ -9,6 +9,7 @@ import { EditableFeatureTypes } from '@/api/features.api' import { DRAWING_HIT_TOLERANCE } from '@/config' import { getVertexCoordinates, pointWithinTolerance } from '@/modules/drawing/lib/drawingUtils' import Overlay from 'ol/Overlay' +import { useI18n } from 'vue-i18n' const cssPointer = 'cursor-pointer' const cssGrab = 'cursor-grab' @@ -30,6 +31,12 @@ export default { default: null, }, }, + setup() { + const i18n = useI18n() + return { + i18n, + } + }, data() { return { tooltipText: '', @@ -66,7 +73,7 @@ export default { this.tooltipText = translationKeys .map((key) => key.toLowerCase()) - .map((key) => this.$i18n.t(key)) + .map((key) => this.i18n.t(key)) .join('
') }, onPointerMove(event) { diff --git a/src/modules/i18n/README.md b/src/modules/i18n/README.md index c59304c72..66a4a027c 100644 --- a/src/modules/i18n/README.md +++ b/src/modules/i18n/README.md @@ -1,25 +1,44 @@ # Internationalization (i18n) module -Responsible for loading and serving `vue-i18n` through the `$t` function in templates. +Responsible for loading and serving `vue-i18n`. This utils can be accessed by linking the result of `useI18n()` +to a local ref (in Composition API) or in-place with the Option API. As we've deactivated the legacy support, +it's not possible to use `this.$i18n` anymore, we must now go through the `useI18n()` function to +get a reference to the utils. -Here's an example how to use this translation : +Here's an example of how to use this translation : + +```javascript +import { useI18n } from 'vue-i18n' + +const i18n = useI18n() +``` ```html - + ``` -Current locale can be accessed through +Current locale can be accessed through the store + +```javascript +import { useStore } from 'vuex' +import { computed } from 'vue' + +const store = useStore() +const currentLocal = computed(() => store.state.i18n.lang) +``` ```html -Current locale is {{ $i18n.locale }} +Current locale is {{ currentLocal }} ``` -Within your Vue Component javascript code you can access translation like this +Within your Option API Vue Component javascript code, you can access translation like this ```javascript -this.$i18n.t('a_translation_key') +useI18n().t('a_translation_key') ``` +Or if you have multiple call to `t(...)`, you can store the reference given by `useI18n()` at some point (do not store it in `data()`) + ## update translations See [the main README.md's section on that](../../../README.md#tooling-for-translation-update) diff --git a/src/modules/i18n/index.js b/src/modules/i18n/index.js index 61f9dd1d4..4f4ded42f 100644 --- a/src/modules/i18n/index.js +++ b/src/modules/i18n/index.js @@ -19,6 +19,7 @@ if (navigator.languages) { const i18n = createI18n({ locale: matchedLanguage || 'en', // default locale messages: languages, + legacy: false, }) export default i18n diff --git a/src/modules/infobox/components/ImportContent.vue b/src/modules/infobox/components/ImportContent.vue index 51fbdb1f7..a67e36245 100644 --- a/src/modules/infobox/components/ImportContent.vue +++ b/src/modules/infobox/components/ImportContent.vue @@ -182,27 +182,34 @@ diff --git a/src/modules/map/components/openlayers/OpenLayersBackgroundLayer.vue b/src/modules/map/components/openlayers/OpenLayersBackgroundLayer.vue new file mode 100644 index 000000000..acd9ef380 --- /dev/null +++ b/src/modules/map/components/openlayers/OpenLayersBackgroundLayer.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/modules/map/components/openlayers/OpenLayersCrossHair.vue b/src/modules/map/components/openlayers/OpenLayersCrossHair.vue new file mode 100644 index 000000000..264b77eea --- /dev/null +++ b/src/modules/map/components/openlayers/OpenLayersCrossHair.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/modules/map/components/openlayers/OpenLayersExternalWMTSLayer.vue b/src/modules/map/components/openlayers/OpenLayersExternalWMTSLayer.vue index 42106bbe3..6743702ab 100644 --- a/src/modules/map/components/openlayers/OpenLayersExternalWMTSLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersExternalWMTSLayer.vue @@ -1,90 +1,78 @@ - + diff --git a/src/modules/map/components/openlayers/OpenLayersGeoJSONLayer.vue b/src/modules/map/components/openlayers/OpenLayersGeoJSONLayer.vue index a36d8772d..5c32413a1 100644 --- a/src/modules/map/components/openlayers/OpenLayersGeoJSONLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersGeoJSONLayer.vue @@ -1,149 +1,79 @@ - + + + diff --git a/src/modules/map/components/openlayers/OpenLayersGeolocationFeedback.vue b/src/modules/map/components/openlayers/OpenLayersGeolocationFeedback.vue new file mode 100644 index 000000000..883348fa0 --- /dev/null +++ b/src/modules/map/components/openlayers/OpenLayersGeolocationFeedback.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/modules/map/components/openlayers/OpenLayersHighlightedFeature.vue b/src/modules/map/components/openlayers/OpenLayersHighlightedFeature.vue deleted file mode 100644 index 66c69fab8..000000000 --- a/src/modules/map/components/openlayers/OpenLayersHighlightedFeature.vue +++ /dev/null @@ -1,120 +0,0 @@ - - - diff --git a/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue b/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue new file mode 100644 index 000000000..be6f21d96 --- /dev/null +++ b/src/modules/map/components/openlayers/OpenLayersHighlightedFeatures.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue b/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue index 8c553b4aa..219225105 100644 --- a/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersInternalLayer.vue @@ -1,3 +1,45 @@ + + - - diff --git a/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue b/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue index a3a3cf189..217a2323a 100644 --- a/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue +++ b/src/modules/map/components/openlayers/OpenLayersKMLLayer.vue @@ -1,118 +1,101 @@ - + diff --git a/src/modules/map/components/openlayers/OpenLayersMap.vue b/src/modules/map/components/openlayers/OpenLayersMap.vue index 91ef40d6b..f058f4c6d 100644 --- a/src/modules/map/components/openlayers/OpenLayersMap.vue +++ b/src/modules/map/components/openlayers/OpenLayersMap.vue @@ -1,555 +1,67 @@ - - - + + diff --git a/src/modules/map/components/cesium/CesiumMap.vue b/src/modules/map/components/cesium/CesiumMap.vue index c9c732fae..9f35f94a0 100644 --- a/src/modules/map/components/cesium/CesiumMap.vue +++ b/src/modules/map/components/cesium/CesiumMap.vue @@ -91,7 +91,7 @@ import { ClickInfo, ClickType } from '@/store/modules/map.store' import { UIModes } from '@/store/modules/ui.store' import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import CustomCoordinateSystem from '@/utils/coordinates/CustomCoordinateSystem.class' -import { createGeoJSONFeature } from '@/utils/layerUtils' +import { identifyGeoJSONFeatureAt } from '@/utils/identifyOnVectorLayer' import log from '@/utils/logging' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import '@geoblocks/cesium-compass' @@ -461,25 +461,19 @@ export default { ) let objects = this.viewer.scene.drillPick(event.position) - const geoJsonFeatures = {} const kmlFeatures = {} // if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor this.visiblePrimitiveLayers .filter((l) => l instanceof GeoAdminGeoJsonLayer) .forEach((geoJSonLayer) => { - objects - .filter((obj) => obj.primitive?.olLayer?.get('id') === geoJSonLayer.getID()) - .forEach((obj) => { - const feature = obj.primitive.olFeature - if (!geoJsonFeatures[feature.getId()]) { - geoJsonFeatures[feature.getId()] = createGeoJSONFeature( - obj.primitive.olFeature, - geoJSonLayer, - feature.getGeometry() - ) - } - }) - features.push(...Object.values(geoJsonFeatures)) + features.push( + ...identifyGeoJSONFeatureAt( + geoJSonLayer, + event.position, + this.projection, + this.resolution + ) + ) }) this.visiblePrimitiveLayers .filter((l) => l instanceof KMLLayer) diff --git a/src/modules/map/components/common/mouse-click.composable.js b/src/modules/map/components/common/mouse-click.composable.js index 27e07b3d6..75ed61de1 100644 --- a/src/modules/map/components/common/mouse-click.composable.js +++ b/src/modules/map/components/common/mouse-click.composable.js @@ -1,5 +1,6 @@ import LayerTypes from '@/api/layers/LayerTypes.enum' import { ClickInfo, ClickType } from '@/store/modules/map.store' +import { identifyGeoJSONFeatureAt, identifyKMLFeatureAt } from '@/utils/identifyOnVectorLayer' import { computed } from 'vue' import { useStore } from 'vuex' @@ -18,6 +19,8 @@ export function useMouseOnMap() { const visibleKMLLayers = computed(() => store.getters.visibleLayers.filter((layer) => layer.type === LayerTypes.KML) ) + const currentMapResolution = computed(() => store.getters.resolution) + const currentProjection = computed(() => store.state.position.projection) /** * @param {[Number, Number]} screenPosition @@ -42,12 +45,26 @@ export function useMouseOnMap() { if (!hasPointerDownTriggeredLocationPopup && isStillOnStartingPosition) { const features = [] // if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor - visibleGeoJsonLayers.value.forEach((_geoJSonLayer) => { - // TODO: implements OpenLayers-free feature identification + visibleGeoJsonLayers.value.forEach((geoJSonLayer) => { + features.push( + ...identifyGeoJSONFeatureAt( + geoJSonLayer, + coordinate, + currentProjection.value, + currentMapResolution.value + ) + ) }) // same for KML layers - visibleKMLLayers.value.forEach((_kmlLayer) => { - // TODO: implements OpenLayers-free feature identification + visibleKMLLayers.value.forEach((kmlLayer) => { + features.push( + ...identifyKMLFeatureAt( + kmlLayer.kmlData, + coordinate, + currentProjection.value, + currentMapResolution.value + ) + ) }) store.dispatch( 'click', diff --git a/src/store/modules/features.store.js b/src/store/modules/features.store.js index a6259c62b..83a07ad9e 100644 --- a/src/store/modules/features.store.js +++ b/src/store/modules/features.store.js @@ -7,7 +7,7 @@ const getSelectedFeatureWithId = (state, featureId) => { export default { state: { - /** @type Array */ + /** @type Array */ selectedFeatures: [], }, getters: { @@ -22,7 +22,8 @@ export default { * tells the store which features are selected (it does not select the features by itself) * * @param commit - * @param {Feature[]} features A list of feature we want to highlight/select on the map + * @param {SelectableFeature[]} features A list of feature we want to highlight/select on + * the map */ setSelectedFeatures({ commit }, features) { if (Array.isArray(features)) { diff --git a/src/utils/geoJsonUtils.js b/src/utils/geoJsonUtils.js index fddd1d2f7..02153b4d8 100644 --- a/src/utils/geoJsonUtils.js +++ b/src/utils/geoJsonUtils.js @@ -27,7 +27,7 @@ export default function reprojectGeoJsonData(geoJsonData, toProjection, fromProj } } else if (toProjection instanceof CoordinateSystem) { // according to the IETF reference, if nothing is said about the projection used, it should be WGS84 - reprojectedGeoJSON = reproject(this.geojsonData, WGS84.epsg, toProjection.epsg) + reprojectedGeoJSON = reproject(geoJsonData, WGS84.epsg, toProjection.epsg) } return reprojectedGeoJSON } diff --git a/src/utils/identifyOnVectorLayer.js b/src/utils/identifyOnVectorLayer.js new file mode 100644 index 000000000..86d3c61fa --- /dev/null +++ b/src/utils/identifyOnVectorLayer.js @@ -0,0 +1,108 @@ +import { LayerFeature } from '@/api/features.api' +import { WGS84 } from '@/utils/coordinates/coordinateSystems' +import reprojectGeoJsonData from '@/utils/geoJsonUtils' +import log from '@/utils/logging' +import distance from '@turf/distance' +import { point } from '@turf/helpers' +import proj4 from 'proj4' + +const pixelToleranceForIdentify = 10 + +/** + * Finds and returns all features, from the given GeoJSON layer, that are under or close to the + * given coordinate (we require the map resolution as input, so that we may calculate a 10-pixels + * tolerance for feature identification) + * + * This means we do not require OpenLayers to perform this search anymore, and that this code can be + * used in any mapping framework. + * + * @param {GeoAdminGeoJsonLayer} geoJsonLayer The GeoJSON layer in which we want to find feature at + * the given coordinate. This layer must have its geoJsonData loaded in order for this + * identification of feature to work properly (this function will not load the data if it is + * missing) + * @param {[Number, Number]} coordinate Where we want to find features ([x, y]) + * @param {CoordinateSystem} projection The projection used to describe the coordinate where we want + * to search for feature + * @param {Number} resolution The current map resolution, in meters/pixel. Used to calculate a + * tolerance of 10 pixels around the given coordinate. + * @returns {SelectableFeature[]} The feature found at the coordinate, or an empty array if none + * were found + */ +export function identifyGeoJSONFeatureAt(geoJsonLayer, coordinate, projection, resolution) { + const features = [] + // if there is a GeoJSON layer currently visible, we will find it and search for features under the mouse cursor + const coordinateWGS84 = point(proj4(projection.epsg, WGS84.epsg, coordinate)) + // to use turf functions, we need to have lat/lon (WGS84) coordinates + const reprojectedGeoJSON = reprojectGeoJsonData(geoJsonLayer.geoJsonData, WGS84, projection) + if (!reprojectedGeoJSON) { + log.error( + `Unable to reproject GeoJSON data in order to find features at coordinates`, + geoJsonLayer.getID(), + coordinate + ) + return [] + } + const matchingFeatures = reprojectedGeoJSON.features + .filter((feature) => { + const distanceWithClick = distance( + coordinateWGS84, + point(feature.geometry.coordinates), + { + units: 'meters', + } + ) + return distanceWithClick <= pixelToleranceForIdentify * resolution + }) + .map((feature) => { + // back to the starting projection + feature.geometry.coordinates = proj4( + WGS84.epsg, + projection.epsg, + feature.geometry.coordinates + ) + return new LayerFeature( + geoJsonLayer, + feature.id, + feature.properties.station_name || feature.id, + `
+
+ ${geoJsonLayer.name} +
+
+ ${feature.properties.description} +
+
`, + proj4(WGS84.epsg, projection.epsg, feature.geometry.coordinates), + null, + feature.geometry + ) + }) + if (matchingFeatures?.length > 0) { + features.push(...matchingFeatures) + } + return features +} + +/** + * Finds and returns all features, from the given KML layer, that are under or close to the given + * coordinate (we require the map resolution as input, so that we may calculate a 10-pixels + * tolerance for feature identification) + * + * This means we do not require OpenLayers to perform this search anymore, and that this code can be + * used in any mapping framework. + * + * @param {KMLLayer} _kmlLayer The KML layer in which we want to find feature at the given + * coordinate. This layer must have its kmlData loaded in order for this identification of feature + * to work properly (this function will not load the data if it is missing) + * @param {[Number, Number]} _coordinate Where we want to find features ([x, y]) + * @param {CoordinateSystem} _projection The projection used to describe the coordinate where we + * want to search for feature + * @param {Number} _resolution The current map resolution, in meters/pixel. Used to calculate a + * tolerance of 10 pixels around the given coordinate. + * @returns {SelectableFeature[]} The feature found at the coordinate, or an empty array if none + * were found + */ +export function identifyKMLFeatureAt(_kmlLayer, _coordinate, _projection, _resolution) { + // TODO : implement KML layer feature identification + return [] +} diff --git a/src/utils/layerUtils.js b/src/utils/layerUtils.js index 3566e6331..80fe871eb 100644 --- a/src/utils/layerUtils.js +++ b/src/utils/layerUtils.js @@ -1,7 +1,5 @@ -import { YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA } from '@/api/layers/LayerTimeConfigEntry.class' import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' -import { LayerFeature } from '@/api/features.api' -import log from '@/utils/logging' +import { YEAR_TO_DESCRIBE_ALL_OR_CURRENT_DATA } from '@/api/layers/LayerTimeConfigEntry.class' export class ActiveLayerConfig { /** @@ -43,37 +41,3 @@ export function getTimestampFromConfig(config, previewYear) { } return config instanceof GeoAdminWMTSLayer ? null : '' } - -/** - * Describes a GeoJSON feature from the backend - * - * For GeoJSON features, there's a catch as they only provide us with the inner tooltip content we - * have to wrap it around the "usual" wrapper from the backend (not very fancy but otherwise the - * look and feel is different from a typical backend tooltip) - * - * @param feature - * @param geoJsonLayer - * @param [geometry] - * @returns {LayerFeature} - */ -export function createGeoJSONFeature(feature, geoJsonLayer, geometry) { - const featureGeometry = feature.getGeometry() - const geoJsonFeature = new LayerFeature( - geoJsonLayer, - geoJsonLayer.getID(), - geoJsonLayer.name, - `
-
- ${geoJsonLayer.name} -
-
- ${feature.get('description')} -
-
`, - featureGeometry.flatCoordinates, - featureGeometry.getExtent(), - geometry - ) - log.debug('GeoJSON feature found', geoJsonFeature) - return geoJsonFeature -} From f31b89f6ed7fbde936784675ca99d46acca84b92 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Mon, 4 Dec 2023 16:57:00 +0100 Subject: [PATCH 018/377] Added auto import order and sorting using eslint NOTE: the sorting of the imports as been fully automated with the es-lint plugin. --- .eslintrc.js | 6 ++++++ package-lock.json | 10 ++++++++++ package.json | 1 + 3 files changed, 17 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 7c7a48300..d6eac9696 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,8 +2,12 @@ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { + parserOptions: { + sourceType: 'module', + }, root: true, ignorePatterns: ['node_modules', '.github', 'dist'], + plugins: ['simple-import-sort'], extends: [ 'eslint:recommended', 'plugin:vue/vue3-recommended', @@ -22,6 +26,8 @@ module.exports = { destructuredArrayIgnorePattern: '^_', }, ], + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', }, globals: { VITE_ENVIRONMENT: true, diff --git a/package-lock.json b/package-lock.json index 2b30d38e5..e4222e20a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-markdownlint": "^0.5.0", "eslint-plugin-prettier-vue": "^5.0.0", + "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-vue": "^9.18.1", "git-describe": "^4.1.1", "googleapis": "^128.0.0", @@ -3620,6 +3621,15 @@ "node": ">=16" } }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz", + "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, "node_modules/eslint-plugin-vue": { "version": "9.18.1", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz", diff --git a/package.json b/package.json index 6664e342b..c467e66b9 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-markdownlint": "^0.5.0", "eslint-plugin-prettier-vue": "^5.0.0", + "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-vue": "^9.18.1", "git-describe": "^4.1.1", "googleapis": "^128.0.0", From bc6eef42e6908438886c024193fbd4fff28dcb97 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Mon, 4 Dec 2023 17:12:24 +0100 Subject: [PATCH 019/377] Run eslint with re-ordering imports --- scripts/check-external-layers-providers.js | 23 ++++++------ src/api/__tests__/features.api.spec.js | 7 ++-- src/api/__tests__/profile.api.spec.js | 5 +-- src/api/__tests__/search.api.spec.js | 3 +- src/api/features.api.js | 7 ++-- src/api/feedback.api.js | 3 +- src/api/files.api.js | 5 +-- src/api/height.api.js | 5 +-- src/api/icon.api.js | 3 +- src/api/layers/WMSCapabilitiesParser.class.js | 9 ++--- .../layers/WMTSCapabilitiesParser.class.js | 7 ++-- .../WMSCapabitliesParser.class.spec.js | 7 ++-- .../WMTSCapabitliesParser.class.spec.js | 7 ++-- src/api/layers/layers-external.api.js | 1 + src/api/layers/layers.api.js | 3 +- src/api/profile/profile.api.js | 5 +-- src/api/qrcode.api.js | 3 +- src/api/search.api.js | 5 +-- src/api/shortlink.api.js | 3 +- src/api/topics.api.js | 3 +- src/api/what3words.api.js | 5 +-- src/main.js | 20 +++++------ .../components/drawingInteraction.mixin.js | 7 ++-- .../lib/__tests__/drawingUtils.spec.js | 5 +-- src/modules/drawing/lib/drawingUtils.js | 7 ++-- src/modules/drawing/lib/export-utils.js | 7 ++-- src/modules/drawing/lib/modifyInteraction.js | 36 +++++++++---------- src/modules/drawing/lib/style.js | 5 +-- src/modules/i18n/index.js | 2 +- .../utils/addPrimitiveFromOLLayer.mixins.js | 8 +++-- .../components/cesium/utils/cameraUtils.js | 6 ++-- .../components/cesium/utils/highlightUtils.js | 3 +- .../cesium/utils/primitiveLayerUtils.js | 3 +- .../common/mouse-click.composable.js | 5 +-- .../components/common/z-index.composable.js | 3 +- .../utils/map-interactions.composable.js | 5 +-- .../openlayers/utils/map-views.composable.js | 7 ++-- .../openlayers/utils/markerStyle.js | 7 ++-- .../openlayers/utils/styleFromLiterals.js | 5 +-- src/router/index.js | 3 +- .../legacyPermalinkManagement.routerPlugin.js | 3 +- .../storeSync/LayerParamConfig.class.js | 2 +- .../__tests__/CameraParamConfig.class.spec.js | 3 +- .../__tests__/LayerParamConfig.class.spec.js | 5 +-- .../SimpleUrlParamConfig.class.spec.js | 3 +- .../abstractParamConfig.class.spec.js | 3 +- .../__tests__/layersParamParser.spec.js | 3 +- src/router/storeSync/layersParamParser.js | 2 +- .../storeSync/storeSync.routerPlugin.js | 3 +- src/store/index.js | 4 ++- .../modules/__tests__/layers.store.spec.js | 5 +-- .../modules/__tests__/rotation.store.spec.js | 5 +-- .../modules/__tests__/zoom.store.spec.js | 5 +-- src/store/modules/position.store.js | 3 +- .../plugins/geolocation-management.plugin.js | 3 +- .../load-geojson-style-and-data.plugin.js | 3 +- ...ed-features-on-projection-change.plugin.js | 3 +- src/store/plugins/sync-camera-lonlatzoom.js | 3 +- src/utils/__tests__/geodesicManager.spec.js | 9 ++--- src/utils/__tests__/legacyKmlUtils.spec.js | 11 +++--- .../__tests__/legacyLayerParamUtils.spec.js | 5 +-- src/utils/__tests__/numberUtils.spec.js | 3 +- src/utils/__tests__/urlQuery.spec.js | 4 +-- .../coordinates/CoordinateSystem.class.js | 3 +- .../WebMercatorCoordinateSystem.class.js | 3 +- .../__test__/CoordinateSystem.class.spec.js | 3 +- .../CoordinateSystemBounds.class.spec.js | 3 +- .../SwissCoordinateSystem.class.spec.js | 3 +- .../__test__/coordinateExtractors.spec.js | 5 +-- .../__test__/coordinateUtils.spec.js | 5 +-- src/utils/coordinates/coordinateExtractors.js | 3 +- src/utils/coordinates/coordinateFormat.js | 5 +-- src/utils/coordinates/coordinateUtils.js | 1 + src/utils/geoJsonUtils.js | 3 +- src/utils/geodesicManager.js | 7 ++-- src/utils/identifyOnVectorLayer.js | 7 ++-- src/utils/legacyKmlUtils.js | 9 ++--- src/utils/setupChartJS.js | 5 +-- src/utils/setupProj4.js | 3 +- tests/e2e-cypress/integration/3d/click.cy.js | 3 +- .../integration/3d/navigation.cy.js | 5 +-- .../integration/3d/transitionTo3d.cy.js | 5 +-- .../integration/drawing/export.cy.js | 3 +- .../integration/drawing/geodesicDrawing.cy.js | 2 +- .../e2e-cypress/integration/drawing/kml.cy.js | 3 +- .../e2e-cypress/integration/geolocation.cy.js | 3 +- .../integration/legacyParamImport.cy.js | 3 +- .../integration/mouseposition.cy.js | 3 +- .../search/coordinates-search.cy.js | 3 +- .../integration/search/search-results.cy.js | 3 +- tests/e2e-cypress/support/commands.js | 7 ++-- tests/e2e-cypress/support/drawing.js | 3 +- 92 files changed, 279 insertions(+), 191 deletions(-) diff --git a/scripts/check-external-layers-providers.js b/scripts/check-external-layers-providers.js index 3a0ef6d3f..254cd0ebb 100755 --- a/scripts/check-external-layers-providers.js +++ b/scripts/check-external-layers-providers.js @@ -7,26 +7,27 @@ const dom = new JSDOM() global.DOMParser = dom.window.DOMParser global.Node = dom.window.Node -import { promises as fs } from 'fs' import axios, { AxiosError } from 'axios' -import writeYamlFile from 'write-yaml-file' import axiosRetry from 'axios-retry' +import { promises as fs } from 'fs' +import { exit } from 'process' +import writeYamlFile from 'write-yaml-file' import yargs from 'yargs' import { hideBin } from 'yargs/helpers' -import { exit } from 'process' + +import { + EXTERNAL_SERVER_TIMEOUT, + parseWmsCapabilities, + parseWmtsCapabilities, +} from '@/api/layers/layers-external.api' import { + guessExternalLayerUrl, + isGpx, + isKml, isWmsGetCap, isWmtsGetCap, - isKml, - isGpx, - guessExternalLayerUrl, } from '@/modules/infobox/utils/external-provider' import { LV95 } from '@/utils/coordinates/coordinateSystems' -import { - parseWmsCapabilities, - parseWmtsCapabilities, - EXTERNAL_SERVER_TIMEOUT, -} from '@/api/layers/layers-external.api' const SIZE_OF_CONTENT_DISPLAY = 150 diff --git a/src/api/__tests__/features.api.spec.js b/src/api/__tests__/features.api.spec.js index 8345d1b2a..8884c519a 100644 --- a/src/api/__tests__/features.api.spec.js +++ b/src/api/__tests__/features.api.spec.js @@ -1,9 +1,10 @@ -import { EditableFeature, EditableFeatureTypes } from '@/api/features.api' -import { MEDIUM, RED } from '@/utils/featureStyleUtils' import { expect } from 'chai' -import { describe, it } from 'vitest' import Feature from 'ol/Feature.js' import Polygon from 'ol/geom/Polygon.js' +import { describe, it } from 'vitest' + +import { EditableFeature, EditableFeatureTypes } from '@/api/features.api' +import { MEDIUM, RED } from '@/utils/featureStyleUtils' const stringifiedTestObject = `{"id":"drawing_feature_2",\ "title":"This is a title",\ diff --git a/src/api/__tests__/profile.api.spec.js b/src/api/__tests__/profile.api.spec.js index 997312b50..3ecbff872 100644 --- a/src/api/__tests__/profile.api.spec.js +++ b/src/api/__tests__/profile.api.spec.js @@ -1,8 +1,9 @@ +import { expect } from 'chai' +import { describe, it } from 'vitest' + import ElevationProfile from '@/api/profile/ElevationProfile.class' import ElevationProfilePoint from '@/api/profile/ElevationProfilePoint.class' import ElevationProfileSegment from '@/api/profile/ElevationProfileSegment.class' -import { expect } from 'chai' -import { describe, it } from 'vitest' const testProfile = new ElevationProfile([ new ElevationProfileSegment([ diff --git a/src/api/__tests__/search.api.spec.js b/src/api/__tests__/search.api.spec.js index ca5051112..ce624b4ab 100644 --- a/src/api/__tests__/search.api.spec.js +++ b/src/api/__tests__/search.api.spec.js @@ -1,7 +1,8 @@ -import { FeatureSearchResult } from '@/api/search.api' import { expect } from 'chai' import { describe, it } from 'vitest' +import { FeatureSearchResult } from '@/api/search.api' + describe('Builds object by extracting all relevant attributes from the backend', () => { describe('FeatureSearchResult.getSimpleTitle', () => { it('Returns title removing HTML', () => { diff --git a/src/api/features.api.js b/src/api/features.api.js index 9e64e32ce..8dd64ed02 100644 --- a/src/api/features.api.js +++ b/src/api/features.api.js @@ -1,3 +1,7 @@ +import axios from 'axios' +import { Icon as openlayersIcon } from 'ol/style' +import proj4 from 'proj4' + import { DrawingIcon } from '@/api/icon.api' import { API_BASE_URL } from '@/config' import { @@ -19,9 +23,6 @@ import { import { GeodesicGeometries } from '@/utils/geodesicManager' import { getEditableFeatureFromLegacyKmlFeature } from '@/utils/legacyKmlUtils' import log from '@/utils/logging' -import axios from 'axios' -import { Icon as openlayersIcon } from 'ol/style' -import proj4 from 'proj4' /** * Representation of a feature that can be selected by the user on the map. This feature can be diff --git a/src/api/feedback.api.js b/src/api/feedback.api.js index c0087b814..a19acdc37 100644 --- a/src/api/feedback.api.js +++ b/src/api/feedback.api.js @@ -1,8 +1,9 @@ +import axios from 'axios' + import { getKmlFromUrl } from '@/api/files.api' import { createShortLink } from '@/api/shortlink.api' import { API_SERVICES_BASE_URL, APP_VERSION } from '@/config' import log from '@/utils/logging' -import axios from 'axios' /** * @param {String} text Mandatory diff --git a/src/api/files.api.js b/src/api/files.api.js index 55170795e..0fd1c39b8 100644 --- a/src/api/files.api.js +++ b/src/api/files.api.js @@ -1,9 +1,10 @@ -import { API_SERVICE_KML_BASE_URL } from '@/config' -import log from '@/utils/logging' import axios from 'axios' import FormData from 'form-data' import pako from 'pako' +import { API_SERVICE_KML_BASE_URL } from '@/config' +import log from '@/utils/logging' + /** * KML links * diff --git a/src/api/height.api.js b/src/api/height.api.js index 0712e9a2c..5eeaa1d55 100644 --- a/src/api/height.api.js +++ b/src/api/height.api.js @@ -1,9 +1,10 @@ +import axios from 'axios' +import proj4 from 'proj4' + import { API_SERVICE_ALTI_BASE_URL } from '@/config' import { LV95 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' import { round } from '@/utils/numberUtils' -import axios from 'axios' -import proj4 from 'proj4' export const meterToFeetFactor = 3.28084 diff --git a/src/api/icon.api.js b/src/api/icon.api.js index 3aca04945..52cc92af8 100644 --- a/src/api/icon.api.js +++ b/src/api/icon.api.js @@ -1,7 +1,8 @@ +import axios from 'axios' + import { API_SERVICES_BASE_URL } from '@/config' import { MEDIUM, RED } from '@/utils/featureStyleUtils' import log from '@/utils/logging' -import axios from 'axios' /** * Collection of icons belonging to the same "category" (or set). diff --git a/src/api/layers/WMSCapabilitiesParser.class.js b/src/api/layers/WMSCapabilitiesParser.class.js index 7fc21dec2..776ad3463 100644 --- a/src/api/layers/WMSCapabilitiesParser.class.js +++ b/src/api/layers/WMSCapabilitiesParser.class.js @@ -1,11 +1,12 @@ -import { WMS_SUPPORTED_VERSIONS } from '@/config' +import { WMSCapabilities } from 'ol/format' +import proj4 from 'proj4' + import { LayerAttribution } from '@/api/layers/AbstractLayer.class' -import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' +import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' +import { WMS_SUPPORTED_VERSIONS } from '@/config' import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' -import { WMSCapabilities } from 'ol/format' -import proj4 from 'proj4' function findLayer(layerId, startFrom, parents) { let found = {} diff --git a/src/api/layers/WMTSCapabilitiesParser.class.js b/src/api/layers/WMTSCapabilitiesParser.class.js index 1b2043f39..ed49e7726 100644 --- a/src/api/layers/WMTSCapabilitiesParser.class.js +++ b/src/api/layers/WMTSCapabilitiesParser.class.js @@ -1,9 +1,10 @@ +import WMTSCapabilities from 'ol/format/WMTSCapabilities' +import proj4 from 'proj4' + import { LayerAttribution } from '@/api/layers/AbstractLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' -import log from '@/utils/logging' -import WMTSCapabilities from 'ol/format/WMTSCapabilities' import allCoordinateSystems, { WGS84 } from '@/utils/coordinates/coordinateSystems' -import proj4 from 'proj4' +import log from '@/utils/logging' function parseCrs(crs) { let epsgNumber = crs?.split(':').pop() diff --git a/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js index 064581ab4..32f8980a6 100644 --- a/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js +++ b/src/api/layers/__tests__/WMSCapabitliesParser.class.spec.js @@ -1,10 +1,11 @@ -import WMSCapabilitiesParser from '../WMSCapabilitiesParser.class' +import { readFile } from 'fs/promises' +import { beforeAll, describe, expect, expectTypeOf, it } from 'vitest' + import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' -import { readFile } from 'fs/promises' -import { describe, it, expect, beforeAll, expectTypeOf } from 'vitest' +import WMSCapabilitiesParser from '../WMSCapabilitiesParser.class' describe('WMSCapabilitiesParser - invalid', () => { it('Throw Error on invalid input', () => { diff --git a/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js b/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js index 68f97b4ad..5e566fab6 100644 --- a/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js +++ b/src/api/layers/__tests__/WMTSCapabitliesParser.class.spec.js @@ -1,8 +1,9 @@ -import WMTSCapabilitiesParser from '../WMTSCapabilitiesParser.class' +import { readFile } from 'fs/promises' +import { beforeAll, describe, expect, expectTypeOf, it } from 'vitest' + import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' -import { readFile } from 'fs/promises' -import { describe, it, expect, beforeAll, expectTypeOf } from 'vitest' +import WMTSCapabilitiesParser from '../WMTSCapabilitiesParser.class' describe('WMTSCapabilitiesParser of wmts-ogc-sample.xml', () => { let capabilities diff --git a/src/api/layers/layers-external.api.js b/src/api/layers/layers-external.api.js index 49ea8d60f..261294134 100644 --- a/src/api/layers/layers-external.api.js +++ b/src/api/layers/layers-external.api.js @@ -1,4 +1,5 @@ import axios from 'axios' + import WMSCapabilitiesParser from '@/api/layers/WMSCapabilitiesParser.class' import WMTSCapabilitiesParser from '@/api/layers/WMTSCapabilitiesParser.class' import log from '@/utils/logging' diff --git a/src/api/layers/layers.api.js b/src/api/layers/layers.api.js index 591c80b5c..09d3b827d 100644 --- a/src/api/layers/layers.api.js +++ b/src/api/layers/layers.api.js @@ -1,3 +1,5 @@ +import axios from 'axios' + import { LayerAttribution } from '@/api/layers/AbstractLayer.class' import GeoAdminAggregateLayer, { AggregateSubLayer, @@ -9,7 +11,6 @@ import LayerTimeConfig from '@/api/layers/LayerTimeConfig.class' import LayerTimeConfigEntry from '@/api/layers/LayerTimeConfigEntry.class' import { API_BASE_URL, WMTS_BASE_URL } from '@/config' import log from '@/utils/logging' -import axios from 'axios' // API file that covers the backend endpoint http://api3.geo.admin.ch/rest/services/all/MapServer/layersConfig // TODO : implement loading of a cached CloudFront version for MVP diff --git a/src/api/profile/profile.api.js b/src/api/profile/profile.api.js index 75558d2e9..7855e8e15 100644 --- a/src/api/profile/profile.api.js +++ b/src/api/profile/profile.api.js @@ -1,11 +1,12 @@ +import axios from 'axios' +import proj4 from 'proj4' + import ElevationProfile from '@/api/profile/ElevationProfile.class' import ElevationProfilePoint from '@/api/profile/ElevationProfilePoint.class' import ElevationProfileSegment from '@/api/profile/ElevationProfileSegment.class' import { API_SERVICE_ALTI_BASE_URL } from '@/config' import { LV95 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' -import axios from 'axios' -import proj4 from 'proj4' function parseProfileFromBackendResponse(backendResponse, startingDist, outputProjection) { const points = [] diff --git a/src/api/qrcode.api.js b/src/api/qrcode.api.js index d4bff6691..862793e65 100644 --- a/src/api/qrcode.api.js +++ b/src/api/qrcode.api.js @@ -1,6 +1,7 @@ +import axios from 'axios' + import { API_SERVICES_BASE_URL } from '@/config' import log from '@/utils/logging' -import axios from 'axios' /** * Generates a QR Code that, when scanned by mobile devices, open the URL given in parameters diff --git a/src/api/search.api.js b/src/api/search.api.js index e872ef3ad..0ca0e2c98 100644 --- a/src/api/search.api.js +++ b/src/api/search.api.js @@ -1,10 +1,11 @@ +import axios from 'axios' +import proj4 from 'proj4' + import { API_SERVICE_SEARCH_BASE_URL } from '@/config' import { LV95, WGS84 } from '@/utils/coordinates/coordinateSystems' import CustomCoordinateSystem from '@/utils/coordinates/CustomCoordinateSystem.class' import LV95CoordinateSystem from '@/utils/coordinates/LV95CoordinateSystem.class' import log from '@/utils/logging' -import axios from 'axios' -import proj4 from 'proj4' // API file that covers the backend endpoint http://api3.geo.admin.ch/services/sdiservices.html#search diff --git a/src/api/shortlink.api.js b/src/api/shortlink.api.js index 1ad4acd22..4261613cd 100644 --- a/src/api/shortlink.api.js +++ b/src/api/shortlink.api.js @@ -1,6 +1,7 @@ +import axios from 'axios' + import { API_SERVICE_SHORTLINK_BASE_URL } from '@/config' import log from '@/utils/logging' -import axios from 'axios' /** * Generates a short link from the given URL diff --git a/src/api/topics.api.js b/src/api/topics.api.js index ac71d90ff..18b270e45 100644 --- a/src/api/topics.api.js +++ b/src/api/topics.api.js @@ -1,3 +1,5 @@ +import axios from 'axios' + import GeoAdminGroupOfLayers from '@/api/layers/GeoAdminGroupOfLayers.class' import { API_BASE_URL } from '@/config' import { @@ -5,7 +7,6 @@ import { getLayersFromLegacyUrlParams, } from '@/utils/legacyLayerParamUtils' import log from '@/utils/logging' -import axios from 'axios' /** Representation of a topic (a subset of layers to be shown to the user) */ export class Topic { diff --git a/src/api/what3words.api.js b/src/api/what3words.api.js index 075a250de..7a10fc40e 100644 --- a/src/api/what3words.api.js +++ b/src/api/what3words.api.js @@ -1,8 +1,9 @@ -import { WGS84 } from '@/utils/coordinates/coordinateSystems' -import log from '@/utils/logging' import axios from 'axios' import proj4 from 'proj4' +import { WGS84 } from '@/utils/coordinates/coordinateSystems' +import log from '@/utils/logging' + // copied from https://developer.what3words.com/tutorial/detecting-if-text-is-in-the-format-of-a-3-word-address const REGEX_WHAT_3_WORDS = /^\/{0,}[^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}$/i diff --git a/src/main.js b/src/main.js index 1b5bf12bd..73ff78eda 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,14 @@ // exposing the config in the logs +// Importing styling CSS libraries +import 'animate.css' +// setting up font awesome vue component +import './setup-fontawesome' + +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import tippy from 'tippy.js' +import { createApp } from 'vue' +import VueSocialSharing from 'vue-social-sharing' + import { API_BASE_URL, API_SERVICE_ALTI_BASE_URL, @@ -22,17 +32,7 @@ import store from '@/store' import log from '@/utils/logging' import setupChartJS from '@/utils/setupChartJS' -import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' -// Importing styling CSS libraries -import 'animate.css' -import tippy from 'tippy.js' - -import { createApp } from 'vue' -import VueSocialSharing from 'vue-social-sharing' - import App from './App.vue' -// setting up font awesome vue component -import './setup-fontawesome' log.debug('Config is', { ENVIRONMENT, diff --git a/src/modules/drawing/components/drawingInteraction.mixin.js b/src/modules/drawing/components/drawingInteraction.mixin.js index c3002001e..5eeef7ffa 100644 --- a/src/modules/drawing/components/drawingInteraction.mixin.js +++ b/src/modules/drawing/components/drawingInteraction.mixin.js @@ -1,10 +1,11 @@ -import { EditableFeature } from '@/api/features.api' -import { editingFeatureStyleFunction, featureStyleFunction } from '@/modules/drawing/lib/style' import DrawInteraction from 'ol/interaction/Draw' import { getUid } from 'ol/util' -import { wrapXCoordinates } from '@/modules/drawing/lib/drawingUtils' import { mapState } from 'vuex' +import { EditableFeature } from '@/api/features.api' +import { wrapXCoordinates } from '@/modules/drawing/lib/drawingUtils' +import { editingFeatureStyleFunction, featureStyleFunction } from '@/modules/drawing/lib/style' + /** * Vue mixin that will handle the addition or removal of a drawing interaction to the drawing * module. diff --git a/src/modules/drawing/lib/__tests__/drawingUtils.spec.js b/src/modules/drawing/lib/__tests__/drawingUtils.spec.js index 7863b9304..19b1ed5ef 100644 --- a/src/modules/drawing/lib/__tests__/drawingUtils.spec.js +++ b/src/modules/drawing/lib/__tests__/drawingUtils.spec.js @@ -1,3 +1,6 @@ +import { expect } from 'chai' +import { describe, it } from 'vitest' + import { formatMeters, formatPointCoordinates, @@ -6,8 +9,6 @@ import { wrapXCoordinates, } from '@/modules/drawing/lib/drawingUtils' import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' -import { expect } from 'chai' -import { describe, it } from 'vitest' describe('Unit test functions from drawingUtils.js', () => { describe('toLv95(coordinate, "EPSG:4326")', () => { diff --git a/src/modules/drawing/lib/drawingUtils.js b/src/modules/drawing/lib/drawingUtils.js index 1f92697ba..ece4e6b23 100644 --- a/src/modules/drawing/lib/drawingUtils.js +++ b/src/modules/drawing/lib/drawingUtils.js @@ -1,11 +1,12 @@ -import { LV95, WGS84 } from '@/utils/coordinates/coordinateSystems' -import { format } from '@/utils/numberUtils' import { wrapX } from 'ol/coordinate' +import KML from 'ol/format/KML' import { LineString, Point, Polygon } from 'ol/geom' import { get as getProjection } from 'ol/proj' import proj4 from 'proj4' -import KML from 'ol/format/KML' + import { EditableFeature } from '@/api/features.api' +import { LV95, WGS84 } from '@/utils/coordinates/coordinateSystems' +import { format } from '@/utils/numberUtils' export function toLv95(input, epsg) { if (Array.isArray(input[0])) { diff --git a/src/modules/drawing/lib/export-utils.js b/src/modules/drawing/lib/export-utils.js index edf58d2d7..ce643d86b 100644 --- a/src/modules/drawing/lib/export-utils.js +++ b/src/modules/drawing/lib/export-utils.js @@ -1,11 +1,12 @@ -import { featureStyleFunction } from '@/modules/drawing/lib/style' -import i18n from '@/modules/i18n/index' -import { WGS84 } from '@/utils/coordinates/coordinateSystems' import Feature from 'ol/Feature' import { GPX, KML } from 'ol/format' import { LineString, Polygon } from 'ol/geom' import { Circle, Icon } from 'ol/style' import Style from 'ol/style/Style' + +import { featureStyleFunction } from '@/modules/drawing/lib/style' +import i18n from '@/modules/i18n/index' +import { WGS84 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' const kmlFormat = new KML() diff --git a/src/modules/drawing/lib/modifyInteraction.js b/src/modules/drawing/lib/modifyInteraction.js index c27ec546b..c0f8cbe03 100644 --- a/src/modules/drawing/lib/modifyInteraction.js +++ b/src/modules/drawing/lib/modifyInteraction.js @@ -28,24 +28,9 @@ * * @module ol/interaction/Modify * */ +import { equals } from "ol/array"; import Collection from "ol/Collection"; import CollectionEventType from "ol/CollectionEventType"; -import Event from "ol/events/Event"; -import EventType from "ol/events/EventType"; -import Feature from "ol/Feature"; -import MapBrowserEventType from "ol/MapBrowserEventType"; -import Point from "ol/geom/Point"; -import PointerInteraction from "ol/interaction/Pointer"; -import RBush from "ol/structs/RBush"; -import VectorEventType from "ol/source/VectorEventType"; -import VectorLayer from "ol/layer/Vector"; -import VectorSource from "ol/source/Vector"; -import { altKeyOnly, always, primaryAction, singleClick } from "ol/events/condition"; -import { - boundingExtent, - buffer as bufferExtent, - createOrUpdateFromCoordinate as createExtent, -} from "ol/extent"; import { wrapX as wrapXCoordinate } from "ol/coordinate"; import { closestOnSegment, @@ -54,9 +39,20 @@ import { squaredDistance as squaredCoordinateDistance, squaredDistanceToSegment, } from "ol/coordinate"; -import { createEditingStyle } from "ol/style/Style"; -import { equals } from "ol/array"; +import { altKeyOnly, always, primaryAction, singleClick } from "ol/events/condition"; +import Event from "ol/events/Event"; +import EventType from "ol/events/EventType"; +import { + boundingExtent, + buffer as bufferExtent, + createOrUpdateFromCoordinate as createExtent, +} from "ol/extent"; +import Feature from "ol/Feature"; +import Point from "ol/geom/Point"; import { fromCircle } from "ol/geom/Polygon"; +import PointerInteraction from "ol/interaction/Pointer"; +import VectorLayer from "ol/layer/Vector"; +import MapBrowserEventType from "ol/MapBrowserEventType"; import { fromUserCoordinate, fromUserExtent, @@ -64,6 +60,10 @@ import { toUserCoordinate, toUserExtent, } from "ol/proj"; +import VectorSource from "ol/source/Vector"; +import VectorEventType from "ol/source/VectorEventType"; +import RBush from "ol/structs/RBush"; +import { createEditingStyle } from "ol/style/Style"; import { getUid } from "ol/util"; /** diff --git a/src/modules/drawing/lib/style.js b/src/modules/drawing/lib/style.js index b933c1229..e91d3df88 100644 --- a/src/modules/drawing/lib/style.js +++ b/src/modules/drawing/lib/style.js @@ -1,7 +1,8 @@ -import { EditableFeatureTypes } from '@/api/features.api' -import { LineString, MultiPoint, Polygon, Point } from 'ol/geom' +import { LineString, MultiPoint, Point, Polygon } from 'ol/geom' import { Circle, Fill, Stroke, Style, Text } from 'ol/style' +import { EditableFeatureTypes } from '@/api/features.api' + /* Z-INDICES The z indices for the styles are given according to the following table: azimuth-circle/fill: 0 diff --git a/src/modules/i18n/index.js b/src/modules/i18n/index.js index 4f4ded42f..9bc8bbf35 100644 --- a/src/modules/i18n/index.js +++ b/src/modules/i18n/index.js @@ -1,6 +1,6 @@ import { createI18n } from 'vue-i18n' -import de from './locales/de.json' +import de from './locales/de.json' import en from './locales/en.json' import fr from './locales/fr.json' import it from './locales/it.json' diff --git a/src/modules/map/components/cesium/utils/addPrimitiveFromOLLayer.mixins.js b/src/modules/map/components/cesium/utils/addPrimitiveFromOLLayer.mixins.js index 637310b19..2472c6057 100644 --- a/src/modules/map/components/cesium/utils/addPrimitiveFromOLLayer.mixins.js +++ b/src/modules/map/components/cesium/utils/addPrimitiveFromOLLayer.mixins.js @@ -1,9 +1,11 @@ +import { PrimitiveCollection } from 'cesium' +import { Vector as VectorLayer } from 'ol/layer' +import FeatureConverter from 'ol-cesium/src/olcs/FeatureConverter' + import { IS_TESTING_WITH_CYPRESS } from '@/config' import { PRIMITIVE_DISABLE_DEPTH_TEST_DISTANCE } from '@/modules/map/components/cesium/constants' import { updateCollectionProperties } from '@/modules/map/components/cesium/utils/primitiveLayerUtils' -import { PrimitiveCollection } from 'cesium' -import FeatureConverter from 'ol-cesium/src/olcs/FeatureConverter' -import { Vector as VectorLayer } from 'ol/layer' + import addLayerToViewer from './addLayerToViewer-mixins' const STYLE_RESOLUTION = 20 diff --git a/src/modules/map/components/cesium/utils/cameraUtils.js b/src/modules/map/components/cesium/utils/cameraUtils.js index ffaf97ff3..9bf23b787 100644 --- a/src/modules/map/components/cesium/utils/cameraUtils.js +++ b/src/modules/map/components/cesium/utils/cameraUtils.js @@ -1,13 +1,13 @@ import { Cartesian2, Cartesian3, - Matrix4, Cartographic, + HeadingPitchRange, + Math as CesiumMath, + Matrix4, Ray, Rectangle, Transforms, - HeadingPitchRange, - Math as CesiumMath, } from 'cesium' /** diff --git a/src/modules/map/components/cesium/utils/highlightUtils.js b/src/modules/map/components/cesium/utils/highlightUtils.js index b2e70fa2a..3deffa7d9 100644 --- a/src/modules/map/components/cesium/utils/highlightUtils.js +++ b/src/modules/map/components/cesium/utils/highlightUtils.js @@ -1,7 +1,8 @@ -import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import { Cartesian3, Color, Entity, HeightReference } from 'cesium' import proj4 from 'proj4' +import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' + let highlightedEntities = [] const highlightFill = Color.fromCssColorString('rgba(255, 255, 0, 0.75)') const highlightedStroke = Color.fromCssColorString('rgba(255, 128, 0, 1)') diff --git a/src/modules/map/components/cesium/utils/primitiveLayerUtils.js b/src/modules/map/components/cesium/utils/primitiveLayerUtils.js index 1e2d06647..17a85dcd8 100644 --- a/src/modules/map/components/cesium/utils/primitiveLayerUtils.js +++ b/src/modules/map/components/cesium/utils/primitiveLayerUtils.js @@ -1,4 +1,3 @@ -import log from '@/utils/logging' import { Billboard, BillboardCollection, @@ -18,6 +17,8 @@ import { VerticalOrigin, } from 'cesium' +import log from '@/utils/logging' + /** * Style to apply to our labels in 3D. It is a complete rip-off of * https://github.com/geoadmin/mf-geoadmin3/blob/6a7b99a2cc9980eec27b394ee709305a239549f1/src/components/StylesService.js#L159-L233 diff --git a/src/modules/map/components/common/mouse-click.composable.js b/src/modules/map/components/common/mouse-click.composable.js index 75ed61de1..8af2734e2 100644 --- a/src/modules/map/components/common/mouse-click.composable.js +++ b/src/modules/map/components/common/mouse-click.composable.js @@ -1,8 +1,9 @@ +import { computed } from 'vue' +import { useStore } from 'vuex' + import LayerTypes from '@/api/layers/LayerTypes.enum' import { ClickInfo, ClickType } from '@/store/modules/map.store' import { identifyGeoJSONFeatureAt, identifyKMLFeatureAt } from '@/utils/identifyOnVectorLayer' -import { computed } from 'vue' -import { useStore } from 'vuex' const msBeforeTriggeringLocationPopup = 700 diff --git a/src/modules/map/components/common/z-index.composable.js b/src/modules/map/components/common/z-index.composable.js index 61eaa95bd..9447733f7 100644 --- a/src/modules/map/components/common/z-index.composable.js +++ b/src/modules/map/components/common/z-index.composable.js @@ -1,7 +1,8 @@ -import LayerTypes from '@/api/layers/LayerTypes.enum' import { computed } from 'vue' import { useStore } from 'vuex' +import LayerTypes from '@/api/layers/LayerTypes.enum' + /** Composable that gives utility function to calculate/get layers' and features' z-index */ export function useLayerZIndexCalculation() { const store = useStore() diff --git a/src/modules/map/components/openlayers/utils/map-interactions.composable.js b/src/modules/map/components/openlayers/utils/map-interactions.composable.js index b7ebdd106..331d74e8a 100644 --- a/src/modules/map/components/openlayers/utils/map-interactions.composable.js +++ b/src/modules/map/components/openlayers/utils/map-interactions.composable.js @@ -1,5 +1,3 @@ -import { useMouseOnMap } from '@/modules/map/components/common/mouse-click.composable' -import { LV95 } from '@/utils/coordinates/coordinateSystems' import { platformModifierKeyOnly } from 'ol/events/condition' import { defaults as getDefaultInteractions, MouseWheelZoom } from 'ol/interaction' import DoubleClickZoomInteraction from 'ol/interaction/DoubleClickZoom' @@ -7,6 +5,9 @@ import DragRotateInteraction from 'ol/interaction/DragRotate' import { computed, onBeforeUnmount, onMounted, watch } from 'vue' import { useStore } from 'vuex' +import { useMouseOnMap } from '@/modules/map/components/common/mouse-click.composable' +import { LV95 } from '@/utils/coordinates/coordinateSystems' + export default function useMapInteractions(map) { const { onLeftClickDown, onLeftClickUp, onRightClick, onMouseMove } = useMouseOnMap() const store = useStore() diff --git a/src/modules/map/components/openlayers/utils/map-views.composable.js b/src/modules/map/components/openlayers/utils/map-views.composable.js index 0524ef430..2f8cf7263 100644 --- a/src/modules/map/components/openlayers/utils/map-views.composable.js +++ b/src/modules/map/components/openlayers/utils/map-views.composable.js @@ -1,11 +1,12 @@ +import { View } from 'ol' +import { computed, onBeforeUnmount, onMounted, watch } from 'vue' +import { useStore } from 'vuex' + import { IS_TESTING_WITH_CYPRESS, VIEW_MIN_RESOLUTION } from '@/config' import { LV95, WEBMERCATOR } from '@/utils/coordinates/coordinateSystems' import { LV95_RESOLUTIONS } from '@/utils/coordinates/SwissCoordinateSystem.class' import log from '@/utils/logging' import { round } from '@/utils/numberUtils' -import { View } from 'ol' -import { computed, onBeforeUnmount, onMounted, watch } from 'vue' -import { useStore } from 'vuex' let animationDuration = 200 if (IS_TESTING_WITH_CYPRESS) { diff --git a/src/modules/map/components/openlayers/utils/markerStyle.js b/src/modules/map/components/openlayers/utils/markerStyle.js index be993d09b..3249ee24c 100644 --- a/src/modules/map/components/openlayers/utils/markerStyle.js +++ b/src/modules/map/components/openlayers/utils/markerStyle.js @@ -1,11 +1,12 @@ +import { Fill, Stroke, Style } from 'ol/style' +import CircleStyle from 'ol/style/Circle' +import IconStyle from 'ol/style/Icon' + import bowlImage from '@/modules/map/assets/bowl.png' import circleImage from '@/modules/map/assets/circle.png' import crossImage from '@/modules/map/assets/cross.png' import markerImage from '@/modules/map/assets/marker.png' import pointImage from '@/modules/map/assets/point.png' -import { Fill, Stroke, Style } from 'ol/style' -import CircleStyle from 'ol/style/Circle' -import IconStyle from 'ol/style/Icon' // style for feature highlighting (we export it so that they can be re-used by OpenLayersHighlightedFeature) export const highlightedFill = new Fill({ diff --git a/src/modules/map/components/openlayers/utils/styleFromLiterals.js b/src/modules/map/components/openlayers/utils/styleFromLiterals.js index f2ae06433..f0942e9e2 100644 --- a/src/modules/map/components/openlayers/utils/styleFromLiterals.js +++ b/src/modules/map/components/openlayers/utils/styleFromLiterals.js @@ -1,9 +1,10 @@ // copied and adapted from https://github.com/geoadmin/mf-geoadmin3/blob/master/src/components/StylesFromLiteralsService.js -import log from '@/utils/logging' -import { isNumber } from '@/utils/numberUtils' import { LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from 'ol/geom' import { Circle, Fill, Icon, RegularShape, Stroke, Style, Text } from 'ol/style' +import log from '@/utils/logging' +import { isNumber } from '@/utils/numberUtils' + function getOlStyleForPoint(options, shape) { if (shape === 'circle') { return new Circle(options) diff --git a/src/router/index.js b/src/router/index.js index a3f9847da..baa9283b0 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,3 +1,5 @@ +import { createRouter, createWebHashHistory } from 'vue-router' + import { IS_TESTING_WITH_CYPRESS } from '@/config' import appLoadingManagementRouterPlugin from '@/router/appLoadingManagement.routerPlugin' import legacyPermalinkManagementRouterPlugin from '@/router/legacyPermalinkManagement.routerPlugin' @@ -6,7 +8,6 @@ import store from '@/store' import { parseQuery, stringifyQuery } from '@/utils/url-router' import LoadingView from '@/views/LoadingView.vue' import MapView from '@/views/MapView.vue' -import { createRouter, createWebHashHistory } from 'vue-router' const history = createWebHashHistory() diff --git a/src/router/legacyPermalinkManagement.routerPlugin.js b/src/router/legacyPermalinkManagement.routerPlugin.js index 6633044f8..980ed2a70 100644 --- a/src/router/legacyPermalinkManagement.routerPlugin.js +++ b/src/router/legacyPermalinkManagement.routerPlugin.js @@ -1,3 +1,5 @@ +import proj4 from 'proj4' + import { transformLayerIntoUrlString } from '@/router/storeSync/LayerParamConfig.class' import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import CustomCoordinateSystem from '@/utils/coordinates/CustomCoordinateSystem.class' @@ -8,7 +10,6 @@ import { isLayersUrlParamLegacy, } from '@/utils/legacyLayerParamUtils' import log from '@/utils/logging' -import proj4 from 'proj4' /** * @param {String} search diff --git a/src/router/storeSync/LayerParamConfig.class.js b/src/router/storeSync/LayerParamConfig.class.js index b70cb3ac2..c798fb3ea 100644 --- a/src/router/storeSync/LayerParamConfig.class.js +++ b/src/router/storeSync/LayerParamConfig.class.js @@ -1,6 +1,6 @@ +import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' -import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' import KMLLayer from '@/api/layers/KMLLayer.class' import LayerTypes from '@/api/layers/LayerTypes.enum' import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class' diff --git a/src/router/storeSync/__tests__/CameraParamConfig.class.spec.js b/src/router/storeSync/__tests__/CameraParamConfig.class.spec.js index 93b9957c0..c31d5ec04 100644 --- a/src/router/storeSync/__tests__/CameraParamConfig.class.spec.js +++ b/src/router/storeSync/__tests__/CameraParamConfig.class.spec.js @@ -1,7 +1,8 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + import PositionUrlParamConfig, { readCameraFromUrlParam, } from '@/router/storeSync/CameraParamConfig.class' -import { beforeEach, describe, expect, it, vi } from 'vitest' describe('CameraParamConfig class test', () => { const testInstance = new PositionUrlParamConfig() diff --git a/src/router/storeSync/__tests__/LayerParamConfig.class.spec.js b/src/router/storeSync/__tests__/LayerParamConfig.class.spec.js index 85300ac92..dce5392c4 100644 --- a/src/router/storeSync/__tests__/LayerParamConfig.class.spec.js +++ b/src/router/storeSync/__tests__/LayerParamConfig.class.spec.js @@ -1,10 +1,11 @@ +import { expect } from 'chai' +import { describe, it } from 'vitest' + import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' import KMLLayer from '@/api/layers/KMLLayer.class' import { createLayerObject } from '@/router/storeSync/LayerParamConfig.class' import { ActiveLayerConfig } from '@/utils/layerUtils' -import { expect } from 'chai' -import { describe, it } from 'vitest' describe('External layer parsing with createLayerObject', () => { it('parses a KML layer correctly', () => { diff --git a/src/router/storeSync/__tests__/SimpleUrlParamConfig.class.spec.js b/src/router/storeSync/__tests__/SimpleUrlParamConfig.class.spec.js index 2be756fc7..03a7f4cc5 100644 --- a/src/router/storeSync/__tests__/SimpleUrlParamConfig.class.spec.js +++ b/src/router/storeSync/__tests__/SimpleUrlParamConfig.class.spec.js @@ -1,7 +1,8 @@ -import SimpleUrlParamConfig from '@/router/storeSync/SimpleUrlParamConfig.class' import { expect } from 'chai' import { describe, it } from 'vitest' +import SimpleUrlParamConfig from '@/router/storeSync/SimpleUrlParamConfig.class' + describe('Test all SimpleUrlParamConfig class functionalities', () => { const fakeStore = { test: 'test', diff --git a/src/router/storeSync/__tests__/abstractParamConfig.class.spec.js b/src/router/storeSync/__tests__/abstractParamConfig.class.spec.js index 91d2075c8..64938a85e 100644 --- a/src/router/storeSync/__tests__/abstractParamConfig.class.spec.js +++ b/src/router/storeSync/__tests__/abstractParamConfig.class.spec.js @@ -1,7 +1,8 @@ -import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class' import { expect } from 'chai' import { describe, it } from 'vitest' +import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class' + class DummyUrlParamConfig extends AbstractParamConfig { constructor( keepInUrlWhenDefault = true, diff --git a/src/router/storeSync/__tests__/layersParamParser.spec.js b/src/router/storeSync/__tests__/layersParamParser.spec.js index aff6d0a6b..b10cf47b2 100644 --- a/src/router/storeSync/__tests__/layersParamParser.spec.js +++ b/src/router/storeSync/__tests__/layersParamParser.spec.js @@ -1,7 +1,8 @@ -import layersParamParser from '@/router/storeSync/layersParamParser' import { expect } from 'chai' import { describe, it } from 'vitest' +import layersParamParser from '@/router/storeSync/layersParamParser' + describe('Testing layersParamParser', () => { const checkLayer = (layer, id, visible = true, opacity = undefined, customAttributes = {}) => { expect(layer).to.be.an('Object') diff --git a/src/router/storeSync/layersParamParser.js b/src/router/storeSync/layersParamParser.js index 02a4a1431..fdae7ccf2 100644 --- a/src/router/storeSync/layersParamParser.js +++ b/src/router/storeSync/layersParamParser.js @@ -1,5 +1,5 @@ -import { isNumber } from '@/utils/numberUtils' import { ActiveLayerConfig } from '@/utils/layerUtils' +import { isNumber } from '@/utils/numberUtils' /** * Parses the URL param value for `layers` as described in the ADR : diff --git a/src/router/storeSync/storeSync.routerPlugin.js b/src/router/storeSync/storeSync.routerPlugin.js index f45c54abd..c13e193ed 100644 --- a/src/router/storeSync/storeSync.routerPlugin.js +++ b/src/router/storeSync/storeSync.routerPlugin.js @@ -1,7 +1,8 @@ +import axios from 'axios' + import { IS_TESTING_WITH_CYPRESS } from '@/config' import storeSyncConfig from '@/router/storeSync/storeSync.config' import log from '@/utils/logging' -import axios from 'axios' export const FAKE_URL_CALLED_AFTER_ROUTE_CHANGE = '/tell-cypress-route-has-changed' diff --git a/src/store/index.js b/src/store/index.js index 2ca6e302e..5928eed4e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,7 +1,9 @@ +import { createStore } from 'vuex' + import from2Dto3Dplugin from '@/store/plugins/2d-to-3d-management.plugin' import loadGeojsonStyleAndData from '@/store/plugins/load-geojson-style-and-data.plugin' import reprojectSelectedFeaturesOnProjectionChangePlugin from '@/store/plugins/reproject-selected-features-on-projection-change.plugin' -import { createStore } from 'vuex' + import app from './modules/app.store' import cesium from './modules/cesium.store' import drawing from './modules/drawing.store' diff --git a/src/store/modules/__tests__/layers.store.spec.js b/src/store/modules/__tests__/layers.store.spec.js index bf0229b74..caac457d5 100644 --- a/src/store/modules/__tests__/layers.store.spec.js +++ b/src/store/modules/__tests__/layers.store.spec.js @@ -1,3 +1,6 @@ +import { expect } from 'chai' +import { beforeEach, describe, it } from 'vitest' + import AbstractLayer from '@/api/layers/AbstractLayer.class' import ExternalGroupOfLayers from '@/api/layers/ExternalGroupOfLayers.class' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' @@ -5,8 +8,6 @@ import GeoAdminWMSLayer from '@/api/layers/GeoAdminWMSLayer.class' import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' import LayerTimeConfig from '@/api/layers/LayerTimeConfig.class' import store from '@/store' -import { expect } from 'chai' -import { beforeEach, describe, it } from 'vitest' const bgLayer = new GeoAdminWMTSLayer( 'background', diff --git a/src/store/modules/__tests__/rotation.store.spec.js b/src/store/modules/__tests__/rotation.store.spec.js index 1916ad960..ea6a01169 100644 --- a/src/store/modules/__tests__/rotation.store.spec.js +++ b/src/store/modules/__tests__/rotation.store.spec.js @@ -1,8 +1,9 @@ -import store from '@/store' -import { normalizeAngle } from '@/store/modules/position.store' import { expect } from 'chai' import { describe, it } from 'vitest' +import store from '@/store' +import { normalizeAngle } from '@/store/modules/position.store' + function validateNormalizeAngle(angle) { expect(angle).to.be.lte(Math.PI) expect(angle).to.be.gt(-Math.PI) diff --git a/src/store/modules/__tests__/zoom.store.spec.js b/src/store/modules/__tests__/zoom.store.spec.js index 084d32f99..ae817032a 100644 --- a/src/store/modules/__tests__/zoom.store.spec.js +++ b/src/store/modules/__tests__/zoom.store.spec.js @@ -1,9 +1,10 @@ -import store from '@/store' -import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import { expect } from 'chai' import proj4 from 'proj4' import { beforeEach, describe, it } from 'vitest' +import store from '@/store' +import { WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' + describe('Zoom level is calculated correctly in the store when using WebMercator as the projection', () => { const screenSize = 100 const lat = 45 diff --git a/src/store/modules/position.store.js b/src/store/modules/position.store.js index 4f355b2d5..6e16a6856 100644 --- a/src/store/modules/position.store.js +++ b/src/store/modules/position.store.js @@ -1,3 +1,5 @@ +import proj4 from 'proj4' + import { DEFAULT_PROJECTION } from '@/config' import CoordinateSystem from '@/utils/coordinates/CoordinateSystem.class' import allCoordinateSystems, { LV95, WGS84 } from '@/utils/coordinates/coordinateSystems' @@ -5,7 +7,6 @@ import CustomCoordinateSystem from '@/utils/coordinates/CustomCoordinateSystem.c import StandardCoordinateSystem from '@/utils/coordinates/StandardCoordinateSystem.class' import log from '@/utils/logging' import { round } from '@/utils/numberUtils' -import proj4 from 'proj4' /** @enum */ export const CrossHairs = { diff --git a/src/store/plugins/geolocation-management.plugin.js b/src/store/plugins/geolocation-management.plugin.js index 7200c91d4..f2e4126cb 100644 --- a/src/store/plugins/geolocation-management.plugin.js +++ b/src/store/plugins/geolocation-management.plugin.js @@ -1,8 +1,9 @@ +import proj4 from 'proj4' + import { IS_TESTING_WITH_CYPRESS } from '@/config' import i18n from '@/modules/i18n' import { WGS84 } from '@/utils/coordinates/coordinateSystems' import log from '@/utils/logging' -import proj4 from 'proj4' let geolocationWatcher = null let firstTimeActivatingGeolocation = true 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 0661d2492..37005456a 100644 --- a/src/store/plugins/load-geojson-style-and-data.plugin.js +++ b/src/store/plugins/load-geojson-style-and-data.plugin.js @@ -3,9 +3,10 @@ * it here */ +import axios from 'axios' + import GeoAdminGeoJsonLayer from '@/api/layers/GeoAdminGeoJsonLayer.class' import log from '@/utils/logging' -import axios from 'axios' async function load(url) { try { diff --git a/src/store/plugins/reproject-selected-features-on-projection-change.plugin.js b/src/store/plugins/reproject-selected-features-on-projection-change.plugin.js index ac308c0fe..bb714d7b6 100644 --- a/src/store/plugins/reproject-selected-features-on-projection-change.plugin.js +++ b/src/store/plugins/reproject-selected-features-on-projection-change.plugin.js @@ -1,7 +1,8 @@ +import proj4 from 'proj4' + import { EditableFeature, LayerFeature } from '@/api/features.api' import { projExtent } from '@/utils/coordinates/coordinateUtils' import log from '@/utils/logging' -import proj4 from 'proj4' let oldProjection = null /** diff --git a/src/store/plugins/sync-camera-lonlatzoom.js b/src/store/plugins/sync-camera-lonlatzoom.js index b324f4004..54fd91a7e 100644 --- a/src/store/plugins/sync-camera-lonlatzoom.js +++ b/src/store/plugins/sync-camera-lonlatzoom.js @@ -1,7 +1,8 @@ +import proj4 from 'proj4' + import { calculateResolution } from '@/modules/map/components/cesium/utils/cameraUtils' import { normalizeAngle } from '@/store/modules/position.store' import { WGS84 } from '@/utils/coordinates/coordinateSystems' -import proj4 from 'proj4' /** * Plugin to synchronize the 3d camera position and orientation with the center and zoom. diff --git a/src/utils/__tests__/geodesicManager.spec.js b/src/utils/__tests__/geodesicManager.spec.js index 072dfcc30..507433647 100644 --- a/src/utils/__tests__/geodesicManager.spec.js +++ b/src/utils/__tests__/geodesicManager.spec.js @@ -1,10 +1,11 @@ -import { HALFSIZE_WEBMERCATOR, GeodesicGeometries } from '@/utils/geodesicManager' -import { WEBMERCATOR } from '@/utils/coordinates/coordinateSystems' -import { Feature } from 'ol' import { expect } from 'chai' +import { Feature } from 'ol' import { LineString, MultiLineString, MultiPolygon } from 'ol/geom' -import { describe, it } from 'vitest' import { Style } from 'ol/style' +import { describe, it } from 'vitest' + +import { WEBMERCATOR } from '@/utils/coordinates/coordinateSystems' +import { GeodesicGeometries, HALFSIZE_WEBMERCATOR } from '@/utils/geodesicManager' function constructGeodLineString(...coords) { const feature = new Feature(new LineString(coords)) diff --git a/src/utils/__tests__/legacyKmlUtils.spec.js b/src/utils/__tests__/legacyKmlUtils.spec.js index 58b09642d..fd9a16c81 100644 --- a/src/utils/__tests__/legacyKmlUtils.spec.js +++ b/src/utils/__tests__/legacyKmlUtils.spec.js @@ -1,5 +1,11 @@ +import { expect } from 'chai' +import { readFileSync } from 'fs' +import { resolve } from 'path' +import { beforeEach, describe, it } from 'vitest' + import { EditableFeatureTypes } from '@/api/features.api' import { DrawingIcon, DrawingIconSet } from '@/api/icon.api' +import { parseKml } from '@/modules/drawing/lib/drawingUtils' import { WEBMERCATOR } from '@/utils/coordinates/coordinateSystems' import { BLACK, @@ -13,11 +19,6 @@ import { WHITE, YELLOW, } from '@/utils/featureStyleUtils' -import { expect } from 'chai' -import { readFileSync } from 'fs' -import { resolve } from 'path' -import { beforeEach, describe, it } from 'vitest' -import { parseKml } from '@/modules/drawing/lib/drawingUtils' const fakeDefaultIconSet = new DrawingIconSet( 'default', diff --git a/src/utils/__tests__/legacyLayerParamUtils.spec.js b/src/utils/__tests__/legacyLayerParamUtils.spec.js index d1cc9e9c4..fcec84058 100644 --- a/src/utils/__tests__/legacyLayerParamUtils.spec.js +++ b/src/utils/__tests__/legacyLayerParamUtils.spec.js @@ -1,3 +1,6 @@ +import { expect } from 'chai' +import { describe, it } from 'vitest' + import { LayerAttribution } from '@/api/layers/AbstractLayer.class' import ExternalWMSLayer from '@/api/layers/ExternalWMSLayer.class' import ExternalWMTSLayer from '@/api/layers/ExternalWMTSLayer.class' @@ -6,8 +9,6 @@ import GeoAdminWMTSLayer from '@/api/layers/GeoAdminWMTSLayer.class' import LayerTimeConfig from '@/api/layers/LayerTimeConfig.class' import LayerTimeConfigEntry from '@/api/layers/LayerTimeConfigEntry.class' import { getLayersFromLegacyUrlParams, isLayersUrlParamLegacy } from '@/utils/legacyLayerParamUtils' -import { expect } from 'chai' -import { describe, it } from 'vitest' describe('Test parsing of legacy URL param into new params', () => { describe('test getLayersFromLegacyUrlParams', () => { diff --git a/src/utils/__tests__/numberUtils.spec.js b/src/utils/__tests__/numberUtils.spec.js index 784e1a539..03a546d8e 100644 --- a/src/utils/__tests__/numberUtils.spec.js +++ b/src/utils/__tests__/numberUtils.spec.js @@ -1,7 +1,8 @@ -import { format, formatThousand, isNumber, randomIntBetween, round } from '@/utils/numberUtils' import { expect } from 'chai' import { describe, it } from 'vitest' +import { format, formatThousand, isNumber, randomIntBetween, round } from '@/utils/numberUtils' + describe('Unit test functions from numberUtils.js', () => { describe('round(value, decimals)', () => { const numberToRound = 123.456789 diff --git a/src/utils/__tests__/urlQuery.spec.js b/src/utils/__tests__/urlQuery.spec.js index 9ca35fe0a..62f11ffd0 100644 --- a/src/utils/__tests__/urlQuery.spec.js +++ b/src/utils/__tests__/urlQuery.spec.js @@ -1,7 +1,7 @@ -import { describe, it } from 'vitest' import { expect } from 'chai' +import { describe, it } from 'vitest' -import { stringifyQuery, parseQuery } from '../url-router' +import { parseQuery, stringifyQuery } from '../url-router' describe('Unit test function for parseQuery', () => { it('Decode + as space', () => { diff --git a/src/utils/coordinates/CoordinateSystem.class.js b/src/utils/coordinates/CoordinateSystem.class.js index c755d4437..800ebdaf2 100644 --- a/src/utils/coordinates/CoordinateSystem.class.js +++ b/src/utils/coordinates/CoordinateSystem.class.js @@ -1,6 +1,7 @@ -import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' import proj4 from 'proj4' +import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' + /** * Representation of a coordinate system (or also called projection system) in the context of this * application. diff --git a/src/utils/coordinates/WebMercatorCoordinateSystem.class.js b/src/utils/coordinates/WebMercatorCoordinateSystem.class.js index 049477f41..5204f82d1 100644 --- a/src/utils/coordinates/WebMercatorCoordinateSystem.class.js +++ b/src/utils/coordinates/WebMercatorCoordinateSystem.class.js @@ -1,10 +1,11 @@ +import proj4 from 'proj4' + import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' import { WGS84 } from '@/utils/coordinates/coordinateSystems' import StandardCoordinateSystem, { PIXEL_LENGTH_IN_KM_AT_ZOOM_ZERO_WITH_256PX_TILES, } from '@/utils/coordinates/StandardCoordinateSystem.class' import { round } from '@/utils/numberUtils' -import proj4 from 'proj4' export default class WebMercatorCoordinateSystem extends StandardCoordinateSystem { constructor() { diff --git a/src/utils/coordinates/__test__/CoordinateSystem.class.spec.js b/src/utils/coordinates/__test__/CoordinateSystem.class.spec.js index 53f40c9ca..9c09d82d3 100644 --- a/src/utils/coordinates/__test__/CoordinateSystem.class.spec.js +++ b/src/utils/coordinates/__test__/CoordinateSystem.class.spec.js @@ -1,7 +1,8 @@ +import { describe, expect, it } from 'vitest' + import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' import { LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import StandardCoordinateSystem from '@/utils/coordinates/StandardCoordinateSystem.class' -import { describe, expect, it } from 'vitest' describe('CoordinateSystem', () => { const coordinateSystemWithouBounds = new StandardCoordinateSystem('test', 'test', 1234, null) diff --git a/src/utils/coordinates/__test__/CoordinateSystemBounds.class.spec.js b/src/utils/coordinates/__test__/CoordinateSystemBounds.class.spec.js index 1f944df47..43a3d5131 100644 --- a/src/utils/coordinates/__test__/CoordinateSystemBounds.class.spec.js +++ b/src/utils/coordinates/__test__/CoordinateSystemBounds.class.spec.js @@ -1,7 +1,8 @@ -import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' import { expect } from 'chai' import { beforeEach, describe, it } from 'vitest' +import CoordinateSystemBounds from '@/utils/coordinates/CoordinateSystemBounds.class' + describe('CoordinateSystemBounds', () => { describe('splitIfOutOfBounds(coordinates, bounds)', () => { let bounds diff --git a/src/utils/coordinates/__test__/SwissCoordinateSystem.class.spec.js b/src/utils/coordinates/__test__/SwissCoordinateSystem.class.spec.js index 5ec7f63a2..b37965bc1 100644 --- a/src/utils/coordinates/__test__/SwissCoordinateSystem.class.spec.js +++ b/src/utils/coordinates/__test__/SwissCoordinateSystem.class.spec.js @@ -1,9 +1,10 @@ +import { describe, expect, it } from 'vitest' + import { LV03, LV95 } from '@/utils/coordinates/coordinateSystems' import { LV95_RESOLUTIONS, swissPyramidZoomToStandardZoomMatrix, } from '@/utils/coordinates/SwissCoordinateSystem.class' -import { describe, expect, it } from 'vitest' describe('Unit test functions from SwissCoordinateSystem', () => { describe('transformCustomZoomLevelToStandard', () => { diff --git a/src/utils/coordinates/__test__/coordinateExtractors.spec.js b/src/utils/coordinates/__test__/coordinateExtractors.spec.js index b5212d62a..8768e4984 100644 --- a/src/utils/coordinates/__test__/coordinateExtractors.spec.js +++ b/src/utils/coordinates/__test__/coordinateExtractors.spec.js @@ -1,10 +1,11 @@ -import coordinateFromString from '@/utils/coordinates/coordinateExtractors' -import { LV03, LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import { expect } from 'chai' import { toStringHDMS } from 'ol/coordinate' import proj4 from 'proj4' import { describe, it } from 'vitest' +import coordinateFromString from '@/utils/coordinates/coordinateExtractors' +import { LV03, LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' + /** * Place a separator after each group of 3 digit * diff --git a/src/utils/coordinates/__test__/coordinateUtils.spec.js b/src/utils/coordinates/__test__/coordinateUtils.spec.js index cd70d62f8..24f88d152 100644 --- a/src/utils/coordinates/__test__/coordinateUtils.spec.js +++ b/src/utils/coordinates/__test__/coordinateUtils.spec.js @@ -1,9 +1,10 @@ -import { LV03, LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' -import { reprojectUnknownSrsCoordsToWGS84 } from '@/utils/coordinates/coordinateUtils' import { expect } from 'chai' import proj4 from 'proj4' import { describe, it } from 'vitest' +import { LV03, LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' +import { reprojectUnknownSrsCoordsToWGS84 } from '@/utils/coordinates/coordinateUtils' + describe('Unit test functions from coordinateUtils.js', () => { describe('reprojectUnknownSrsCoordsToWGS84(x,y)', () => { const coordinatesLV95 = [2600000, 1190000] diff --git a/src/utils/coordinates/coordinateExtractors.js b/src/utils/coordinates/coordinateExtractors.js index 78ed076db..002253d08 100644 --- a/src/utils/coordinates/coordinateExtractors.js +++ b/src/utils/coordinates/coordinateExtractors.js @@ -1,7 +1,8 @@ +import proj4 from 'proj4' + import { WGS84 } from '@/utils/coordinates/coordinateSystems' import { reprojectUnknownSrsCoordsToWGS84 } from '@/utils/coordinates/coordinateUtils' import { toPoint as mgrsToWGS84 } from '@/utils/militaryGridProjection' -import proj4 from 'proj4' // 47.5 7.5 const REGEX_WEB_MERCATOR = /^\s*([\d]{1,3}[.\d]+)\s*[ ,/]+\s*([\d]{1,3}[.\d]+)\s*$/i diff --git a/src/utils/coordinates/coordinateFormat.js b/src/utils/coordinates/coordinateFormat.js index 0bdf8ea50..0e571caf7 100644 --- a/src/utils/coordinates/coordinateFormat.js +++ b/src/utils/coordinates/coordinateFormat.js @@ -1,9 +1,10 @@ +import { format as formatCoordinate, toStringHDMS } from 'ol/coordinate' +import proj4 from 'proj4' + import { LV03, LV95, WEBMERCATOR, WGS84 } from '@/utils/coordinates/coordinateSystems' import { toRoundedString } from '@/utils/coordinates/coordinateUtils' import { latLonToMGRS, latLonToUTM } from '@/utils/militaryGridProjection' import { formatThousand } from '@/utils/numberUtils' -import { format as formatCoordinate, toStringHDMS } from 'ol/coordinate' -import proj4 from 'proj4' /** Representation of coordinates in a human-readable format */ export class CoordinateFormat { diff --git a/src/utils/coordinates/coordinateUtils.js b/src/utils/coordinates/coordinateUtils.js index 45986b82b..9e481ca42 100644 --- a/src/utils/coordinates/coordinateUtils.js +++ b/src/utils/coordinates/coordinateUtils.js @@ -1,4 +1,5 @@ import proj4 from 'proj4' + import log from '../logging' import { formatThousand } from '../numberUtils' import { LV03, LV95, WEBMERCATOR, WGS84 } from './coordinateSystems' diff --git a/src/utils/geoJsonUtils.js b/src/utils/geoJsonUtils.js index 02153b4d8..baca19f7d 100644 --- a/src/utils/geoJsonUtils.js +++ b/src/utils/geoJsonUtils.js @@ -1,6 +1,7 @@ +import { reproject } from 'reproject' + import CoordinateSystem from '@/utils/coordinates/CoordinateSystem.class' import { WGS84 } from '@/utils/coordinates/coordinateSystems' -import { reproject } from 'reproject' /** * Re-projecting the GeoJSON if not in the wanted projection diff --git a/src/utils/geodesicManager.js b/src/utils/geodesicManager.js index 5e7f0988a..44806b9d0 100644 --- a/src/utils/geodesicManager.js +++ b/src/utils/geodesicManager.js @@ -1,6 +1,3 @@ -import log from '@/utils/logging' -import { formatAngle, formatMeters } from '@/modules/drawing/lib/drawingUtils' -import { WGS84 } from '@/utils/coordinates/coordinateSystems' import { Geodesic, Math as geographicMath, PolygonArea } from 'geographiclib-geodesic' import { boundingExtent, @@ -13,6 +10,10 @@ import RBush from 'ol/structs/RBush' /* Warning: private class of openlayers */ import { Circle, Fill, RegularShape, Stroke, Style, Text } from 'ol/style' import proj4 from 'proj4' +import { formatAngle, formatMeters } from '@/modules/drawing/lib/drawingUtils' +import { WGS84 } from '@/utils/coordinates/coordinateSystems' +import log from '@/utils/logging' + const geod = Geodesic.WGS84 /* This means that the wgs map extent [-180,-90,180,90] would be diff --git a/src/utils/identifyOnVectorLayer.js b/src/utils/identifyOnVectorLayer.js index 86d3c61fa..d8cee1265 100644 --- a/src/utils/identifyOnVectorLayer.js +++ b/src/utils/identifyOnVectorLayer.js @@ -1,10 +1,11 @@ +import distance from '@turf/distance' +import { point } from '@turf/helpers' +import proj4 from 'proj4' + import { LayerFeature } from '@/api/features.api' import { WGS84 } from '@/utils/coordinates/coordinateSystems' import reprojectGeoJsonData from '@/utils/geoJsonUtils' import log from '@/utils/logging' -import distance from '@turf/distance' -import { point } from '@turf/helpers' -import proj4 from 'proj4' const pixelToleranceForIdentify = 10 diff --git a/src/utils/legacyKmlUtils.js b/src/utils/legacyKmlUtils.js index 876c005c9..2903dbb94 100644 --- a/src/utils/legacyKmlUtils.js +++ b/src/utils/legacyKmlUtils.js @@ -1,12 +1,13 @@ -import { EditableFeature, EditableFeatureTypes } from '@/api/features.api' -import { DrawingIcon } from '@/api/icon.api' -import { allStylingColors, allStylingSizes, MEDIUM, RED, SMALL } from '@/utils/featureStyleUtils' -import log from '@/utils/logging' import Feature from 'ol/Feature' import { getDefaultStyle } from 'ol/format/KML' import IconStyle from 'ol/style/Icon' import Style from 'ol/style/Style' +import { EditableFeature, EditableFeatureTypes } from '@/api/features.api' +import { DrawingIcon } from '@/api/icon.api' +import { allStylingColors, allStylingSizes, MEDIUM, RED, SMALL } from '@/utils/featureStyleUtils' +import log from '@/utils/logging' + /** * This is a helper function for {@link deserialize} that generates an editable feature based on the * style stored in the kml ' diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue new file mode 100644 index 000000000..c52e2d74c --- /dev/null +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue new file mode 100644 index 000000000..bca6136a2 --- /dev/null +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue new file mode 100644 index 000000000..c8103cb31 --- /dev/null +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/modules/menu/components/advancedTools/ImportFile/utils.js b/src/modules/menu/components/advancedTools/ImportFile/utils.js new file mode 100644 index 000000000..ea9a520af --- /dev/null +++ b/src/modules/menu/components/advancedTools/ImportFile/utils.js @@ -0,0 +1,41 @@ +import KMLLayer from '@/api/layers/KMLLayer.class' + +/** + * Checks if file is KML + * + * @param {string} fileContent + * @returns {boolean} + */ +export function isKml(fileContent) { + return //.test(fileContent) +} + +/** + * Checks if file is GPX + * + * @param {string} fileContent + * @returns {boolean} + */ +export function isGpx(fileContent) { + return //.test(fileContent) +} + +/** + * Handle file content + * + * @param {string} content Content of the file + * @param {string} source Source of the file (either URL or file path) + * @returns + */ +export function handleFileContent(content, source) { + let layer = null + if (isKml(content)) { + // TODO just for test + layer = new KMLLayer(source, true, 1, null, null, null, null, true /* isExternal */) + } else if (isGpx(content)) { + // TODO GPX layer not done yet + } else { + throw new Error(`Unsupported file ${source} content`) + } + return layer +} diff --git a/src/modules/menu/components/advancedTools/MenuAdvancedToolsList.vue b/src/modules/menu/components/advancedTools/MenuAdvancedToolsList.vue index e30316d90..07a197c8b 100644 --- a/src/modules/menu/components/advancedTools/MenuAdvancedToolsList.vue +++ b/src/modules/menu/components/advancedTools/MenuAdvancedToolsList.vue @@ -1,28 +1,46 @@ @@ -39,23 +65,5 @@ export default { .advanced-tools-list { list-style-type: none; margin-bottom: 0; - - .advanced-tools-item { - .advanced-tools-title { - display: block; - color: black; - text-decoration: none; - cursor: pointer; - border-bottom-width: 1px; - border-bottom-style: solid; - border-bottom-color: #e9e9e9; - height: 2.75em; - line-height: 2.75em; - } - .advanced-tools-title:hover, - .advanced-tools-title:focus { - color: #666; - } - } } diff --git a/src/modules/menu/components/advancedTools/MenuAdvancedToolsListItem.vue b/src/modules/menu/components/advancedTools/MenuAdvancedToolsListItem.vue new file mode 100644 index 000000000..c84c2bc9e --- /dev/null +++ b/src/modules/menu/components/advancedTools/MenuAdvancedToolsListItem.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/store/modules/ui.store.js b/src/store/modules/ui.store.js index da58790bb..4b635c73c 100644 --- a/src/store/modules/ui.store.js +++ b/src/store/modules/ui.store.js @@ -97,6 +97,12 @@ export default { * @type Boolean */ importOverlay: false, + /** + * Flag telling if import file (map tooltip overlay or infobox) is shown + * + * @type Boolean + */ + importFile: false, }, getters: { screenDensity(state) { @@ -210,6 +216,9 @@ export default { toggleImportOverlay({ commit, state }) { commit('setImportOverlay', !state.importOverlay) }, + toggleImportFile({ commit, state }) { + commit('setImportFile', !state.importFile) + }, }, mutations: { setSize(state, { height, width }) { @@ -240,5 +249,8 @@ export default { setImportOverlay(state, flagValue) { state.importOverlay = flagValue }, + setImportFile(state, flagValue) { + state.importFile = flagValue + }, }, } diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 000000000..05e7b1bb4 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,17 @@ +/** + * Check if the provided string is a valid URL + * + * @param {string} urlToCheck + * @returns {boolean} True if valid, false otherwise + */ +export function isValidUrl(urlToCheck) { + let url + + try { + url = new URL(urlToCheck) + } catch (_) { + return false + } + + return url.protocol === 'http:' || url.protocol === 'https:' +} From 06af1b1449ca587c0f3ad4f66196ac622bbcb580 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 6 Dec 2023 06:47:31 +0100 Subject: [PATCH 035/377] BGDIINF_SB-3194: Code improvement based on review --- .../components/advancedTools/ImportFile/ImportFile.vue | 10 +++------- .../advancedTools/ImportFile/ImportFileButtons.vue | 4 ++-- .../advancedTools/ImportFile/ImportFileLocalTab.vue | 5 +++-- .../advancedTools/ImportFile/ImportFileOnlineTab.vue | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue index 581f2ef67..f0b5bcc63 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue @@ -1,8 +1,8 @@ @@ -16,14 +16,12 @@ const selectedTab = ref('online') :class="{ active: selectedTab === 'online', }" - data-bs-toggle="tab" - data-bs-target="#nav-online" type="button" role="tab" aria-controls="nav-online" :aria-selected="selectedTab === 'online'" data-cy="online-import-btn" - @click="() => (selectedTab = 'online')" + @click="selectedTab = 'online'" > Online @@ -34,14 +32,12 @@ const selectedTab = ref('online') :class="{ active: selectedTab === 'local', }" - data-bs-toggle="tab" - data-bs-target="#nav-local" type="button" role="tab" aria-controls="nav-local" :aria-selected="selectedTab === 'local'" data-cy="local-import-btn" - @click="() => (selectedTab = 'local')" + @click="selectedTab = 'local'" > Local diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue index c52e2d74c..8924a39ae 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue @@ -1,7 +1,7 @@ From 3d32741a878b56e74a48885eaffd71f295ebcaee Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 6 Dec 2023 12:09:21 +0100 Subject: [PATCH 036/377] BGDIINF_SB-3194: Sets the geoadmin tabs style globally --- .../menu/components/advancedTools/ImportFile/ImportFile.vue | 6 ------ src/scss/webmapviewer-bootstrap-theme.scss | 5 ++++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue index f0b5bcc63..b4c5be541 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFile.vue @@ -58,10 +58,4 @@ const selectedTab = ref('online') diff --git a/src/scss/webmapviewer-bootstrap-theme.scss b/src/scss/webmapviewer-bootstrap-theme.scss index 12529735b..f547d5d95 100644 --- a/src/scss/webmapviewer-bootstrap-theme.scss +++ b/src/scss/webmapviewer-bootstrap-theme.scss @@ -27,7 +27,10 @@ $list-item-hover-color: $gray-300; $map-button-border-color: $gray-800; $map-button-hover-border-color: $gray-700; -// Override bootstrap class here +// Override bootstrap variable/class here +$nav-link-color: $black; +$nav-tabs-link-active-color: $white; +$nav-tabs-link-active-bg: $primary; // Add our custom class here .btn-outline-group { From 402cfafdfee684fed1cb499b4cd0c12ac5ce2f81 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Wed, 6 Dec 2023 13:06:30 +0100 Subject: [PATCH 037/377] BGDIINF_SB-3194: Use composable for import button state --- .../advancedTools/ImportFile/ImportFileLocalTab.vue | 10 +++++----- .../advancedTools/ImportFile/ImportFileOnlineTab.vue | 10 ++++------ .../menu/components/advancedTools/importButton.js | 11 +++++++++++ 3 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 src/modules/menu/components/advancedTools/importButton.js diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue index 02a347d6e..b56fac9dd 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileLocalTab.vue @@ -2,11 +2,10 @@ import { computed, ref } from 'vue' import { useI18n } from 'vue-i18n' -import log from '@/utils/logging' -import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils' +import { useImportButton } from '@/modules/menu/components/advancedTools/importButton' import ImportFileButtons from '@/modules/menu/components/advancedTools/ImportFile/ImportFileButtons.vue' - -const BTN_RESET_TIMEOUT = 3000 // milliseconds +import { handleFileContent } from '@/modules/menu/components/advancedTools/ImportFile/utils' +import log from '@/utils/logging' const LOCAL_UPLOAD_ACCEPT = '.kml,.KML,.gpx,.GPX' const LOCAL_UPLOAD_MAX_SIZE = 250 * 1024 * 1024 // 250mb @@ -19,6 +18,8 @@ const importFileLocalInput = ref(null) const selectedFile = ref(null) const errorMessage = ref(null) +useImportButton(buttonState) + // Computed properties const isValid = computed(() => !errorMessage.value && selectedFile.value) const isInvalid = computed(() => errorMessage.value) @@ -61,7 +62,6 @@ async function loadFile() { if (!errorMessage.value) { buttonState.value = 'succeeded' - setTimeout(() => (buttonState.value = 'default'), BTN_RESET_TIMEOUT) } else { buttonState.value = 'default' } diff --git a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue index 7b8abbff6..6737ddaff 100644 --- a/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue +++ b/src/modules/menu/components/advancedTools/ImportFile/ImportFileOnlineTab.vue @@ -1,14 +1,13 @@ - diff --git a/src/modules/drawing/components/DrawingExporter.vue b/src/modules/drawing/components/DrawingExporter.vue index 9f25e0cb6..c1d920f46 100644 --- a/src/modules/drawing/components/DrawingExporter.vue +++ b/src/modules/drawing/components/DrawingExporter.vue @@ -1,19 +1,8 @@ - - - + + diff --git a/src/modules/drawing/components/DrawingHeader.vue b/src/modules/drawing/components/DrawingHeader.vue index 5e1c766c2..baca62ff2 100644 --- a/src/modules/drawing/components/DrawingHeader.vue +++ b/src/modules/drawing/components/DrawingHeader.vue @@ -1,22 +1,22 @@ + + - - diff --git a/src/modules/drawing/components/DrawingLineInteraction.vue b/src/modules/drawing/components/DrawingLineInteraction.vue index eec50255c..d7881a153 100644 --- a/src/modules/drawing/components/DrawingLineInteraction.vue +++ b/src/modules/drawing/components/DrawingLineInteraction.vue @@ -1,38 +1,31 @@ - + - + diff --git a/src/modules/drawing/components/DrawingMarkerInteraction.vue b/src/modules/drawing/components/DrawingMarkerInteraction.vue index 7fef8ce0c..9436ecbc0 100644 --- a/src/modules/drawing/components/DrawingMarkerInteraction.vue +++ b/src/modules/drawing/components/DrawingMarkerInteraction.vue @@ -1,35 +1,37 @@ - + - + diff --git a/src/modules/drawing/components/DrawingMeasureInteraction.vue b/src/modules/drawing/components/DrawingMeasureInteraction.vue index b8370d014..0116ef668 100644 --- a/src/modules/drawing/components/DrawingMeasureInteraction.vue +++ b/src/modules/drawing/components/DrawingMeasureInteraction.vue @@ -1,39 +1,31 @@ - + - + diff --git a/src/modules/drawing/components/DrawingModifyInteraction.vue b/src/modules/drawing/components/DrawingModifyInteraction.vue deleted file mode 100644 index 5d1cfc5bb..000000000 --- a/src/modules/drawing/components/DrawingModifyInteraction.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - diff --git a/src/modules/drawing/components/DrawingSelectInteraction.vue b/src/modules/drawing/components/DrawingSelectInteraction.vue index 178014eb6..011630672 100644 --- a/src/modules/drawing/components/DrawingSelectInteraction.vue +++ b/src/modules/drawing/components/DrawingSelectInteraction.vue @@ -1,132 +1,106 @@ - + + + diff --git a/src/modules/drawing/components/DrawingTextInteraction.vue b/src/modules/drawing/components/DrawingTextInteraction.vue index b78f37b47..1774a1b7b 100644 --- a/src/modules/drawing/components/DrawingTextInteraction.vue +++ b/src/modules/drawing/components/DrawingTextInteraction.vue @@ -1,29 +1,35 @@ - - - + + diff --git a/src/modules/drawing/components/DrawingToolbox.vue b/src/modules/drawing/components/DrawingToolbox.vue index 33ecec5f1..207c0e438 100644 --- a/src/modules/drawing/components/DrawingToolbox.vue +++ b/src/modules/drawing/components/DrawingToolbox.vue @@ -1,6 +1,86 @@ + + - - diff --git a/src/utils/ModalWithBackdrop.vue b/src/utils/ModalWithBackdrop.vue index 71a144b05..24d2c5c49 100644 --- a/src/utils/ModalWithBackdrop.vue +++ b/src/utils/ModalWithBackdrop.vue @@ -3,7 +3,7 @@
- +