From 30e0475407cfb6811b26c43fb94f2445b11b4b1b Mon Sep 17 00:00:00 2001 From: Yves Choquette Date: Mon, 30 Sep 2024 15:56:42 -0400 Subject: [PATCH] 2529-More corrections for add layer and the config API --- .../{metadata-new.meta => metadata.meta} | 120 ++++++++++++------ .../public/templates/layers/geojson.html | 18 +-- .../public/templates/layers/layerlib.js | 58 ++++----- .../abstract-geoview-esri-layer-config.ts | 10 +- .../abstract-geoview-layer-config.ts | 6 +- .../raster-config/wms-config.ts | 3 +- .../vector-config/geojson-config.ts | 17 +-- .../entry-config-base-class.ts | 32 +++++ .../group-node/esri-group-layer-config.ts | 23 ++-- .../group-node/geojson-group-layer-config.ts | 12 +- .../group-node/wfs-group-layer-config.ts | 8 ++ .../group-node/wms-group-layer-config.ts | 13 +- .../abstract-base-esri-layer-entry-config.ts | 56 ++++---- .../leaf/raster/wms-layer-entry-config.ts | 13 +- .../leaf/vector/geojson-layer-entry-config.ts | 15 ++- .../leaf/vector/wfs-layer-entry-config.ts | 63 ++++----- .../geoview-layers/raster/esri-dynamic.ts | 4 +- .../layer/geoview-layers/raster/esri-image.ts | 4 +- .../layer/geoview-layers/vector/geojson.ts | 38 ++++-- 19 files changed, 304 insertions(+), 209 deletions(-) rename packages/geoview-core/public/datasets/geojson/{metadata-new.meta => metadata.meta} (79%) diff --git a/packages/geoview-core/public/datasets/geojson/metadata-new.meta b/packages/geoview-core/public/datasets/geojson/metadata.meta similarity index 79% rename from packages/geoview-core/public/datasets/geojson/metadata-new.meta rename to packages/geoview-core/public/datasets/geojson/metadata.meta index 7b6a31e1b3a..05bc17984da 100644 --- a/packages/geoview-core/public/datasets/geojson/metadata-new.meta +++ b/packages/geoview-core/public/datasets/geojson/metadata.meta @@ -126,12 +126,27 @@ "color": "rgba(0,0,255,0.5)", "paternSize": 10, "paternWidth": 2, - "fillStyle": "diagonalCross", "stroke": { "color": "rgba(128,0,0,1)", "lineStyle": "dot" } } + }, + { + "label": "Default", + "visible": true, + "values": ["Default"], + "settings": { + "type": "filledPolygon", + "color": "rgba(0,0,255,0.5)", + "paternSize": 10, + "paternWidth": 2, + "fillStyle": "diagonalCross", + "stroke": { + "color": "rgba(0,0,128,1)", + "lineStyle": "dot" + } + } } ] } @@ -168,14 +183,18 @@ }, "style": { "type": "simple", - "label": "LineString label", - "settings": { - "type": "lineString", - "stroke": { - "lineStyle": "shortDash-dot-dot", - "color": "rgba(128,0,0,1)" + "fields": ["LineString label"], + "info": [ + { + "settings": { + "type": "lineString", + "stroke": { + "lineStyle": "shortDash-dot-dot", + "color": "rgba(128,0,0,1)" + } + } } - } + ] } }, { @@ -215,14 +234,18 @@ } }, "style": { - "styleType": "simple", - "label": "Icon point label", - "settings": { - "type": "iconSymbol", - "mimeType": "image/png", - "src": "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAcNJREFUKJFjYSATsCBz/v//z/Tp8+fwO/cf273/+FmckZHxn6iQwBMVJbldXJyc27Bq/P//v8jVm3cmtUxf7rX+3nN+ZEVJ+srhd+8/3KikIFfMyMj4Fa7x////zBeu3JjuXDM55Ou//xjOmnfxrsS+8t7Uze1FLAwMDClwjU+fv0irmLzYH5smGHjw/TdTz7yVIe/ef9wiJMi/gYWBgYHhxp2HXgeev2dFVtjhb8Pw7dtPhqbdp+FiCy894E+4cz+IgYEBovHDp88qyJpmxngwxIf5wfnImt+9f68Ed+rff3/ZkTVycnDA2dxcHMhSDH/+/WeHa+Tm5HzJwMCgCJMsW7SZgYmZkeHHj18M5RsPo2jk4eJ+DdcoKSZyQoeX0+LK5+8MDAwMDC9+/WWImbkeI4D85MUZJMWED8A1qirKVjUlBvgETVqugqEa5mQmRoacKO9zWurKPXCNfHx8389evha5Njd8bfPCLXIXPn1F0eQgLshQGut7XUREOJCRkfEfXCMDAwODsa7WmQOnT+t150VMevn6ne33bz+kGBgZ/3FzcTyUEBXZxcb4p0RbTek3TD1KWnUwNf3IwMAQj8u5yAAAupehfivnXOEAAAAASUVORK5CYII=", - "opacity": 0.5 - } + "type": "simple", + "fields": ["Icon point label"], + "info": [ + { + "settings": { + "type": "iconSymbol", + "mimeType": "image/png", + "src": "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAcNJREFUKJFjYSATsCBz/v//z/Tp8+fwO/cf273/+FmckZHxn6iQwBMVJbldXJyc27Bq/P//v8jVm3cmtUxf7rX+3nN+ZEVJ+srhd+8/3KikIFfMyMj4Fa7x////zBeu3JjuXDM55Ou//xjOmnfxrsS+8t7Uze1FLAwMDClwjU+fv0irmLzYH5smGHjw/TdTz7yVIe/ef9wiJMi/gYWBgYHhxp2HXgeev2dFVtjhb8Pw7dtPhqbdp+FiCy894E+4cz+IgYEBovHDp88qyJpmxngwxIf5wfnImt+9f68Ed+rff3/ZkTVycnDA2dxcHMhSDH/+/WeHa+Tm5HzJwMCgCJMsW7SZgYmZkeHHj18M5RsPo2jk4eJ+DdcoKSZyQoeX0+LK5+8MDAwMDC9+/WWImbkeI4D85MUZJMWED8A1qirKVjUlBvgETVqugqEa5mQmRoacKO9zWurKPXCNfHx8389evha5Njd8bfPCLXIXPn1F0eQgLshQGut7XUREOJCRkfEfXCMDAwODsa7WmQOnT+t150VMevn6ne33bz+kGBgZ/3FzcTyUEBXZxcb4p0RbTek3TD1KWnUwNf3IwMAQj8u5yAAAupehfivnXOEAAAAASUVORK5CYII=", + "opacity": 0.5 + } + } + ] } }, { @@ -274,12 +297,16 @@ } }, "style": { - "styleType": "simple", - "label": "Point label", - "settings": { - "type": "simpleSymbol", - "symbol": "star" - } + "type": "simple", + "fields": ["Point label"], + "info": [ + { + "settings": { + "type": "simpleSymbol", + "symbol": "star" + } + } + ] } }, { @@ -336,12 +363,16 @@ } }, "style": { - "styleType": "simple", - "label": "Point label", - "settings": { - "type": "simpleSymbol", - "symbol": "star" - } + "type": "simple", + "fields": ["Point label"], + "info": [ + { + "settings": { + "type": "simpleSymbol", + "symbol": "star" + } + } + ] } }, { @@ -393,12 +424,16 @@ } }, "style": { - "styleType": "simple", - "label": "Point label", - "settings": { - "type": "simpleSymbol", - "symbol": "star" - } + "type": "simple", + "fields": ["Point label"], + "info": [ + { + "settings": { + "type": "simpleSymbol", + "symbol": "star" + } + } + ] } }, { @@ -450,12 +485,16 @@ } }, "style": { - "styleType": "simple", - "label": "Point label", - "settings": { - "type": "simpleSymbol", - "symbol": "star" - } + "type": "simple", + "fields": ["Point label"], + "info": [ + { + "settings": { + "type": "simpleSymbol", + "symbol": "star" + } + } + ] } } ] @@ -464,3 +503,4 @@ } ] } + diff --git a/packages/geoview-core/public/templates/layers/geojson.html b/packages/geoview-core/public/templates/layers/geojson.html index f63f19a6d0b..65d84f49d24 100644 --- a/packages/geoview-core/public/templates/layers/geojson.html +++ b/packages/geoview-core/public/templates/layers/geojson.html @@ -64,7 +64,7 @@

1. Many GeoJSON Layers

'projection': 3978 }, 'basemapOptions': { - 'basemapId': 'imagery', + 'basemapId': 'transport', 'shaded': false, 'labeled': false }, @@ -77,13 +77,11 @@

1. Many GeoJSON Layers

'listOfLayerEntryConfig': [ { 'layerId': 'polygons.json', - 'layerName': { 'en': 'Polygons' }, - 'layerFilter': 'creationDate >= date \'2020/02/15\'' + 'layerName': { 'en': 'Polygons' } }, { 'layerId': 'lines.json', - 'layerName': { 'en': 'Lines' }, - 'layerFilter': 'creationDate >= date \'2020-05-28T12:00:00-05:00\'' + 'layerName': { 'en': 'Lines' } }, { 'entryType': 'group', @@ -93,13 +91,11 @@

1. Many GeoJSON Layers

{ 'layerId': 'icon_points.json', 'layerName': { 'en': 'Icons' }, - 'initialSettings': { 'states': {'visible' : false} }, - 'layerFilter': 'creationDate >= date \'2020-01-14T12:00:01-05:00\'' + 'initialSettings': { 'states': {'visible' : false} } }, { 'layerId': 'points.json', - 'layerName': { 'en': 'Points' }, - 'layerFilter': 'creationDate >= date \'2019-02-15T22:00:00Z\'' + 'layerName': { 'en': 'Points' } } ] } @@ -112,7 +108,7 @@

1. Many GeoJSON Layers

'corePackages': [], 'theme': 'geo.ca' }"> - +

   
   
@@ -246,7 +242,7 @@ 

1. Many GeoJSON Layers

window.addEventListener('load', () => { createCodeSnippet(); createConfigSnippet(); - }); + }); diff --git a/packages/geoview-core/public/templates/layers/layerlib.js b/packages/geoview-core/public/templates/layers/layerlib.js index 19edc18b8bf..888d210dafc 100644 --- a/packages/geoview-core/public/templates/layers/layerlib.js +++ b/packages/geoview-core/public/templates/layers/layerlib.js @@ -351,37 +351,35 @@ const createTableOfFilter = (mapId) => { mapButtonsDiv.appendChild(br); } } - if (geoviewLayer.getLayerFilter(layerPath)) { - const layerFilterText = document.createElement('p'); - layerFilterText.innerText = `Extra filter: `; - mapButtonsDiv.appendChild(layerFilterText); - const layerFilterInput = document.createElement('input'); - layerFilterInput.id = `filter-input-${mapId}-${geoviewLayer.getGeoviewLayerId()}`; - layerFilterInput.style.width = '50%'; - layerFilterText.appendChild(layerFilterInput); - layerFilterInput.value = geoviewLayer.getLayerFilter(layerPath) || ''; - const layerFilterButton = document.createElement('button'); - layerFilterButton.addEventListener('click', (e) => { - const checkbox = document.getElementById(`checkbox-${mapId}-${geoviewLayer.getGeoviewLayerId()}`); - geoviewLayer.applyViewFilter(layerPath, layerFilterInput.value, checkbox.value !== 'true'); - }); - layerFilterButton.innerText = 'Apply'; - layerFilterText.style.width = 'max-content'; - layerFilterText.appendChild(layerFilterButton); + const layerFilterText = document.createElement('p'); + layerFilterText.innerText = `Extra filter: `; + mapButtonsDiv.appendChild(layerFilterText); + const layerFilterInput = document.createElement('input'); + layerFilterInput.id = `filter-input-${mapId}-${geoviewLayer.getGeoviewLayerId()}`; + layerFilterInput.style.width = '50%'; + layerFilterText.appendChild(layerFilterInput); + layerFilterInput.value = geoviewLayer.getLayerFilter(layerPath) || ''; + const layerFilterButton = document.createElement('button'); + layerFilterButton.addEventListener('click', (e) => { + const checkbox = document.getElementById(`checkbox-${mapId}-${geoviewLayer.getGeoviewLayerId()}`); + geoviewLayer.applyViewFilter(layerPath, layerFilterInput.value, checkbox.value !== 'true'); + }); + layerFilterButton.innerText = 'Apply'; + layerFilterText.style.width = 'max-content'; + layerFilterText.appendChild(layerFilterButton); - const checkboxInput = document.createElement('input'); - checkboxInput.type = 'checkbox'; - checkboxInput.value = 'false'; - checkboxInput.id = `checkbox-${mapId}-${geoviewLayer.getGeoviewLayerId()}`; - checkboxInput.addEventListener('click', (e) => { - checkboxInput.value = checkboxInput.value === 'true' ? 'false' : 'true'; - geoviewLayer.applyViewFilter(layerPath, layerFilterInput.value, checkboxInput.value !== 'true'); - }); - mapButtonsDiv.appendChild(checkboxInput); - const checkboxText = document.createElement('label'); - checkboxText.innerText = `apply only the extra filter`; - mapButtonsDiv.appendChild(checkboxText); - } + const checkboxInput = document.createElement('input'); + checkboxInput.type = 'checkbox'; + checkboxInput.value = 'false'; + checkboxInput.id = `checkbox-${mapId}-${geoviewLayer.getGeoviewLayerId()}`; + checkboxInput.addEventListener('click', (e) => { + checkboxInput.value = checkboxInput.value === 'true' ? 'false' : 'true'; + geoviewLayer.applyViewFilter(layerPath, layerFilterInput.value, checkboxInput.value !== 'true'); + }); + mapButtonsDiv.appendChild(checkboxInput); + const checkboxText = document.createElement('label'); + checkboxText.innerText = `apply only the extra filter`; + mapButtonsDiv.appendChild(checkboxText); }); } }); diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts index f7bc681bc2b..9d114b2d986 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-esri-layer-config.ts @@ -235,15 +235,15 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye }; // #endregion PRIVATE - // ================= - // #region PROTECTED + // ============== + // #region STATIC /** * Converts an esri geometry type string to a TypeStyleGeometry. * @param {string} esriGeometryType - The esri geometry type to convert * @returns {TypeStyleGeometry} The corresponding TypeStyleGeometry - * @protected @static + * @static */ - protected static convertEsriGeometryTypeToOLGeometryType(esriGeometryType: string): TypeStyleGeometry { + static convertEsriGeometryTypeToOLGeometryType(esriGeometryType: string): TypeStyleGeometry { switch (esriGeometryType) { case 'esriGeometryPoint': case 'esriGeometryMultipoint': @@ -260,7 +260,7 @@ export abstract class AbstractGeoviewEsriLayerConfig extends AbstractGeoviewLaye throw new Error(`Unsupported geometry type: ${esriGeometryType}`); } } - // #endregion PROTECTED + // #endregion STATIC // #endregion METHODS // #endregion CLASS HEADER } diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts index 614cccfdd3c..33295f438ab 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/abstract-geoview-layer-config.ts @@ -279,7 +279,9 @@ export abstract class AbstractGeoviewLayerConfig { if (layerTreeFilter !== undefined) { // When the filter is an empty array, we create the layer tree for all the metadata in the service metadata. if (layerTreeFilter.length === 0) { - this.setMetadataLayerTree(this.processListOfLayerEntryConfig(this.createLayerTreeFromServiceMetadata())); + const layerTree = this.processListOfLayerEntryConfig(this.createLayerTreeFromServiceMetadata()); + await this.fetchListOfLayerMetadata(layerTree); + this.setMetadataLayerTree(layerTree); } else { // When the filter contains one or many layer identifiers, we create the layer tree using only the specified layers. // If the filter contains several layer identifiers, we create a group layer, as the root of the tree must contain @@ -312,7 +314,7 @@ export abstract class AbstractGeoviewLayerConfig { /** * A recursive method to process the listOfLayerEntryConfig. The goal is to process each valid sublayer, searching the service's * metadata to verify the layer's existence and whether it is a layer group, in order to determine the node's final structure. - * If it is a layer group, it will be created. + * If the metadata indicate the node is a layer group, it will be created by the createLayerEntryNode. * * @param {EntryConfigBaseClass[]} listOfLayerEntryConfig the list of sublayers to process. * diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts index 6934cbb3aaf..85214d2e32c 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/raster-config/wms-config.ts @@ -276,7 +276,8 @@ export class WmsLayerConfig extends AbstractGeoviewLayerConfig { const response = await fetch(metadataUrl); const capabilitiesString = await response.text(); this.setServiceMetadata(parser.read(capabilitiesString)); - if (this.getServiceMetadata()) { + // GV: If this.getServiceMetadata() returns {}, we need to verify if the object is empty to conclude there is no metadata. + if (Object.keys(this.getServiceMetadata()).length) { this.#processMetadataInheritance(); this.metadataAccessPath = this.getServiceMetadata().Capability.Request.GetMap.DCPType[0].HTTP.Get.OnlineResource as string; } else throw new GeoviewLayerConfigError('Unable to read the metadata, value returned is empty.'); diff --git a/packages/geoview-core/src/api/config/types/classes/geoview-config/vector-config/geojson-config.ts b/packages/geoview-core/src/api/config/types/classes/geoview-config/vector-config/geojson-config.ts index 10ca706a0cc..e050e29cdb8 100644 --- a/packages/geoview-core/src/api/config/types/classes/geoview-config/vector-config/geojson-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/geoview-config/vector-config/geojson-config.ts @@ -48,7 +48,7 @@ export class GeoJsonLayerConfig extends AbstractGeoviewLayerConfig { const pathItemLength = metadataAccessPathItems.length; const lastPathItem = metadataAccessPathItems[pathItemLength - 1]; if (lastPathItem.toLowerCase().endsWith('.json') || lastPathItem.toLowerCase().endsWith('.geojson')) { - // The metadataAccessPath ends with a layer index. It is therefore a path to a data layer rather than a path to service metadata. + // The metadataAccessPath ends with a layer reference. It is therefore a path to a data layer rather than a path to service metadata. // We therefore need to correct the configuration by separating the layer index and the path to the service metadata. this.metadataAccessPath = metadataAccessPathItems.slice(0, -1).join('/'); if (this.listOfLayerEntryConfig.length) { @@ -134,17 +134,12 @@ export class GeoJsonLayerConfig extends AbstractGeoviewLayerConfig { const fetchResponse = await fetch(this.metadataAccessPath); if (fetchResponse.status === 404) throw new GeoviewLayerConfigError('The service metadata fetch returned a 404 status (Not Found)'); const layerMetadata = (await fetchResponse.json()) as TypeJsonObject; - const metadataAccessPathElements = this.metadataAccessPath.split('/'); - this.metadataAccessPath = metadataAccessPathElements.slice(0, metadataAccessPathElements.length - 1).join('/'); if (layerMetadata) this.setServiceMetadata(layerMetadata); else throw new GeoviewLayerConfigError('The metadata object returned is undefined'); - } else { - await this.createLayerTree(); - return; - } - this.listOfLayerEntryConfig = this.processListOfLayerEntryConfig(this.listOfLayerEntryConfig); - await this.fetchListOfLayerMetadata(); + this.listOfLayerEntryConfig = this.processListOfLayerEntryConfig(this.listOfLayerEntryConfig); + await this.fetchListOfLayerMetadata(); + } await this.createLayerTree(); } catch (error) { @@ -166,13 +161,15 @@ export class GeoJsonLayerConfig extends AbstractGeoviewLayerConfig { * @protected @override */ protected override createLayerEntryNode(layerId: string, parentNode: EntryConfigBaseClass | undefined): EntryConfigBaseClass { - if (this.getServiceMetadata()) + // GV: To determine if service metadata exists, we must verify that the object is not empty. + if (Object.keys(this.getServiceMetadata()).length === 0) return this.createLeafNode( toJsonObject({ layerId, layerName: createLocalizedString(layerId) }), this.getLanguage(), this, parentNode )!; + // If we cannot find the layerId in the layer definitions, throw an error. const layerFound = this.findLayerMetadataEntry(layerId); if (!layerFound) { diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/entry-config-base-class.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/entry-config-base-class.ts index 792ab7e373d..96c777b6a34 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/entry-config-base-class.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/entry-config-base-class.ts @@ -148,6 +148,12 @@ export abstract class EntryConfigBaseClass { */ abstract fetchLayerMetadata(): Promise; + /** + * This method is used to parse the layer metadata and extract the style, source information and other properties. + * @abstract @protected + */ + protected abstract parseLayerMetadata(): void; + // #endregion ABSTRACT // ================= @@ -157,7 +163,32 @@ export abstract class EntryConfigBaseClass { * @protected */ protected validateLayerConfig(layerConfig: TypeJsonObject): void { + // TODO: Code to be deleted when the configuration api is used permanently. We do that to avoid validation errors. + // GV: Delete this + // GV: ||| + // GV: vvv + const entryType = layerConfig?.entryType; + // eslint-disable-next-line no-param-reassign + delete layerConfig.entryType; + // eslint-disable-next-line no-param-reassign + if (entryType === 'group') (layerConfig.isLayerGroup as boolean) = true; + // GV: ^^^ + // GV: ||| + + // GV: Do not delete the following line. + if (!isvalidComparedToInputSchema(this.getSchemaPath(), layerConfig)) this.setErrorDetectedFlag(); + + // TODO: Code to be deleted when the configuration api is used permanently. + // GV: Delete this + // GV: ||| + // GV: vvv + // eslint-disable-next-line no-param-reassign + if (entryType) layerConfig.entryType = entryType; + // eslint-disable-next-line no-param-reassign + if (entryType === 'group') delete layerConfig.isLayerGroup; + // GV: ^^^ + // GV: ||| } // #endregion PROTECTED @@ -326,6 +357,7 @@ export abstract class EntryConfigBaseClass { cloneOfTheNode.layerName = this.layerName; cloneOfTheNode.setErrorDetectedFlag(this.#errorDetectedFlag); cloneOfTheNode.setLayerMetadata(this.#layerMetadata); + cloneOfTheNode.parseLayerMetadata(); return cloneOfTheNode; } diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts index 3a946c2f964..640d4dbd949 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/esri-group-layer-config.ts @@ -59,7 +59,7 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { // The metadata used are the layer metadata. this.setLayerMetadata(data); // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); return; } } catch (error) { @@ -76,28 +76,25 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { ); } } - // #endregion OVERRIDE - // =============== - // #region PRIVATE /** * This method is used to analyze metadata and extract the relevant information from a group layer based on a definition * provided by the ESRI service. - * @private + * @override @protected */ - #parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); this.minScale = layerMetadata.minScale as number; this.maxScale = layerMetadata.maxScale as number; const metadataExtent = [ - layerMetadata.extent.xmin, - layerMetadata.extent.ymin, - layerMetadata.extent.xmax, - layerMetadata.extent.ymax, + layerMetadata.initialExtent.xmin, + layerMetadata.initialExtent.ymin, + layerMetadata.initialExtent.xmax, + layerMetadata.initialExtent.ymax, ] as Extent; - const sourceProj = layerMetadata.extent.spatialReference.wkid; + const sourceProj = layerMetadata.initialExtent.spatialReference.wkid; if (sourceProj === '4326') this.initialSettings.extent = validateExtentWhenDefined(metadataExtent); else this.initialSettings.extent = validateExtentWhenDefined( @@ -111,6 +108,10 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { if (layerMetadata.copyrightText) this.attributions.push(layerMetadata.copyrightText as string); } + // #endregion OVERRIDE + + // =============== + // #region PRIVATE /** * This method is used to analyze metadata and extract the relevant information from a group layer based on a definition * provided by the user's configuration. In this case, we use the service metadata. diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/geojson-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/geojson-group-layer-config.ts index ea4206d552e..3f00af7bea0 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/geojson-group-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/geojson-group-layer-config.ts @@ -47,7 +47,7 @@ export class GeoJsonGroupLayerConfig extends GroupLayerEntryConfig { this.setLayerMetadata(layerMetadata); // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); } // Fetch the sub-layers metadata that compose the group. @@ -60,15 +60,11 @@ export class GeoJsonGroupLayerConfig extends GroupLayerEntryConfig { } } - // #endregion OVERRIDE - - // =============== - // #region PRIVATE /** * This method is used to parse the layer metadata and extract the source information and other properties. - * @private + * @override @protected */ - #parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); if (layerMetadata?.attributions) this.attributions.push(layerMetadata.attributions as string); @@ -95,7 +91,7 @@ export class GeoJsonGroupLayerConfig extends GroupLayerEntryConfig { } } - // #endregion PRIVATE + // #endregion OVERRIDE // #endregion METHODS // #endregion CLASS HEADER } diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wfs-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wfs-group-layer-config.ts index 52bebf05837..b3ce3885237 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wfs-group-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wfs-group-layer-config.ts @@ -35,6 +35,14 @@ export class WfsGroupLayerConfig extends GroupLayerEntryConfig { } } + /** *************************************************************************************************************************** + * This method is used to parse the layer metadata and extract the style, source information and other properties. + * However, since WFS doesn't have groups in its metadata, this routine does nothing for the group nodes. + * + * @protected @override + */ + protected override parseLayerMetadata(): void {} + // #endregion OVERRIDE // #endregion METHODS // #endregion CLASS HEADER diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts index 0121fe3e5b7..5c073e7c0a2 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/group-node/wms-group-layer-config.ts @@ -51,7 +51,7 @@ export class WmsGroupLayerConfig extends GroupLayerEntryConfig { } // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); await this.fetchListOfLayerMetadata(); @@ -62,16 +62,12 @@ export class WmsGroupLayerConfig extends GroupLayerEntryConfig { } } - // #endregion OVERRIDE - - // =============== - // #region PRIVATE /** *************************************************************************************************************************** * This method is used to analyze metadata and extract the relevant information from a group layer based on a definition * provided by the WMS service. - * @private + * @override @protected */ - #parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); this.layerName = layerMetadata.Title as string; @@ -86,7 +82,8 @@ export class WmsGroupLayerConfig extends GroupLayerEntryConfig { this.initialSettings.extent = validateExtentWhenDefined(layerMetadata.EX_GeographicBoundingBox as Extent); this.initialSettings.bounds = this.initialSettings.extent; } - // #endregion PRIVATE + + // #endregion OVERRIDE // #endregion METHODS // #endregion CLASS HEADER } diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts index e8f0959f120..8e81b732ad5 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/abstract-base-esri-layer-entry-config.ts @@ -2,14 +2,16 @@ import axios from 'axios'; import { Cast, TypeJsonArray, TypeJsonObject } from '@config/types/config-types'; import { codedValueType, Extent, rangeDomainType, TypeFeatureInfoLayerConfig, TypeOutfields } from '@config/types/map-schema-types'; -import { AbstractBaseLayerEntryConfig } from '@/api/config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config'; +import { AbstractBaseLayerEntryConfig } from '@config/types/classes/sub-layer-config/leaf/abstract-base-layer-entry-config'; + +import { GeoviewLayerConfigError } from '@config/types/classes/config-exceptions'; +import { AbstractGeoviewEsriLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-esri-layer-config'; -import { logger } from '@/core/utils/logger'; -import { DateMgt, TimeDimensionESRI } from '@/core/utils/date-mgt'; import { Projection } from '@/geo/utils/projection'; import { validateExtentWhenDefined } from '@/geo/utils/utilities'; import { isvalidComparedToInternalSchema } from '@/api/config/utils'; -import { GeoviewLayerConfigError } from '../../config-exceptions'; +import { logger } from '@/core/utils/logger'; +import { DateMgt, TimeDimensionESRI } from '@/core/utils/date-mgt'; // ======================== // #region CLASS HEADER @@ -60,33 +62,17 @@ export abstract class AbstractBaseEsriLayerEntryConfig extends AbstractBaseLayer } this.setErrorDetectedFlag(); } - // #endregion OVERRIDE - - // ========================== - // #region PROTECTED - /** - * This method will create a Geoview temporal dimension if it exist in the service metadata. - * - * @param {TypeJsonObject} timeDimension The ESRI time dimension object. - * @protected - */ - // TODO: Issue #2139 - There is a bug with the temporal dimension returned by service URL: - // TODO.CONT: https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/Temporal_Test_Bed_fr/MapServer/0 - protected processTemporalDimension(timeDimension: TypeJsonObject): void { - if (timeDimension?.timeExtent) { - // The singleHandle property is True for ESRI Image and false for ESRI Feature and Dynamic. - const singleHandle = false; - this.temporalDimension = DateMgt.createDimensionFromESRI(Cast(timeDimension), singleHandle); - } - } /** * This method is used to parse the layer metadata and extract the style, source information and other properties. - * @protected + * @override @protected */ - protected parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); + if (layerMetadata.geometryType) + this.geometryType = AbstractGeoviewEsriLayerConfig.convertEsriGeometryTypeToOLGeometryType(layerMetadata.geometryType as string); + this.minScale = layerMetadata.minScale as number; this.maxScale = layerMetadata.maxScale as number; @@ -118,6 +104,26 @@ export abstract class AbstractBaseEsriLayerEntryConfig extends AbstractBaseLayer if (layerMetadata.copyrightText) this.attributions.push(layerMetadata.copyrightText as string); } + // #endregion OVERRIDE + + // ========================== + // #region PROTECTED + /** + * This method will create a Geoview temporal dimension if it exist in the service metadata. + * + * @param {TypeJsonObject} timeDimension The ESRI time dimension object. + * @protected + */ + // TODO: Issue #2139 - There is a bug with the temporal dimension returned by service URL: + // TODO.CONT: https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/Temporal_Test_Bed_fr/MapServer/0 + protected processTemporalDimension(timeDimension: TypeJsonObject): void { + if (timeDimension?.timeExtent) { + // The singleHandle property is True for ESRI Image and false for ESRI Feature and Dynamic. + const singleHandle = false; + this.temporalDimension = DateMgt.createDimensionFromESRI(Cast(timeDimension), singleHandle); + } + } + /** * This method creates the feature information from the layer metadata. * diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts index 44baa144d5f..8ce767101bb 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/raster/wms-layer-entry-config.ts @@ -78,7 +78,7 @@ export class WmsLayerEntryConfig extends AbstractBaseLayerEntryConfig { if (layerMetadata) { this.setLayerMetadata(layerMetadata); // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { throw new GeoviewLayerConfigError( @@ -112,15 +112,12 @@ export class WmsLayerEntryConfig extends AbstractBaseLayerEntryConfig { }, }; } - // #endregion OVERRIDE - // =============== - // #region PRIVATE /** * This method is used to parse the layer metadata and extract the source information and other properties. - * @private + * @override @protected */ - #parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); if (layerMetadata?.Attribution?.Title) this.attributions.push(layerMetadata.Attribution.Title as string); @@ -138,6 +135,10 @@ export class WmsLayerEntryConfig extends AbstractBaseLayerEntryConfig { this.#processTemporalDimension(layerMetadata.Dimension); } + // #endregion OVERRIDE + + // =============== + // #region PRIVATE /** *************************************************************************************************************************** * This method will create a Geoview temporal dimension if it existds in the service metadata * @param {TypeJsonObject} wmsDimension The WMS time dimension object diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/geojson-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/geojson-layer-entry-config.ts index a145831a263..1b27088491c 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/geojson-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/geojson-layer-entry-config.ts @@ -84,6 +84,8 @@ export class GeoJsonLayerEntryConfig extends AbstractBaseLayerEntryConfig { // If an error has already been detected, then the layer is unusable. if (this.getErrorDetectedFlag()) return Promise.resolve(); + // If the GeoJson GeoView layer doesn't have service metadata, the layer metadata are set using an empty object and they + // will be fetch on the fly by the layer api. if (Object.keys(this.getGeoviewLayerConfig().getServiceMetadata()).length === 0) { this.setLayerMetadata({}); return Promise.resolve(); @@ -94,7 +96,7 @@ export class GeoJsonLayerEntryConfig extends AbstractBaseLayerEntryConfig { this.setLayerMetadata(layerMetadata); // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); if (!isvalidComparedToInternalSchema(this.getSchemaPath(), this, true)) { throw new GeoviewLayerConfigError( @@ -129,16 +131,15 @@ export class GeoJsonLayerEntryConfig extends AbstractBaseLayerEntryConfig { }, }; } - // #endregion OVERRIDE - // =============== - // #region PRIVATE /** * This method is used to parse the layer metadata and extract the source information and other properties. - * @private + * @override @protected */ - #parseLayerMetadata(): void { + protected override parseLayerMetadata(): void { const layerMetadata = this.getLayerMetadata(); + // return if the layer has no metadata. + if (Object.keys(layerMetadata).length === 0) return; if (layerMetadata?.attributions) this.attributions.push(layerMetadata.attributions as string); this.geometryType = (layerMetadata.geometryType || this.geometryType) as TypeStyleGeometry; @@ -168,7 +169,7 @@ export class GeoJsonLayerEntryConfig extends AbstractBaseLayerEntryConfig { } } - // #endregion PRIVATE + // #endregion OVERRIDE // #endregion METHODS // #endregion CLASS HEADER } diff --git a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/wfs-layer-entry-config.ts b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/wfs-layer-entry-config.ts index 29cb2b1f0d3..3f82b3f8383 100644 --- a/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/wfs-layer-entry-config.ts +++ b/packages/geoview-core/src/api/config/types/classes/sub-layer-config/leaf/vector/wfs-layer-entry-config.ts @@ -93,7 +93,7 @@ export class WfsLayerEntryConfig extends AbstractBaseLayerEntryConfig { }); // Parse the raw layer metadata and build the geoview configuration. - this.#parseLayerMetadata(); + this.parseLayerMetadata(); this.source.featureInfo = this.#createFeatureInfoUsingMetadata(); @@ -129,6 +129,37 @@ export class WfsLayerEntryConfig extends AbstractBaseLayerEntryConfig { }, }; } + + /** + * This method is used to parse the layer metadata and extract the source information and other properties. + * @override @protected + */ + protected override parseLayerMetadata(): void { + const layerMetadata = this.getLayerMetadata().fromGetCapabilities; + + if (findPropertyNameByRegex(layerMetadata, /(?:WGS84BoundingBox)/)) { + const lowerCorner = ( + findPropertyNameByRegex(layerMetadata, [/(?:WGS84BoundingBox)/, /(?:LowerCorner)/, /(?:#text)/]) as string + ).split(' '); + const upperCorner = ( + findPropertyNameByRegex(layerMetadata, [/(?:WGS84BoundingBox)/, /(?:UpperCorner)/, /(?:#text)/]) as string + ).split(' '); + const bounds = [Number(lowerCorner[0]), Number(lowerCorner[1]), Number(upperCorner[0]), Number(upperCorner[1])] as Extent; + + this.initialSettings!.extent = validateExtentWhenDefined(bounds); + if (this.initialSettings?.extent?.find?.((value, i) => value !== bounds[i])) + logger.logWarning( + `The extent specified in the metadata for the layer path “${this.getLayerPath()}” is considered invalid and has been corrected.` + ); + + this.bounds = this.initialSettings!.extent; + } + + this.source.featureInfo!.queryable = this.#layerIsQueryable(); + + // this.#processTemporalDimension(layerMetadata.Dimension); + } + // #endregion OVERRIDE // =============== @@ -209,36 +240,6 @@ export class WfsLayerEntryConfig extends AbstractBaseLayerEntryConfig { return []; } - /** - * This method is used to parse the layer metadata and extract the source information and other properties. - * @private - */ - #parseLayerMetadata(): void { - const layerMetadata = this.getLayerMetadata().fromGetCapabilities; - - if (findPropertyNameByRegex(layerMetadata, /(?:WGS84BoundingBox)/)) { - const lowerCorner = ( - findPropertyNameByRegex(layerMetadata, [/(?:WGS84BoundingBox)/, /(?:LowerCorner)/, /(?:#text)/]) as string - ).split(' '); - const upperCorner = ( - findPropertyNameByRegex(layerMetadata, [/(?:WGS84BoundingBox)/, /(?:UpperCorner)/, /(?:#text)/]) as string - ).split(' '); - const bounds = [Number(lowerCorner[0]), Number(lowerCorner[1]), Number(upperCorner[0]), Number(upperCorner[1])] as Extent; - - this.initialSettings!.extent = validateExtentWhenDefined(bounds); - if (this.initialSettings?.extent?.find?.((value, i) => value !== bounds[i])) - logger.logWarning( - `The extent specified in the metadata for the layer path “${this.getLayerPath()}” is considered invalid and has been corrected.` - ); - - this.bounds = this.initialSettings!.extent; - } - - this.source.featureInfo!.queryable = this.#layerIsQueryable(); - - // this.#processTemporalDimension(layerMetadata.Dimension); - } - /** * This method creates the feature information from the layer metadata. * diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts index 928fd94e81a..3dd63592b00 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-dynamic.ts @@ -273,8 +273,8 @@ export class EsriDynamic extends AbstractGeoViewRaster { sourceOptions.attributions = [(this.metadata?.copyrightText ? this.metadata?.copyrightText : '') as string]; sourceOptions.url = getLocalizedValue(layerConfig.source.dataAccessPath!, AppEventProcessor.getDisplayLanguage(this.mapId)); sourceOptions.params = { LAYERS: `show:${layerConfig.layerId}` }; - if (layerConfig.source.transparent) Object.defineProperty(sourceOptions.params, 'transparent', layerConfig.source.transparent!); - if (layerConfig.source.format) Object.defineProperty(sourceOptions.params, 'format', layerConfig.source.format!); + if (layerConfig.source.transparent) sourceOptions.params.transparent = layerConfig.source.transparent!; + if (layerConfig.source.format) sourceOptions.params.format = layerConfig.source.format!; if (layerConfig.source.crossOrigin) { sourceOptions.crossOrigin = layerConfig.source.crossOrigin; } else { diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-image.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-image.ts index a60db3447ed..d460e59c2ea 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-image.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/esri-image.ts @@ -318,8 +318,8 @@ export class EsriImage extends AbstractGeoViewRaster { sourceOptions.attributions = [(this.metadata!.copyrightText ? this.metadata!.copyrightText : '') as string]; sourceOptions.url = getLocalizedValue(layerConfig.source.dataAccessPath!, AppEventProcessor.getDisplayLanguage(this.mapId)); sourceOptions.params = { LAYERS: `show:${layerConfig.layerId}` }; - if (layerConfig.source.transparent) Object.defineProperty(sourceOptions.params, 'transparent', layerConfig.source.transparent!); - if (layerConfig.source.format) Object.defineProperty(sourceOptions.params, 'format', layerConfig.source.format!); + if (layerConfig.source.transparent) sourceOptions.params.transparent = layerConfig.source.transparent!; + if (layerConfig.source.format) sourceOptions.params.format = layerConfig.source.format!; if (layerConfig.source.crossOrigin) { sourceOptions.crossOrigin = layerConfig.source.crossOrigin; } else { diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/vector/geojson.ts b/packages/geoview-core/src/geo/layer/geoview-layers/vector/geojson.ts index c85e929a067..410e245fe9f 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/vector/geojson.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/vector/geojson.ts @@ -131,13 +131,10 @@ export class GeoJSON extends AbstractGeoViewVector { // When no metadata are provided, all layers are considered valid. if (!this.metadata) return; - // Note that geojson metadata as we defined it does not contains layer group. If you need geogson layer group, - // you can define them in the configuration section. if (Array.isArray(this.metadata?.listOfLayerEntryConfig)) { - const metadataLayerList = Cast(this.metadata?.listOfLayerEntryConfig); - const foundEntry = metadataLayerList.find( - (layerMetadata) => - layerMetadata.layerId === layerConfig.layerId && layerMetadata.layerIdExtension === layerConfig.layerIdExtension + const foundEntry = this.#recursiveSearch( + `${layerConfig.layerId}${layerConfig.layerIdExtension ? `.${layerConfig.layerIdExtension}` : ''}`, + Cast(this.metadata?.listOfLayerEntryConfig) ); if (!foundEntry) { this.layerLoadError.push({ @@ -156,6 +153,27 @@ export class GeoJSON extends AbstractGeoViewVector { }); } + /** *************************************************************************************************************************** + * This method is used to do a recursive search in the array of layer entry config. + * + * @param {string} layerId The layer list to search. + * @param {TypeLayerEntryConfig[]} metadataLayerList The layer list to search. + * + * @returns {TypeLayerEntryConfig | undefined} The found layer or undefined if not found. + * @private + */ + #recursiveSearch(searchKey: string, metadataLayerList: TypeLayerEntryConfig[]): TypeLayerEntryConfig | undefined { + for (const layerMetadata of metadataLayerList) { + if (searchKey === `${layerMetadata.layerId}${layerMetadata.layerIdExtension ? `.${layerMetadata.layerIdExtension}` : ''}`) + return layerMetadata; + if ('isLayerGroup' in layerMetadata && (layerMetadata.isLayerGroup as boolean)) { + const foundLayer = this.#recursiveSearch(searchKey, layerMetadata.listOfLayerEntryConfig); + if (foundLayer) return foundLayer; + } + } + return undefined; + } + /** *************************************************************************************************************************** * This method is used to process the layer's metadata. It will fill the empty fields of the layer's configuration (renderer, * initial settings, fields and aliases). @@ -170,10 +188,10 @@ export class GeoJSON extends AbstractGeoViewVector { if (!(layerConfig instanceof VectorLayerEntryConfig)) throw new Error('Invalid layer configuration type provided'); if (this.metadata) { - const metadataLayerList = Cast(this.metadata?.listOfLayerEntryConfig); - const layerMetadataFound = metadataLayerList.find( - (layerMetadata) => layerMetadata.layerId === layerConfig.layerId && layerMetadata.layerIdExtension === layerConfig.layerIdExtension - ); + const layerMetadataFound = this.#recursiveSearch( + `${layerConfig.layerId}${layerConfig.layerIdExtension ? `.${layerConfig.layerIdExtension}` : ''}`, + Cast(this.metadata?.listOfLayerEntryConfig) + ) as VectorLayerEntryConfig; if (layerMetadataFound) { layerConfig.layerName = layerConfig.layerName || layerMetadataFound.layerName; layerConfig.source = defaultsDeep(layerConfig.source, layerMetadataFound.source);