diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 297f5e903f0..ba9065963d5 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -143,7 +143,7 @@ importers: version: 6.3.1(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@types/react@18.3.18)(react@18.3.1) '@mui/x-date-pickers': specifier: ^7.20.0 - version: 7.23.3(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1) + version: 7.23.6(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1) '@nieuwlandgeo/sldreader': specifier: ^0.4.3 version: 0.4.3(ol@10.3.1) @@ -200,7 +200,7 @@ importers: version: 7.5.1(react@18.3.1) material-react-table: specifier: ^3.0.1 - version: 3.1.0(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/icons-material@6.3.1)(@mui/material@6.3.1)(@mui/x-date-pickers@7.23.3)(react-dom@18.3.1)(react@18.3.1) + version: 3.1.0(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/icons-material@6.3.1)(@mui/material@6.3.1)(@mui/x-date-pickers@7.23.6)(react-dom@18.3.1)(react@18.3.1) ol: specifier: ^10.2.1 version: 10.3.1 @@ -2962,8 +2962,8 @@ packages: react-is: 19.0.0 dev: false - /@mui/x-date-pickers@7.23.3(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-bjTYX/QzD5ZhVZNNnastMUS3j2Hy4p4IXmJgPJ0vKvQBvUdfEO+ZF42r3PJNNde0FVT1MmTzkmdTlz0JZ6ukdw==} + /@mui/x-date-pickers@7.23.6(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-jt6rEAYLju3NZe3y2S+I5KcTiSHV79FW0jeNUEUTceg1qsPzseHbND66k3zVF0hO3N2oZtLtPywof6vN5Doe+Q==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -2971,7 +2971,7 @@ packages: '@mui/material': ^5.15.14 || ^6.0.0 '@mui/system': ^5.15.14 || ^6.0.0 date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0 - date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 + date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0 dayjs: ^1.10.7 luxon: ^3.0.2 moment: ^2.29.4 @@ -3005,7 +3005,7 @@ packages: '@mui/material': 6.3.1(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) '@mui/system': 6.3.1(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@types/react@18.3.18)(react@18.3.1) '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) - '@mui/x-internals': 7.23.0(@types/react@18.3.18)(react@18.3.1) + '@mui/x-internals': 7.23.6(@types/react@18.3.18)(react@18.3.1) '@types/react-transition-group': 4.4.12(@types/react@18.3.18) clsx: 2.1.1 dayjs: 1.11.13 @@ -3017,8 +3017,8 @@ packages: - '@types/react' dev: false - /@mui/x-internals@7.23.0(@types/react@18.3.18)(react@18.3.1): - resolution: {integrity: sha512-bPclKpqUiJYIHqmTxSzMVZi6MH51cQsn5U+8jskaTlo3J4QiMeCYJn/gn7YbeR9GOZFp8hetyHjoQoVHKRXCig==} + /@mui/x-internals@7.23.6(@types/react@18.3.18)(react@18.3.1): + resolution: {integrity: sha512-hT1Pa4PNCnxwiauPbYMC3p4DiEF1x05Iu4C1MtC/jMJ1LtthymLmTuQ6ZQ53/R9FeqK6sYd6A6noR+vNMjp5DA==} engines: {node: '>=14.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4517,8 +4517,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.79 + caniuse-lite: 1.0.30001692 + electron-to-chromium: 1.5.80 node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) dev: true @@ -4592,8 +4592,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001690: - resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} + /caniuse-lite@1.0.30001692: + resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} dev: true /ccount@2.0.1: @@ -5288,8 +5288,8 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true - /electron-to-chromium@1.5.79: - resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} + /electron-to-chromium@1.5.80: + resolution: {integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==} dev: true /email-addresses@5.0.0: @@ -6594,8 +6594,8 @@ packages: toidentifier: 1.0.1 dev: true - /http-parser-js@0.5.8: - resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + /http-parser-js@0.5.9: + resolution: {integrity: sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==} dev: true /http-proxy-middleware@2.0.7(@types/express@4.17.21): @@ -7878,7 +7878,7 @@ packages: hasBin: true dev: true - /material-react-table@3.1.0(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/icons-material@6.3.1)(@mui/material@6.3.1)(@mui/x-date-pickers@7.23.3)(react-dom@18.3.1)(react@18.3.1): + /material-react-table@3.1.0(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/icons-material@6.3.1)(@mui/material@6.3.1)(@mui/x-date-pickers@7.23.6)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-/zPn38QhxQE7mkwLex4CojX3UP2+/+/u7NVq7CHS5d6P8LdTteJPVYXVzym/uhaXzAzFB1ojsbP7zI/y6iUdtQ==} engines: {node: '>=16'} peerDependencies: @@ -7894,7 +7894,7 @@ packages: '@emotion/styled': 11.14.0(@emotion/react@11.14.0)(@types/react@18.3.18)(react@18.3.1) '@mui/icons-material': 6.3.1(@mui/material@6.3.1)(@types/react@18.3.18)(react@18.3.1) '@mui/material': 6.3.1(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) - '@mui/x-date-pickers': 7.23.3(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1) + '@mui/x-date-pickers': 7.23.6(@emotion/react@11.14.0)(@emotion/styled@11.14.0)(@mui/material@6.3.1)(@mui/system@6.3.1)(@types/react@18.3.18)(dayjs@1.11.13)(react-dom@18.3.1)(react@18.3.1) '@tanstack/match-sorter-utils': 8.19.4 '@tanstack/react-table': 8.20.6(react-dom@18.3.1)(react@18.3.1) '@tanstack/react-virtual': 3.11.2(react-dom@18.3.1)(react@18.3.1) @@ -7931,8 +7931,8 @@ packages: engines: {node: '>= 0.6'} dev: true - /memfs@4.15.3: - resolution: {integrity: sha512-vR/g1SgqvKJgAyYla+06G4p/EOcEmwhYuVb1yc1ixcKf8o/sh7Zngv63957ZSNd1xrZJoinmNyDf2LzuP8WJXw==} + /memfs@4.17.0: + resolution: {integrity: sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==} engines: {node: '>= 4.0.0'} dependencies: '@jsonjoy.com/json-pack': 1.1.1(tslib@2.8.1) @@ -10198,7 +10198,7 @@ packages: optional: true dependencies: colorette: 2.0.20 - memfs: 4.15.3 + memfs: 4.17.0 mime-types: 2.1.35 on-finished: 2.4.1 range-parser: 1.2.1 @@ -10322,7 +10322,7 @@ packages: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} dependencies: - http-parser-js: 0.5.8 + http-parser-js: 0.5.9 safe-buffer: 5.2.1 websocket-extensions: 0.1.4 dev: true diff --git a/packages/geoview-core/public/templates/demos/demo-function-event.html b/packages/geoview-core/public/templates/demos/demo-function-event.html index 2e2b02ce8ac..65e538da857 100644 --- a/packages/geoview-core/public/templates/demos/demo-function-event.html +++ b/packages/geoview-core/public/templates/demos/demo-function-event.html @@ -207,6 +207,9 @@

Events that will generate notifications:

const LYR_PATH_FEATURE = "esriFeatureLYR5/0"; const LYR_PATH_GEOCORE = "f4c51eaa-a6ca-48b9-a1fc-b0651da20509"; + var CNT_GENERIC_STATUS_CHANGE = 0; + var CNT_SPECIFIC_STATUS_CHANGE = 0; + // Register a handler when the map is init cgpv.onMapInit((mapId) => { // !! @@ -269,7 +272,7 @@

Events that will generate notifications:

// listen to ANY/ALL layer status at ANY time (generic event catcher) cgpv.api.maps[mapId].layer.legendsLayerSet.onLayerStatusUpdated((sender, payload) => { //cgpv.api.maps.Map1.notifications.addNotificationSuccess(`${payload.layer.layerPath} (generic event) status changed to ${payload.layer.layerStatus}`); - console.log(`${payload.layer.layerPath} (generic event) status changed to ${payload.layer.layerStatus}`); + console.log(`${payload.layer.layerPath} (generic event ${++CNT_GENERIC_STATUS_CHANGE}) status changed to ${payload.layer.layerStatus}`); }); // listen to layer item visibility changed event (any layers) @@ -298,9 +301,10 @@

Events that will generate notifications:

// Check the layer status of the particular layer before registering the hook - to really know at which status the layer is. // GV If you really want to make sure to track ALL status changes for ANY particular layer, you can use a hook such as: // `cgpv.api.maps[mapId].layer.legendsLayerSet.onLayerStatusUpdated()`. See example in cgpv.onMapInit handler above. + cgpv.api.maps.Map1.layer.getLayerEntryConfig(LYR_PATH_UNIQUE)?.onLayerStatusChanged((sender, payload) => { cgpv.api.maps.Map1.notifications.addNotificationSuccess(`${LYR_PATH_UNIQUE} (specific event) status changed to ${payload.layerStatus}`); - console.log(`${LYR_PATH_UNIQUE} (specific event) status changed to ${payload.layerStatus}`); + console.log(`${LYR_PATH_UNIQUE} (specific event ${++CNT_SPECIFIC_STATUS_CHANGE}) status changed to ${payload.layerStatus}`); }); // listen to individual layer loaded event diff --git a/packages/geoview-core/public/templates/demos/demo-osdp-integration.html b/packages/geoview-core/public/templates/demos/demo-osdp-integration.html index 802febaa4e4..b5881f9a557 100644 --- a/packages/geoview-core/public/templates/demos/demo-osdp-integration.html +++ b/packages/geoview-core/public/templates/demos/demo-osdp-integration.html @@ -244,7 +244,7 @@

OSDP Integration

/** OSDP function * Adds layers to the map. - * + * * @param layers Array of layers. */ function addLayers(layers) { diff --git a/packages/geoview-core/src/core/components/layers/left-panel/add-new-layer/add-new-layer.tsx b/packages/geoview-core/src/core/components/layers/left-panel/add-new-layer/add-new-layer.tsx index 67315f432bc..2a223d3ab57 100644 --- a/packages/geoview-core/src/core/components/layers/left-panel/add-new-layer/add-new-layer.tsx +++ b/packages/geoview-core/src/core/components/layers/left-panel/add-new-layer/add-new-layer.tsx @@ -41,7 +41,7 @@ import { EsriImageLayerEntryConfig } from '@/core/utils/config/validation-classe import { OgcWmsLayerEntryConfig } from '@/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config'; import { GeoPackage, TypeGeoPackageLayerConfig } from '@/geo/layer/geoview-layers/vector/geopackage'; import { GeoCore } from '@/geo/layer/other/geocore'; -import { GeoViewLayerAddedResult } from '@/geo/layer/layer'; +import { GeoViewLayerAddedResult, LayerApi } from '@/geo/layer/layer'; import { CONST_LAYER_TYPES, TypeGeoviewLayerTypeWithGeoCore, @@ -216,7 +216,7 @@ export function AddNewLayer(): JSX.Element { listOfLayerEntryConfig: [] as OgcWmsLayerEntryConfig[], metadataAccessPath: accessPath, } as TypeWMSLayerConfig; - const wmsGeoviewLayerInstance = new WmsGeoviewClass(mapId, wmsGeoviewLayerConfig); + const wmsGeoviewLayerInstance = new WmsGeoviewClass(mapId, wmsGeoviewLayerConfig, LayerApi.DEBUG_WMS_LAYER_GROUP_FULL_SUB_LAYERS); // Synchronize the geoviewLayerId. wmsGeoviewLayerConfig.geoviewLayerId = wmsGeoviewLayerInstance.geoviewLayerId; setGeoviewLayerInstance(wmsGeoviewLayerInstance); @@ -248,7 +248,7 @@ export function AddNewLayer(): JSX.Element { geoviewLayerConfig: wmsGeoviewLayerConfig, layerId: childLayer.Name as string, layerName: childLayer.Title as string, - } as OgcWmsLayerEntryConfig) + } as unknown as OgcWmsLayerEntryConfig) ); } diff --git a/packages/geoview-core/src/core/utils/config/validation-classes/config-base-class.ts b/packages/geoview-core/src/core/utils/config/validation-classes/config-base-class.ts index b570ef3e581..20f69c5f0e0 100644 --- a/packages/geoview-core/src/core/utils/config/validation-classes/config-base-class.ts +++ b/packages/geoview-core/src/core/utils/config/validation-classes/config-base-class.ts @@ -149,6 +149,9 @@ export abstract class ConfigBaseClass { } if (newLayerStatus === 'processed' && this.#waitForProcessedBeforeSendingLoaded) this.layerStatus = 'loaded'; + // GV For quick debug, uncomment the line + // if (newLayerStatus === 'error') debugger; + // TODO: Cleanup - Commenting this and leaving it here for now.. It turns out that the parentLayerConfig property can't be trusted // GV due to a bug with different instances of entryconfigs stored in the objects and depending how you navigate the objects, you get // GV different instances. Example below (where 'parentLayerConfig.listOfLayerEntryConfig[0]' is indeed going back to 'uniqueValueId/uniqueValueId/4') @@ -222,6 +225,28 @@ export abstract class ConfigBaseClass { } as unknown as TypeJsonObject; } + /** + * Clones the configuration class. + * + * @returns {ConfigBaseClass} The cloned ConfigBaseClass object. + */ + clone(): ConfigBaseClass { + // Redirect to clone the object and return it + return this.onClone(); + } + + /** + * Overridable function to clone a child of a ConfigBaseClass. + * + * @returns {ConfigBaseClass} The cloned child object of a ConfigBaseClass. + */ + protected onClone(): ConfigBaseClass { + // Crash on purpose. + // GV Make sure to implement a 'protected override onClone(): ConfigBaseClass' in the child-class to + // GV use this cloning feature. See OgcWMSLayerEntryConfig for example. + throw new Error(`Not implemented exception onClone on layer path ${this.layerPath}`); + } + /** * Recursively checks the list of layer entries to see if all of them are greater than or equal to the provided layer status. * diff --git a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config.ts b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config.ts index 87796dd0ba7..2a42a3b7874 100644 --- a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config.ts +++ b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config.ts @@ -1,5 +1,6 @@ import { CONST_LAYER_TYPES } from '@/geo/layer/geoview-layers/abstract-geoview-layers'; import { CONST_LAYER_ENTRY_TYPES, TypeSourceImageWmsInitialConfig } from '@/geo/map/map-schema-types'; +import { ConfigBaseClass } from '@/core/utils/config/validation-classes/config-base-class'; import { AbstractBaseLayerEntryConfig } from '@/core/utils/config/validation-classes/abstract-base-layer-entry-config'; /** ****************************************************************************************************************************** @@ -40,4 +41,13 @@ export class OgcWmsLayerEntryConfig extends AbstractBaseLayerEntryConfig { // Default value for layerConfig.source.serverType is 'mapserver'. if (!this.source.serverType) this.source.serverType = 'mapserver'; } + + /** + * Clones an instance of a OgcWmsLayerEntryConfig. + * + * @returns {ConfigBaseClass} The cloned OgcWmsLayerEntryConfig instance + */ + protected override onClone(): ConfigBaseClass { + return new OgcWmsLayerEntryConfig(this); + } } diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/esri-layer-common.ts b/packages/geoview-core/src/geo/layer/geoview-layers/esri-layer-common.ts index 3a425c35211..057064a45c6 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/esri-layer-common.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/esri-layer-common.ts @@ -142,6 +142,7 @@ export function commonValidateListOfLayerEntryConfig( // TODO: Refactor: Do not do this on the fly here anymore with the new configs (quite unpredictable)... // Don't forget to replace the old version in the registered layers + // TODO: TEST GROUP LAYER TEST Officially remove setLayerEntryConfigObsolete once passed testing MapEventProcessor.getMapViewerLayerAPI(layer.mapId).setLayerEntryConfigObsolete(groupLayerConfig); (layer.metadata!.layers[esriIndex].subLayerIds as TypeJsonArray).forEach((layerId) => { @@ -164,6 +165,7 @@ export function commonValidateListOfLayerEntryConfig( // FIXME: Temporary patch to keep the behavior until those layer classes don't exist // TODO: Refactor: Do not do this on the fly here anymore with the new configs (quite unpredictable)... (standardizing this call with the other one above for now) + // TODO: TEST GROUP LAYER TEST Officially remove setLayerEntryConfigObsolete once passed testing MapEventProcessor.getMapViewerLayerAPI(layer.mapId).setLayerEntryConfigObsolete(subLayerEntryConfig); }); diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts index f6bd073d178..37274e8935b 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/wms.ts @@ -7,9 +7,7 @@ import { Options as SourceOptions } from 'ol/source/ImageWMS'; import WMSCapabilities from 'ol/format/WMSCapabilities'; import { Extent } from 'ol/extent'; -import cloneDeep from 'lodash/cloneDeep'; - -import { Cast, TypeJsonArray, TypeJsonObject } from '@/core/types/global-types'; +import { TypeJsonArray, TypeJsonObject } from '@/core/types/global-types'; import { AbstractGeoViewLayer, CONST_LAYER_TYPES } from '@/geo/layer/geoview-layers/abstract-geoview-layers'; import { AbstractGeoViewRaster } from '@/geo/layer/geoview-layers/raster/abstract-geoview-raster'; import { TypeLayerEntryConfig, TypeGeoviewLayerConfig, CONST_LAYER_ENTRY_TYPES, layerEntryIsGroupLayer } from '@/geo/map/map-schema-types'; @@ -21,6 +19,7 @@ import { logger } from '@/core/utils/logger'; import { OgcWmsLayerEntryConfig } from '@/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config'; import { AbstractBaseLayerEntryConfig } from '@/core/utils/config/validation-classes/abstract-base-layer-entry-config'; import { GroupLayerEntryConfig } from '@/core/utils/config/validation-classes/group-layer-entry-config'; +import { ConfigBaseClass } from '@/core/utils/config/validation-classes/config-base-class'; export interface TypeWMSLayerConfig extends Omit { geoviewLayerType: typeof CONST_LAYER_TYPES.WMS; @@ -79,14 +78,17 @@ export const geoviewEntryIsWMS = (verifyIfGeoViewEntry: TypeLayerEntryConfig): v export class WMS extends AbstractGeoViewRaster { WMSStyles: string[]; + fullSubLayers: boolean = false; + /** *************************************************************************************************************************** * Initialize layer * @param {string} mapId the id of the map * @param {TypeWMSLayerConfig} layerConfig the layer configuration */ - constructor(mapId: string, layerConfig: TypeWMSLayerConfig) { + constructor(mapId: string, layerConfig: TypeWMSLayerConfig, fullSubLayers: boolean) { super(CONST_LAYER_TYPES.WMS, layerConfig, mapId); this.WMSStyles = []; + this.fullSubLayers = fullSubLayers; } /** *************************************************************************************************************************** @@ -97,17 +99,21 @@ export class WMS extends AbstractGeoViewRaster { // GV Layers Refactoring - Obsolete (in config) protected override async fetchServiceMetadata(): Promise { const metadataUrl = this.metadataAccessPath; - if (metadataUrl) { - const metadataAccessPathIsXmlFile = metadataUrl.slice(-4).toLowerCase() === '.xml'; + let curatedMetadataUrl = metadataUrl; + if (!metadataUrl.includes('request=GetCapabilities')) { + curatedMetadataUrl = `${metadataUrl}?service=WMS&version=1.3.0&request=GetCapabilities`; + } + if (curatedMetadataUrl) { + const metadataAccessPathIsXmlFile = curatedMetadataUrl.slice(-4).toLowerCase() === '.xml'; if (metadataAccessPathIsXmlFile) { // XML metadata is a special case that does not use GetCapabilities to get the metadata - await this.#fetchXmlServiceMetadata(metadataUrl); + await this.#fetchXmlServiceMetadata(curatedMetadataUrl); } else { const layerConfigsToQuery = this.#getLayersToQuery(); if (layerConfigsToQuery.length === 0) { // Use GetCapabilities to get the metadata try { - const metadata = await this.#getServiceMetadata(`${metadataUrl}?service=WMS&version=1.3.0&request=GetCapabilities`); + const metadata = await this.#getServiceMetadata(curatedMetadataUrl); this.metadata = metadata; this.#processMetadataInheritance(); } catch (error) { @@ -126,9 +132,7 @@ export class WMS extends AbstractGeoViewRaster { for (i = 0; layerConfigsToQuery[i].layerId !== layerConfig.layerId; i++); if (i === layerIndex) // This is the first time we execute this query - promisedArrayOfMetadata.push( - this.#getServiceMetadata(`${metadataUrl}?service=WMS&version=1.3.0&request=GetCapabilities&Layers=${layerConfig.layerId}`) - ); + promisedArrayOfMetadata.push(this.#getServiceMetadata(`${curatedMetadataUrl}&Layers=${layerConfig.layerId}`)); // query already done. Use previous returned value else promisedArrayOfMetadata.push(promisedArrayOfMetadata[i]); }); @@ -234,7 +238,7 @@ export class WMS extends AbstractGeoViewRaster { * * @param {string} layerName The layer name to be found * @param {TypeJsonObject} layerProperty The layer property from the metadata - * @param {number[]} pathToTheLayerProperty The path leading to the parent of the layerProperty parameter + * @param {number[]} pathToTheParentLayer The path leading to the parent of the layerProperty parameter * * @returns {number[]} An array containing the path to the layer or [] if not found. * @private @@ -396,7 +400,7 @@ export class WMS extends AbstractGeoViewRaster { } if ('Layer' in layerFound) { - this.#createGroupLayer(layerFound, layerConfig as AbstractBaseLayerEntryConfig); + this.#createGroupLayer(layerFound, layerConfig as unknown as GroupLayerEntryConfig); return; } @@ -409,35 +413,57 @@ export class WMS extends AbstractGeoViewRaster { * This method create recursively dynamic group layers from the service metadata. * * @param {TypeJsonObject} layer The dynamic group layer metadata. - * @param {AbstractBaseLayerEntryConfig} layerConfig The layer configurstion associated to the dynamic group. + * @param {GroupLayerEntryConfig} layerConfig The group layer configuration associated to the dynamic group. * @private */ // GV Layers Refactoring - Obsolete (in config) - #createGroupLayer(layer: TypeJsonObject, layerConfig: AbstractBaseLayerEntryConfig): void { + #createGroupLayer(layer: TypeJsonObject, layerConfig: GroupLayerEntryConfig): void { // TODO: Refactor - createGroup is the same thing for all the layers type? group is a geoview structure. // TO.DOCONT: Should it be handle upper in abstract class to loop in structure and launch the creation of a leaf? // TODO: The answer is no. Even if the final structure is the same, the input structure is different for each geoview layer types. const newListOfLayerEntryConfig: TypeLayerEntryConfig[] = []; const arrayOfLayerMetadata = Array.isArray(layer.Layer) ? layer.Layer : ([layer.Layer] as TypeJsonArray); + // GV Special WMS group layer case situation... + // TODO: Bug - There was an issue with the layer configuration for a long time ('Private element not on object') which + // TO.DOCONT: was causing the loop below to fail before finishing the first loop (midway deep into 'registerLayerConfigInit()'). + // TO.DOCONT: The fact that an exception was raised was actually provoking the behavior that we want with the UI display of + // TO.DOCONT: the WMS group layers (between Layers and Details tabs). + // TO.DOCONT: However, fixing the cloning issue and completing the loops as they should be, was causing an unwanted side-effect + // TO.DOCONT: with the UI. + // TO.DOCONT: Therefore, we're making it crash on purpose by raising a 'Processing cancelled' exception for now to keep + // TO.DOCONT: the behavior the same as before.. + + // Assign the layer name right away + layerConfig.layerName = layer.Title as string; + + // Loop on the sub layers arrayOfLayerMetadata.forEach((subLayer) => { // Log for pertinent debugging purposes logger.logTraceCore('WMS - createGroupLayer', 'Cloning the layer config', layerConfig.layerPath); - const subLayerEntryConfig: TypeLayerEntryConfig = cloneDeep(layerConfig); - subLayerEntryConfig.parentLayerConfig = Cast(layerConfig); + const subLayerEntryConfig: ConfigBaseClass = layerConfig.clone(); + subLayerEntryConfig.parentLayerConfig = layerConfig; subLayerEntryConfig.layerId = subLayer.Name as string; subLayerEntryConfig.layerName = subLayer.Title as string; - newListOfLayerEntryConfig.push(subLayerEntryConfig); + newListOfLayerEntryConfig.push(subLayerEntryConfig as TypeLayerEntryConfig); // FIXME: Temporary patch to keep the behavior until those layer classes don't exist this.getMapViewer().layer.registerLayerConfigInit(subLayerEntryConfig); + + // If we don't want all sub layers (simulating the 'Private element not on object' error we had for long time) + if (!this.fullSubLayers) { + // Skip the rest on purpose (ref TODO: Bug above) + throw new Error('Processing cancelled'); + } }); - const switchToGroupLayer = Cast(layerConfig); - switchToGroupLayer.entryType = CONST_LAYER_ENTRY_TYPES.GROUP; - switchToGroupLayer.layerName = layer.Title as string; - switchToGroupLayer.isMetadataLayerGroup = true; - switchToGroupLayer.listOfLayerEntryConfig = newListOfLayerEntryConfig; + // TODO: Bug - Continuation of the TODO Bug above.. Purposely don't do this anymore (the throw will cause skipping of this) + // TO.DOCONT: in order to reproduce the old behavior now that the 'Private element' bug is fixed.. + // TO.DOCONT: Leaving the code there, uncommented, so that if/when we remove the throw of the + // TO.DOCONT: 'Processing cancelled' this gets executed as would be expected + layerConfig.entryType = CONST_LAYER_ENTRY_TYPES.GROUP; + layerConfig.isMetadataLayerGroup = true; + layerConfig.listOfLayerEntryConfig = newListOfLayerEntryConfig; this.validateListOfLayerEntryConfig(newListOfLayerEntryConfig); } diff --git a/packages/geoview-core/src/geo/layer/gv-layers/abstract-gv-layer.ts b/packages/geoview-core/src/geo/layer/gv-layers/abstract-gv-layer.ts index 35f29637f1b..947c01b2398 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/abstract-gv-layer.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/abstract-gv-layer.ts @@ -393,15 +393,15 @@ export abstract class AbstractGVLayer extends AbstractBaseLayer { /** * Queries the legend. - * This function raises legend querying and queried events. It calls the overridable getLegend() function. + * This function raises legend querying and queried events. It calls the overridable onFetchLegend() function. * @returns {Promise} The promise when the legend (or null) will be received */ queryLegend(): Promise { // Emit that the legend has been queried this.#emitLegendQuerying(); - // Get the legend - const promiseLegend = this.getLegend(); + // Fetch the legend by calling the overridable function + const promiseLegend = this.onFetchLegend(); // Whenever the promise resolves promiseLegend @@ -452,8 +452,7 @@ export abstract class AbstractGVLayer extends AbstractBaseLayer { * of the layerConfig object is undefined, the legend property of the object returned will be null. * @returns {Promise} The legend of the layer. */ - async getLegend(): Promise { - // TODO: Refactor - Layers refactoring. Rename this function to onFetchLegend() once the layers refactoring is done + async onFetchLegend(): Promise { try { const legend: TypeLegend = { type: this.getLayerConfig().geoviewLayerConfig.geoviewLayerType, diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts index 00cd055c094..a2014201b40 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-dynamic.ts @@ -665,10 +665,10 @@ export class GVEsriDynamic extends AbstractGVRaster { * Overrides the fetching of the legend for an Esri Dynamic layer. * @returns {Promise} The legend of the layer or null. */ - override async getLegend(): Promise { + override async onFetchLegend(): Promise { const layerConfig = this.getLayerConfig(); // Only raster layers need the alternate code - if (layerConfig.getLayerMetadata()?.type !== 'Raster Layer') return super.getLegend(); + if (layerConfig.getLayerMetadata()?.type !== 'Raster Layer') return super.onFetchLegend(); try { if (!layerConfig) return null; diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-image.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-image.ts index e4f6802064b..a13b91cbcf9 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-image.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-esri-image.ts @@ -102,7 +102,7 @@ export class GVEsriImage extends AbstractGVRaster { * Overrides the fetching of the legend for an Esri image layer. * @returns {Promise} The legend of the layer or null. */ - override async getLegend(): Promise { + override async onFetchLegend(): Promise { const layerConfig = this.getLayerConfig(); try { if (!layerConfig) return null; diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-image-static.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-image-static.ts index 8e3960f7c02..7b89315e784 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-image-static.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-image-static.ts @@ -102,7 +102,7 @@ export class GVImageStatic extends AbstractGVRaster { * Overrides the fetching of the legend for an Esri image layer. * @returns {Promise} The legend of the layer or null. */ - override async getLegend(): Promise { + override async onFetchLegend(): Promise { const layerConfig = this.getLayerConfig(); try { const legendImage = await GVImageStatic.#getLegendImage(layerConfig!); diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-wms.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-wms.ts index af8dde51d58..83d7599f46b 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-wms.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/gv-wms.ts @@ -180,9 +180,12 @@ export class GVWMS extends AbstractGVRaster { } } } - } else if (infoFormat === 'text/html') { - featureMember = { html: response.data }; - } else featureMember = { plain_text: { '#text': response.data } }; + } else if (response.data && response.data.length > 0) { + // The response has any data to show + if (infoFormat === 'text/html') { + featureMember = { html: response.data }; + } else featureMember = { plain_text: { '#text': response.data } }; + } if (featureMember) { const featureInfoResult = GVWMS.#formatWmsFeatureInfoResult(featureMember, clickCoordinate); @@ -202,7 +205,7 @@ export class GVWMS extends AbstractGVRaster { * Overrides the fetching of the legend for a WMS layer. * @returns {Promise} The legend of the layer or null. */ - override async getLegend(): Promise { + override async onFetchLegend(): Promise { try { // Get the layer config in a loaded phase const layerConfig = this.getLayerConfig(); @@ -214,7 +217,7 @@ export class GVWMS extends AbstractGVRaster { // If more than 1 if (this.WMSStyles.length > 1) { for (let i = 0; i < this.WMSStyles.length; i++) { - // TODO: refactor - does this await in a loop may haev an impact on performance? + // TODO: refactor - does this await in a loop may have an impact on performance? // TO.DOCONT: In this case here, when glancing at the code, the only reason to await would be if the order that the styleLegend // TO.DOCONT: get added to the styleLegends array MUST be the same order as they are in the WMSStyles array (as in they are 2 arrays with same indexes pointers). // TO.DOCONT: Without the await, WMSStyles[2] stuff could be associated with something in styleLegends[1] position for example (1<>2). @@ -251,7 +254,7 @@ export class GVWMS extends AbstractGVRaster { return legend; } catch (error) { // Log - logger.logError('gv-wms.getLegend()\n', error); + logger.logError('gv-wms.onFetchLegend()\n', error); return null; } } diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts index 1db09858a60..674e9d499bc 100644 --- a/packages/geoview-core/src/geo/layer/layer.ts +++ b/packages/geoview-core/src/geo/layer/layer.ts @@ -165,6 +165,9 @@ export class LayerApi { // Maximum time duration to wait when registering a layer for the time slider static #MAX_WAIT_TIME_SLIDER_REGISTRATION = 20000; + // Temporary debugging flag indicating if we want the WMS group layers to have their sub layers fully blown up + static DEBUG_WMS_LAYER_GROUP_FULL_SUB_LAYERS = false; + /** * Initializes layer types and listen to add/remove layer events from outside * @param {MapViewer} mapViewer - A reference to the map viewer @@ -639,7 +642,7 @@ export class LayerApi { } else if (layerConfigIsCSV(geoviewLayerConfig)) { layerBeingAdded = new CSV(this.getMapId(), geoviewLayerConfig); } else if (layerConfigIsWMS(geoviewLayerConfig)) { - layerBeingAdded = new WMS(this.getMapId(), geoviewLayerConfig); + layerBeingAdded = new WMS(this.getMapId(), geoviewLayerConfig, LayerApi.DEBUG_WMS_LAYER_GROUP_FULL_SUB_LAYERS); } else if (layerConfigIsEsriDynamic(geoviewLayerConfig)) { layerBeingAdded = new EsriDynamic(this.getMapId(), geoviewLayerConfig); } else if (layerConfigIsEsriFeature(geoviewLayerConfig)) { @@ -1202,12 +1205,19 @@ export class LayerApi { // Remove layer info from registered layers this.getLayerEntryConfigIds().forEach((registeredLayerPath) => { if (registeredLayerPath.startsWith(`${layerPath}/`) || registeredLayerPath === layerPath) { - // Remove ol layer + // Remove actual OL layer from the map if (this.getOLLayer(registeredLayerPath)) this.mapViewer.map.removeLayer(this.getOLLayer(registeredLayerPath) as BaseLayer); - // Unregister layer + + // Unregister layer config from the application this.unregisterLayerConfig(this.getLayerEntryConfig(registeredLayerPath)!); - // Remove from registered layers + + // Remove from registered layer configs delete this.#layerEntryConfigs[registeredLayerPath]; + delete this.#geoviewLayers[registeredLayerPath]; + + // Remove from registered layers + delete this.#gvLayers[registeredLayerPath]; + delete this.#olLayers[registeredLayerPath]; } }); diff --git a/packages/geoview-core/src/geo/utils/projection.ts b/packages/geoview-core/src/geo/utils/projection.ts index 2163caa1d0a..b5dde4d2366 100644 --- a/packages/geoview-core/src/geo/utils/projection.ts +++ b/packages/geoview-core/src/geo/utils/projection.ts @@ -29,6 +29,7 @@ export abstract class Projection { 3578: 'EPSG:3578', LCC: 'EPSG:3978', 3979: 'EPSG:3979', + 42101: 'EPSG:42101', 102100: 'EPSG:102100', // TODO: Minor - The official name of this projection is ESRI:102100 (not EPSG:102100). However, for the purpose of simplification in GeoView code base, we name it with EPSG prefix. 102184: 'EPSG:102184', // TODO: Minor - The official name of this projection is ESRI:102184 (not EPSG:102184). However, for the purpose of simplification in GeoView code base, we name it with EPSG prefix. 102190: 'EPSG:102190', // TODO: Minor - The official name of this projection is ESRI:102190 (not EPSG:102190). However, for the purpose of simplification in GeoView code base, we name it with EPSG prefix. @@ -414,6 +415,21 @@ function init4269Projection(): void { if (projection) Projection.PROJECTIONS['4269'] = projection; } +/** + * Initializes the EPSG:42101 projection + */ +function init42101Projection(): void { + proj4.defs( + Projection.PROJECTION_NAMES[42101], + '+proj=lcc +lat_0=0 +lon_0=-95 +lat_1=49 +lat_2=77 +x_0=0 +y_0=-8000000 +datum=WGS84 +units=m +no_defs +type=crs' + ); + register(proj4); + + const projection = olGetProjection(Projection.PROJECTION_NAMES[42101]); + + if (projection) Projection.PROJECTIONS['42101'] = projection; +} + /** * Initializes the EPSG:3979 projection */ @@ -499,6 +515,7 @@ initCSRS98Projection(); init3578Projection(); init3979Projection(); init4269Projection(); +init42101Projection(); init102100Projection(); init102184Projection(); init102190Projection(); diff --git a/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts b/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts index 9afd8a5c097..731ee1ca66a 100644 --- a/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts +++ b/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts @@ -324,7 +324,8 @@ function getStyleGeometry(settings: TypeKindOfVectorSettings): TypeStyleGeometry */ function processUniqueValueRenderer(renderer: EsriUniqueValueRenderer): TypeLayerStyleConfig | undefined { const style: TypeLayerStyleConfig = {}; - const fields = [renderer.field1]; + const fields = []; + if (renderer.field1) fields.push(renderer.field1); if (renderer.field2) fields.push(renderer.field2); if (renderer.field3) fields.push(renderer.field3);