From ee4650159ad149a92849e455ed6559a20353e7cd Mon Sep 17 00:00:00 2001 From: AlexNRCan Date: Thu, 21 Nov 2024 16:22:36 -0500 Subject: [PATCH] - Added support for more projections (wkids (3578, 3979, 102184, 102190)) to support a wider variety of services. - Added support for wkt projections (when wkt instead of wkid is used) in the metadata. - Fixed a few projection issues. - Fixed an issue with the `supportsDynamicLayers` flag, now continuing the layer loading (instead of stopping with an error) - Fixed a couple of ESLint warnings - Added some robustness when extent is badly or not defined at the service level --- .../configs/navigator/06-basic-footer.json | 124 +---------- .../geoview-core/src/api/config/config-api.ts | 3 +- .../raster-config/wms-config.ts | 3 +- .../group-node/esri-group-layer-config.ts | 8 +- .../abstract-base-esri-layer-entry-config.ts | 6 +- .../map-event-processor.ts | 8 +- .../geoview-core/src/api/plugin/plugin.ts | 3 +- .../core/components/app-bar/app-bar-api.ts | 2 +- .../add-new-layer/add-new-layer.tsx | 42 +++- .../components/map-info/map-info-style.ts | 3 +- .../src/core/utils/localStorage.ts | 4 +- .../src/core/utils/useWhatChanged.ts | 2 +- .../geoview-core/src/core/utils/utilities.ts | 2 + .../src/geo/layer/basemap/basemap.ts | 2 + .../geoview-layers/abstract-geoview-layers.ts | 2 +- .../layer/geoview-layers/esri-layer-common.ts | 6 +- .../raster/abstract-geoview-raster.ts | 5 +- .../geoview-layers/raster/esri-dynamic.ts | 11 +- .../geoview-layers/vector/ogc-feature.ts | 4 +- .../gv-layers/raster/abstract-gv-raster.ts | 3 +- packages/geoview-core/src/geo/layer/layer.ts | 5 +- .../geoview-core/src/geo/map/map-viewer.ts | 24 +- .../geoview-core/src/geo/utils/projection.ts | 208 +++++++++++++++++- .../src/geo/utils/renderer/esri-renderer.ts | 47 ++-- 24 files changed, 329 insertions(+), 198 deletions(-) diff --git a/packages/geoview-core/public/configs/navigator/06-basic-footer.json b/packages/geoview-core/public/configs/navigator/06-basic-footer.json index 647ff91230a..c0de06334df 100644 --- a/packages/geoview-core/public/configs/navigator/06-basic-footer.json +++ b/packages/geoview-core/public/configs/navigator/06-basic-footer.json @@ -9,130 +9,10 @@ "shaded": true, "labeled": true }, - "overlayObjects": { - "pointMarkers": { - "group1": [ - { - "id": "1", - "coordinate": [-100, 60], - "color": "blue", - "opacity": 0.5 - }, - { - "id": "2", - "coordinate": [-80, 65], - "color": "rgb(0, 226, 0)" - }, - { - "id": "3", - "coordinate": [-115, 66], - "color": "#C52022" - } - ] - } - }, "listOfGeoviewLayerConfig": [ { - "geoviewLayerId": "airborne_radioactivity", - "geoviewLayerName": "Airborne Radioactivity", - "metadataAccessPath": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/HC/airborne_radioactivity_en/MapServer", - "geoviewLayerType": "esriDynamic", - "listOfLayerEntryConfig": [ - { - "layerId": "1" - } - ] - }, - { - "geoviewLayerId": "uniqueValueId", - "geoviewLayerName": "uniqueValue", - "metadataAccessPath": "https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer/", - "geoviewLayerType": "esriFeature", - "listOfLayerEntryConfig": [ - { - "layerId": "1", - "layerFilter": "E_Province = 'Alberta' or E_Province = 'Manitoba'" - } - ] - }, - { - "geoviewLayerId": "fail_bad_url", - "geoviewLayerName": "Test fail bad url", - "metadataAccessPath": "https://maps-cartes.services.geo.caaaaaa/server_serveur/rest/services/HC/airborne_radioactivity_en/MapServer", - "geoviewLayerType": "esriDynamic", - "listOfLayerEntryConfig": [ - { - "layerId": "1" - } - ] - }, - { - "geoviewLayerId": "esriFeatureLYR5", - "geoviewLayerName": "Top Projects", - "metadataAccessPath": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/900A_and_top_100_en/MapServer/", - "geoviewLayerType": "esriFeature", - "listOfLayerEntryConfig": [ - { - "layerId": "0" - } - ] - }, - { - "geoviewLayerId": "nonmetalmines", - "geoviewLayerName": "Non metal mines", - "metadataAccessPath": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/900A_and_top_100_en/MapServer/", - "geoviewLayerType": "esriFeature", - "listOfLayerEntryConfig": [ - { - "layerId": "5" - } - ] - }, - { - "geoviewLayerId": "geojsonLYR1", - "geoviewLayerName": "GeoJSON Sample", - "metadataAccessPath": "./datasets/geojson/metadata.meta", - "geoviewLayerType": "GeoJSON", - "listOfLayerEntryConfig": [ - { - "layerId": "polygons.json", - "layerName": "Polygons" - }, - { - "layerId": "lines.json", - "layerName": "Lines" - }, - { - "entryType": "group", - "layerId": "point-feature-group", - "layerName": "Points & Icons", - "listOfLayerEntryConfig": [ - { - "layerId": "icon_points.json", - "layerName": "Icons" - }, - { - "layerId": "points.json", - "layerName": "Points" - }, - { - "layerId": "points_1.json", - "layerName": "Points 1", - "initialSettings": { - "controls": { "remove": true } - } - }, - { - "layerId": "points_2.json", - "layerName": "Points 2" - }, - { - "layerId": "points_3.json", - "layerName": "Points 3" - } - ] - } - ] + "geoviewLayerId": "b7859aed-bb3c-4abe-8a98-444c194b260f", + "geoviewLayerType": "geoCore" } ] }, diff --git a/packages/geoview-core/src/api/config/config-api.ts b/packages/geoview-core/src/api/config/config-api.ts index 799fd9b81f7..4db706de4f1 100644 --- a/packages/geoview-core/src/api/config/config-api.ts +++ b/packages/geoview-core/src/api/config/config-api.ts @@ -487,7 +487,8 @@ export class ConfigApi { // we return undefined to signal that we cannot create the GeoView layer. if (!geoviewLayerConfig) return undefined; } catch (error) { - logger.logError(`Unable to convert GeoCore layer (Id=${serviceAccessString}).`); + // Log error + logger.logError(`Unable to convert GeoCore layer (Id=${serviceAccessString}).`, error); return undefined; } } else { 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 acfc71d13e8..4cc2d0546de 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 @@ -285,7 +285,8 @@ export class WmsLayerConfig extends AbstractGeoviewLayerConfig { // In the event of a service metadata reading error, we report the geoview layer and all its sublayers as being in error. this.setErrorDetectedFlag(); this.setErrorDetectedFlagForAllLayers(this.listOfLayerEntryConfig); - logger.logError(`Error detected while reading WMS metadata for geoview layer ${this.geoviewLayerId}.`); + // Log error + logger.logError(`Error detected while reading WMS metadata for geoview layer ${this.geoviewLayerId}.`, error); } } 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 640d4dbd949..84c7291abb4 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 @@ -98,7 +98,7 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { if (sourceProj === '4326') this.initialSettings.extent = validateExtentWhenDefined(metadataExtent); else this.initialSettings.extent = validateExtentWhenDefined( - Projection.transformExtent(metadataExtent, `EPSG:${sourceProj}`, Projection.PROJECTION_NAMES.LNGLAT) + Projection.transformExtentFromObj(metadataExtent, layerMetadata.initialExtent.spatialReference, Projection.PROJECTION_NAMES.LNGLAT) ); if (layerMetadata.defaultVisibility !== undefined) this.initialSettings.states!.visible = layerMetadata.defaultVisibility as boolean; @@ -133,7 +133,11 @@ export class EsriGroupLayerConfig extends GroupLayerEntryConfig { if (sourceProj === '4326') this.initialSettings.extent = validateExtentWhenDefined(metadataExtent); else this.initialSettings.extent = validateExtentWhenDefined( - Projection.transformExtent(metadataExtent, `EPSG:${sourceProj}`, Projection.PROJECTION_NAMES.LNGLAT) + Projection.transformExtentFromObj( + metadataExtent, + serviceMetadata.initialExtent.spatialReference, + Projection.PROJECTION_NAMES.LNGLAT + ) ); this.initialSettings.states!.queryable = (serviceMetadata?.capabilities as string)?.includes('Query') || false; 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 8e81b732ad5..5502e59df3a 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 @@ -86,7 +86,11 @@ export abstract class AbstractBaseEsriLayerEntryConfig extends AbstractBaseLayer const sourceProj = layerMetadata.extent.spatialReference.wkid; if (sourceProj === '4326') this.initialSettings.extent = validateExtentWhenDefined(metadataExtent); else { - metadataExtent = Projection.transformExtent(metadataExtent, `EPSG:${sourceProj}`, Projection.PROJECTION_NAMES.LNGLAT); + metadataExtent = Projection.transformExtentFromObj( + metadataExtent, + layerMetadata.extent.spatialReference, + Projection.PROJECTION_NAMES.LNGLAT + ); this.initialSettings.extent = validateExtentWhenDefined(metadataExtent); } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts index c2801a1cdfa..c437f8c86ed 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts @@ -893,10 +893,10 @@ export class MapEventProcessor extends AbstractEventProcessor { const projectionConfig = Projection.PROJECTIONS[this.getMapState(mapId).currentProjection]; if (bbox) { - // GV There were issues with fromLonLat in rare cases in LCC projections, transformExtent seems to solve them. - // GV fromLonLat and transformExtent give differing results in many cases, fromLonLat had issues with the first + // GV There were issues with fromLonLat in rare cases in LCC projections, transformExtentFromProj seems to solve them. + // GV fromLonLat and transformExtentFromProj give differing results in many cases, fromLonLat had issues with the first // GV three results from a geolocator search for "vancouver river" - const convertedExtent = Projection.transformExtent(bbox, Projection.PROJECTION_NAMES.LNGLAT, projectionConfig); + const convertedExtent = Projection.transformExtentFromProj(bbox, Projection.PROJECTION_NAMES.LNGLAT, projectionConfig); // Highlight this.getMapViewerLayerAPI(mapId).featureHighlight.highlightGeolocatorBBox(convertedExtent); @@ -958,7 +958,7 @@ export class MapEventProcessor extends AbstractEventProcessor { // If extent is in config, use it if (getGeoViewStore(mapId).getState().mapConfig!.map.viewSettings.initialView?.extent) { const lnglatExtent = getGeoViewStore(mapId).getState().mapConfig!.map.viewSettings.initialView!.extent as Extent; - extent = Projection.transformExtent(lnglatExtent, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${currProjection}`); + extent = Projection.transformExtentFromProj(lnglatExtent, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${currProjection}`); options.padding = [0, 0, 0, 0]; } diff --git a/packages/geoview-core/src/api/plugin/plugin.ts b/packages/geoview-core/src/api/plugin/plugin.ts index 05fcdf840df..2a6dbf95e7f 100644 --- a/packages/geoview-core/src/api/plugin/plugin.ts +++ b/packages/geoview-core/src/api/plugin/plugin.ts @@ -133,7 +133,8 @@ export abstract class Plugin { pluginConfigObj = result; } } catch (error) { - // config not found + // Log error + logger.logError(`Config not found.`, error); } } diff --git a/packages/geoview-core/src/core/components/app-bar/app-bar-api.ts b/packages/geoview-core/src/core/components/app-bar/app-bar-api.ts index db0a78fce36..d955e63585f 100644 --- a/packages/geoview-core/src/core/components/app-bar/app-bar-api.ts +++ b/packages/geoview-core/src/core/components/app-bar/app-bar-api.ts @@ -221,7 +221,7 @@ export class AppBarApi { this.#emitAppBarRemoved({ buttonPanelId, group }); } catch (error) { // Log - logger.logError(`Failed to get app bar panel button ${group}/${buttonPanelId}`); + logger.logError(`Failed to get app bar panel button ${group}/${buttonPanelId}`, error); } } 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 e7a938a54d5..33e409007a1 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 @@ -267,8 +267,10 @@ export function AddNewLayer(): JSX.Element { } else { setLayerList(layers); } - } catch (err) { - if ((err as Error).message === 'proj') { + } catch (error) { + // Log error + logger.logError(error); + if ((error as Error).message === 'proj') { emitErrorProj('WMS', proj, supportedProj); } else { emitErrorServer('WMS'); @@ -315,8 +317,10 @@ export function AddNewLayer(): JSX.Element { } else { setLayerList(layers); } - } catch (err) { + } catch (error) { emitErrorServer('WFS'); + // Log error + logger.logError(error); return false; } return true; @@ -404,8 +408,10 @@ export function AddNewLayer(): JSX.Element { } else { setLayerList(layers); } - } catch (err) { + } catch (error) { emitErrorServer('OGC API Feature'); + // Log error + logger.logError(error); return false; } return true; @@ -431,8 +437,10 @@ export function AddNewLayer(): JSX.Element { setLayerList(layers); } } - } catch (err) { + } catch (error) { emitErrorServer('GeoCore UUID'); + // Log error + logger.logError(error); return false; } return true; @@ -504,8 +512,10 @@ export function AddNewLayer(): JSX.Element { } else { throw new Error('err'); } - } catch (err) { + } catch (error) { emitErrorServer(esriOptions(type).err); + // Log error + logger.logError(error); return false; } return true; @@ -540,8 +550,10 @@ export function AddNewLayer(): JSX.Element { ]; setLayerName(layers[0].layerName!); setLayerEntries([layers[0]]); - } catch (err) { + } catch (error) { emitErrorServer('ESRI Image'); + // Log error + logger.logError(error); return false; } return true; @@ -583,8 +595,10 @@ export function AddNewLayer(): JSX.Element { ]; setLayerName(layers[0].layerName!); setLayerEntries([layers[0]]); - } catch (err) { + } catch (error) { emitErrorServer('XYZ Tile'); + // Log error + logger.logError(error); return false; } return true; @@ -621,8 +635,10 @@ export function AddNewLayer(): JSX.Element { ]; setLayerName(layers[0].layerName!); setLayerEntries([layers[0]]); - } catch (err) { + } catch (error) { emitErrorServer('CSV'); + // Log error + logger.logError(error); return false; } return true; @@ -687,8 +703,10 @@ export function AddNewLayer(): JSX.Element { setLayerName(layers[0].layerName!); setLayerEntries([layers[0]]); } - } catch (err) { + } catch (error) { emitErrorServer('GeoJSON'); + // Log error + logger.logError(error); return false; } return true; @@ -723,8 +741,10 @@ export function AddNewLayer(): JSX.Element { ]; setLayerName(layers[0].layerName!); setLayerEntries([layers[0]]); - } catch (err) { + } catch (error) { emitErrorServer('GeoPackage'); + // Log error + logger.logError(error); return false; } return true; diff --git a/packages/geoview-core/src/core/components/map-info/map-info-style.ts b/packages/geoview-core/src/core/components/map-info/map-info-style.ts index 77acb33c23c..f08a71d7617 100644 --- a/packages/geoview-core/src/core/components/map-info/map-info-style.ts +++ b/packages/geoview-core/src/core/components/map-info/map-info-style.ts @@ -1,5 +1,6 @@ // map-info.tsx -export const getSxClasses = () => ({ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getSxClasses = (): any => ({ mouseScaleControlsContainer: { display: 'flex', flexDirection: 'row', diff --git a/packages/geoview-core/src/core/utils/localStorage.ts b/packages/geoview-core/src/core/utils/localStorage.ts index b8b8366db27..010c893c8e6 100644 --- a/packages/geoview-core/src/core/utils/localStorage.ts +++ b/packages/geoview-core/src/core/utils/localStorage.ts @@ -15,7 +15,7 @@ export const getItemAsNumber = (key: string, defaultValue?: number): number | un // If set and valid: return it; else default will be returned if (levelValueNumber && !Number.isNaN(levelValueNumber)) return levelValueNumber; - } catch (e) { + } catch { // Failed to read localStorage, eat the exception and continue to set the value to the default } @@ -49,7 +49,7 @@ export const getItemAsNumberOrNumberArray = (key: string, defaultValue?: number // If set and valid: return it; else default will be returned if (levelValueNumber && !Number.isNaN(levelValueNumber)) return levelValueNumber; - } catch (e) { + } catch { // Failed to read localStorage, eat the exception and continue to set the value to the default } diff --git a/packages/geoview-core/src/core/utils/useWhatChanged.ts b/packages/geoview-core/src/core/utils/useWhatChanged.ts index 6d96cf25318..1ae47a58e67 100644 --- a/packages/geoview-core/src/core/utils/useWhatChanged.ts +++ b/packages/geoview-core/src/core/utils/useWhatChanged.ts @@ -35,7 +35,7 @@ function stringifyValue(dependencyItem: unknown): string | unknown { } } return dependencyItem; - } catch (e) { + } catch { // Failed to read as string return 'COMPLEX OBJECT'; } diff --git a/packages/geoview-core/src/core/utils/utilities.ts b/packages/geoview-core/src/core/utils/utilities.ts index 507ac6d97ca..87291c09f16 100644 --- a/packages/geoview-core/src/core/utils/utilities.ts +++ b/packages/geoview-core/src/core/utils/utilities.ts @@ -206,6 +206,8 @@ export function getXMLHttpRequest(url: string): Promise { }; jsonObj.send(null); } catch (error) { + // Log warning + logger.logWarning(error); resolve('{}'); } }); diff --git a/packages/geoview-core/src/geo/layer/basemap/basemap.ts b/packages/geoview-core/src/geo/layer/basemap/basemap.ts index ae0ac82e3c1..f8f794f4f71 100644 --- a/packages/geoview-core/src/geo/layer/basemap/basemap.ts +++ b/packages/geoview-core/src/geo/layer/basemap/basemap.ts @@ -265,6 +265,8 @@ export class Basemap { }; } } catch (error) { + // Log error + logger.logError(error); return null; } } diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts b/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts index 57f3161654d..4a730d7b9e9 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/abstract-geoview-layers.ts @@ -1032,7 +1032,7 @@ export abstract class AbstractGeoViewLayer { // // TODO: Check - Are the bounds initially always 4326? // if (projectionCode && bounds) { // bounds = validateExtent(bounds); - // return Projection.transformExtent(bounds, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${projectionCode}`); + // return Projection.transformExtentFromProj(bounds, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${projectionCode}`); // } // } // return bounds; 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 3625f4312ab..729b6f7f49d 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 @@ -339,9 +339,11 @@ export function commonProcessInitialSettings( layerMetadata.extent.xmax, layerMetadata.extent.ymax, ] as Extent; - const latlonExtent = Projection.transformExtent( + + // Transform to latlon extent + const latlonExtent = Projection.transformExtentFromObj( layerExtent, - `EPSG:${layerMetadata.extent.spatialReference.wkid}`, + layerMetadata.extent.spatialReference, Projection.PROJECTION_NAMES.LNGLAT ); layerConfig.initialSettings!.bounds = latlonExtent; diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/abstract-geoview-raster.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/abstract-geoview-raster.ts index 62c72ab2fec..9017db5a51f 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/abstract-geoview-raster.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/abstract-geoview-raster.ts @@ -52,7 +52,8 @@ export abstract class AbstractGeoViewRaster extends AbstractGeoViewLayer { * @returns {OLProjection | undefined} The OpenLayer projection */ getMetadataProjection(): OLProjection | undefined { - return Projection.getProjection(`EPSG:${this.metadata?.fullExtent?.spatialReference?.wkid}`) || undefined; + // Redirect + return Projection.getProjectionFromObj(this.metadata?.fullExtent?.spatialReference); } /** @@ -61,7 +62,7 @@ export abstract class AbstractGeoViewRaster extends AbstractGeoViewLayer { */ getMetadataExtent(layerPath: string): Extent | undefined { // Get the layer metadata precisely - const { extent } = this.getLayerMetadata(layerPath); + const extent = this.getLayerMetadata(layerPath)?.extent; // If found if (extent) { 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 fd2c669396c..1165e65cf9c 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 @@ -165,12 +165,11 @@ export class EsriDynamic extends AbstractGeoViewRaster { */ // GV Layers Refactoring - Obsolete (in config?) esriChildHasDetectedAnError(layerConfig: TypeLayerEntryConfig): boolean { - if (!this.metadata!.supportsDynamicLayers) { - this.layerLoadError.push({ - layer: layerConfig.layerPath, - loggerMessage: `Layer ${layerConfig.layerPath} of map ${this.mapId} does not support dynamic layers.`, - }); - return true; + if (this.metadata?.supportsDynamicLayers === false) { + // Log a warning, but continue + logger.logWarning( + `Layer ${layerConfig.layerPath} of map ${this.mapId} does not technically support dynamic layers per its metadata.` + ); } return false; } diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/vector/ogc-feature.ts b/packages/geoview-core/src/geo/layer/geoview-layers/vector/ogc-feature.ts index 2020f6156a2..f7b783a4e65 100644 --- a/packages/geoview-core/src/geo/layer/geoview-layers/vector/ogc-feature.ts +++ b/packages/geoview-core/src/geo/layer/geoview-layers/vector/ogc-feature.ts @@ -193,9 +193,9 @@ export class OgcFeature extends AbstractGeoViewVector { layerConfig.initialSettings.extent = validateExtentWhenDefined(layerConfig.initialSettings.extent); if (!layerConfig.initialSettings.bounds && foundCollection.extent?.spatial?.bbox && foundCollection.extent?.spatial?.crs) { - const latlonExtent = Projection.transformExtent( + const latlonExtent = Projection.transformExtentFromProj( foundCollection.extent.spatial.bbox[0] as number[], - Projection.getProjection(foundCollection.extent.spatial.crs as string)!, + Projection.getProjectionFromProj(foundCollection.extent.spatial.crs as string)!, Projection.PROJECTION_NAMES.LNGLAT ); layerConfig.initialSettings.bounds = latlonExtent; diff --git a/packages/geoview-core/src/geo/layer/gv-layers/raster/abstract-gv-raster.ts b/packages/geoview-core/src/geo/layer/gv-layers/raster/abstract-gv-raster.ts index 99dd88b0725..0d347864ce6 100644 --- a/packages/geoview-core/src/geo/layer/gv-layers/raster/abstract-gv-raster.ts +++ b/packages/geoview-core/src/geo/layer/gv-layers/raster/abstract-gv-raster.ts @@ -29,7 +29,8 @@ export abstract class AbstractGVRaster extends AbstractGVLayer { * @returns {OLProjection | undefined} The OpenLayer projection */ getMetadataProjection(): OLProjection | undefined { - return Projection.getProjection(`EPSG:${this.getLayerConfig().getServiceMetadata()?.fullExtent?.spatialReference?.wkid}`) || undefined; + // Redirect + return Projection.getProjectionFromObj(this.getLayerConfig().getServiceMetadata()?.fullExtent?.spatialReference); } /** diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts index d9b9425f6ee..a6d83cc7751 100644 --- a/packages/geoview-core/src/geo/layer/layer.ts +++ b/packages/geoview-core/src/geo/layer/layer.ts @@ -167,7 +167,7 @@ export class LayerApi { // ************************************************************ // INDICATES IF USING HYBRID MODE WITH THE NEW GVLAYERS CLASSES // ************************************************************ - static LAYERS_HYBRID_MODE = false; + static LAYERS_HYBRID_MODE = true; /** * Initializes layer types and listen to add/remove layer events from outside @@ -622,6 +622,9 @@ export class LayerApi { hoverable: true, legendCollapsed: false, }; + // TODO: Check - Shouldn't we wait for the layer to actually be retrieved positively from + // TO.DOCONT: Geocore, before adding ordered layer information? What if the + // TO.DOCONT: fetch (createLayersFromUUID) fails, will there be garbage in layer info? MapEventProcessor.addOrderedLayerInfo(this.getMapId(), layerInfo); // Create geocore layer configs and add diff --git a/packages/geoview-core/src/geo/map/map-viewer.ts b/packages/geoview-core/src/geo/map/map-viewer.ts index b860c26cc8a..1ff28238a74 100644 --- a/packages/geoview-core/src/geo/map/map-viewer.ts +++ b/packages/geoview-core/src/geo/map/map-viewer.ts @@ -242,7 +242,11 @@ export class MapViewer { let extentProjected: Extent | undefined; if (mapViewSettings.maxExtent) - extentProjected = Projection.transformExtent(mapViewSettings.maxExtent, Projection.PROJECTION_NAMES.LNGLAT, projection.getCode()); + extentProjected = Projection.transformExtentFromProj( + mapViewSettings.maxExtent, + Projection.PROJECTION_NAMES.LNGLAT, + projection.getCode() + ); const initialMap = new OLMap({ target: mapElement, @@ -1010,7 +1014,11 @@ export class MapViewer { viewOptions.maxZoom = mapView.maxZoom ? mapView.maxZoom : currentView.getMaxZoom(); viewOptions.rotation = mapView.rotation ? mapView.rotation : currentView.getRotation(); if (mapView.maxExtent) - viewOptions.extent = Projection.transformExtent(mapView.maxExtent, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${mapView.projection}`); + viewOptions.extent = Projection.transformExtentFromProj( + mapView.maxExtent, + Projection.PROJECTION_NAMES.LNGLAT, + `EPSG:${mapView.projection}` + ); const newView = new View(viewOptions); this.map.setView(newView); @@ -1084,7 +1092,7 @@ export class MapViewer { }, minZoom: currentView.getMinZoom(), maxZoom: currentView.getMaxZoom(), - maxExtent: Projection.transformExtent(extent, Projection.PROJECTION_NAMES.LNGLAT, currentView.getProjection()), + maxExtent: Projection.transformExtentFromProj(extent, Projection.PROJECTION_NAMES.LNGLAT, currentView.getProjection()), projection: currentView.getProjection().getCode().split(':')[1] as unknown as TypeValidMapProjectionCodes, }; @@ -1271,7 +1279,7 @@ export class MapViewer { */ zoomToLngLatExtentOrCoordinate(extent: Extent | Coordinate, options?: FitOptions): Promise { const fullExtent = extent.length === 2 ? [extent[0], extent[1], extent[0], extent[1]] : extent; - const projectedExtent = Projection.transformExtent( + const projectedExtent = Projection.transformExtentFromProj( fullExtent, Projection.PROJECTION_NAMES.LNGLAT, `EPSG:${this.getMapState().currentProjection}` @@ -1294,8 +1302,8 @@ export class MapViewer { // if (bounds) { // const { currentProjection } = this.getMapState(); // mapBounds = projectionCode - // ? Projection.transformExtent(bounds, `EPSG:${projectionCode}`, Projection.PROJECTIONS[currentProjection], 20) - // : Projection.transformExtent(bounds, Projection.PROJECTIONS[currentProjection], Projection.PROJECTIONS[currentProjection], 25); + // ? Projection.transformExtentFromProj(bounds, `EPSG:${projectionCode}`, Projection.PROJECTIONS[currentProjection], 20) + // : Projection.transformExtentFromProj(bounds, Projection.PROJECTIONS[currentProjection], Projection.PROJECTIONS[currentProjection], 25); // } else { // this.layer.getGeoviewLayerIds().forEach((geoviewLayerId) => { // // TODO Refactor - Layers refactoring. There needs to be a getMetadataBounds (new layers and new config) to complete the full layers migration. @@ -1544,7 +1552,7 @@ export class MapViewer { convertExtentFromProjToMapProj(extent: Extent, fromProj: ProjectionLike): Extent { // If different projections if (fromProj !== this.getProjection().getCode()) { - return Projection.transformExtent(extent, fromProj, this.getProjection()); + return Projection.transformExtentFromProj(extent, fromProj, this.getProjection()); } // Same projection @@ -1560,7 +1568,7 @@ export class MapViewer { convertExtentFromMapProjToProj(extent: Extent, toProj: ProjectionLike): Extent { // If different projections if (toProj !== this.getProjection().getCode()) { - return Projection.transformExtent(extent, this.getProjection(), toProj); + return Projection.transformExtentFromProj(extent, this.getProjection(), toProj); } // Same projection diff --git a/packages/geoview-core/src/geo/utils/projection.ts b/packages/geoview-core/src/geo/utils/projection.ts index 2572e35f1f2..fe4c7cab920 100644 --- a/packages/geoview-core/src/geo/utils/projection.ts +++ b/packages/geoview-core/src/geo/utils/projection.ts @@ -13,6 +13,7 @@ import { } from 'ol/proj'; import { Extent } from 'ol/extent'; import { logger } from '@/core/utils/logger'; +import { TypeJsonObject } from '@/core/types/global-types'; /** * Class used to handle functions for trasforming projections @@ -30,13 +31,23 @@ export abstract class Projection { * constant used for the available projection names */ static PROJECTION_NAMES = { + 3578: 'EPSG:3578', LCC: 'EPSG:3978', + 3979: 'EPSG:3979', + 102184: 'EPSG:102184', // This is technically supposed to be ESRI:102184, but more things would need to change in order to support this, works now + 102190: 'EPSG:102190', // This is technically supposed to be ESRI:102190, but some things would need to change in order to support this, works now WM: 'EPSG:3857', LNGLAT: 'EPSG:4326', CSRS: 'EPSG:4617', CSRS98: 'EPSG:4140', }; + // Incremental number when creating custom WKTs on the fly + static CUSTOM_WKT_NUM: number = 1001; + + // Holding all custom generated wkt + static CUSTOM_WKT_AND_NUM: { [wkt_num: string]: string } = {}; + /** * List of supported projections and their OpenLayers projection */ @@ -65,6 +76,68 @@ export abstract class Projection { return coordinates; } + /** + * Transforms an extent from source projection to destination projection. This returns a new extent (and does not modify the + * original). + * + * @param {Extent} extent The extent to transform. + * @param {TypeJsonObject | undefined} projection An object containing a wkid or wkt property. + * @param {ProjectionLike} destination Destination projection-like. + * @param {number} stops Optional number of stops per side used for the transform. By default only the corners are used. + * + * @returns The new extent transformed in the destination projection. + */ + static transformExtentFromObj( + extent: Extent, + projection: TypeJsonObject | undefined, + destination: ProjectionLike, + stops?: number | undefined + ): Extent { + // Get the projection object + const projectionObj = Projection.getProjectionFromObj(projection); + + // If found + if (projectionObj) { + // Redirect + return Projection.transformExtentFromProj(extent, projectionObj, destination, stops); + } + + // Invalid projection + throw new Error(`Invalid or unsupported projection: ${JSON.stringify(projection)}`); + } + + /** + * Transforms an extent from source projection to destination projection. This returns a new extent (and does not modify the + * original). + * + * @param {Extent} extent The extent to transform. + * @param {number} wkid An EPSG id number. + * @param {ProjectionLike} destination Destination projection-like. + * @param {number} stops Optional number of stops per side used for the transform. By default only the corners are used. + * + * @returns The new extent transformed in the destination projection. + */ + static transformExtentFromWKID(extent: Extent, wkid: number, destination: ProjectionLike, stops?: number | undefined): Extent { + // Redirect + return Projection.transformExtentFromProj(extent, `EPSG:${wkid}`, destination, stops); + } + + /** + * Transforms an extent from source projection to destination projection. This returns a new extent (and does not modify the + * original). + * + * @param {Extent} extent The extent to transform. + * @param {string} customWKT A custom WKT projection. + * @param {ProjectionLike} destination Destination projection-like. + * @param {number} stops Optional number of stops per side used for the transform. By default only the corners are used. + * + * @returns The new extent transformed in the destination projection. + */ + static transformExtentFromWKT(extent: Extent, customWKT: string, destination: ProjectionLike, stops?: number | undefined): Extent { + // Redirect + return Projection.transformExtentFromProj(extent, Projection.getProjectionFromWKT(customWKT), destination, stops); + } + /** * Transforms an extent from source projection to destination projection. This returns a new extent (and does not modify the * original). @@ -76,7 +149,8 @@ export abstract class Projection { * * @returns The new extent transformed in the destination projection. */ - static transformExtent(extent: Extent, source: ProjectionLike, destination: ProjectionLike, stops?: number | undefined): Extent { + static transformExtentFromProj(extent: Extent, source: ProjectionLike, destination: ProjectionLike, stops?: number | undefined): Extent { + // Project return olTransformExtent(extent, source, destination, stops); } @@ -147,10 +221,62 @@ export abstract class Projection { * Wrapper around OpenLayers get function that fetches a Projection object for the code specified. * * @param {ProjectionLike} projectionLike Either a code string which is a combination of authority and identifier such as "EPSG:4326", or an existing projection object, or undefined. - * @return {olProjection | null} — Projection object, or null if not in list. + * @return {olProjection | undefined} — Projection object, or undefined if not in list. + */ + static getProjectionFromObj(projection: TypeJsonObject | undefined): olProjection | undefined { + // If wkid + if (projection && projection.wkid) { + // Redirect + return Projection.getProjectionFromProj(`EPSG:${projection.wkid}`); + } + + // If wkt + if (projection && projection.wkt) { + // Redirect + return Projection.getProjectionFromWKT(projection.wkt as string); + } + + // Nope + return undefined; + } + + /** + * Wrapper around OpenLayers get function that fetches a Projection object for the code specified. + * + * @param {ProjectionLike} projectionLike Either a code string which is a combination of authority and identifier such as "EPSG:4326", or an existing projection object, or undefined. + * @return {olProjection | undefined} — Projection object, or undefined if not in list. */ - static getProjection(projectionLike: ProjectionLike): olProjection | null { - return olGetProjection(projectionLike); + static getProjectionFromWKT(customWKT: string): olProjection | undefined { + // If the custom WKT doesn't exist + if (!this.CUSTOM_WKT_AND_NUM[customWKT]) { + // Register a new custom projection using the WKT + const WKT_KEY = `CUSTOM:${this.CUSTOM_WKT_NUM}`; + // Increment for the next one + this.CUSTOM_WKT_NUM++; + + // Register in proj4js + proj4.defs(WKT_KEY, customWKT); + register(proj4); + + // Add it for the next time this WKT is used + this.CUSTOM_WKT_AND_NUM[customWKT] = WKT_KEY; + } + + // Get the key + const wktKey = this.CUSTOM_WKT_AND_NUM[customWKT]; + + // Get the projection + return Projection.getProjectionFromProj(wktKey); + } + + /** + * Wrapper around OpenLayers get function that fetches a Projection object for the code specified. + * + * @param {ProjectionLike} projectionLike Either a code string which is a combination of authority and identifier such as "EPSG:4326", or an existing projection object, or undefined. + * @return {olProjection | undefined} — Projection object, or undefined if not in list. + */ + static getProjectionFromProj(projectionLike: ProjectionLike): olProjection | undefined { + return olGetProjection(projectionLike) || undefined; } /** @@ -196,7 +322,7 @@ function initLCCProjection(): void { // define 3978 projection proj4.defs( Projection.PROJECTION_NAMES.LCC, - '+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs' + '+proj=lcc +lat_0=49 +lon_0=-95 +lat_1=49 +lat_2=77 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs' ); register(proj4); @@ -210,7 +336,7 @@ function initLCCProjection(): void { */ function initCSRSProjection(): void { // define 4617 projection - proj4.defs(Projection.PROJECTION_NAMES.CSRS, '+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs'); + proj4.defs(Projection.PROJECTION_NAMES.CSRS, '+proj=longlat +ellps=GRS80 +no_defs +type=crs'); register(proj4); const projection = olGetProjection(Projection.PROJECTION_NAMES.CSRS); @@ -223,7 +349,7 @@ function initCSRSProjection(): void { */ function initCSRS98Projection(): void { // define 4140 projection - proj4.defs(Projection.PROJECTION_NAMES.CSRS98, '+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs'); + proj4.defs(Projection.PROJECTION_NAMES.CSRS98, '+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs +type=crs'); register(proj4); const projection = olGetProjection(Projection.PROJECTION_NAMES.CSRS98); @@ -231,10 +357,78 @@ function initCSRS98Projection(): void { if (projection) Projection.PROJECTIONS['4140'] = projection; } +/** + * initialize EPSG:3578 projection + * @private + */ +function init3578Projection(): void { + proj4.defs( + Projection.PROJECTION_NAMES[3578], + '+proj=aea +lat_0=59 +lon_0=-132.5 +lat_1=61.6666666666667 +lat_2=68 +x_0=500000 +y_0=500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs' + ); + register(proj4); + + const projection = olGetProjection(Projection.PROJECTION_NAMES[3578]); + + if (projection) Projection.PROJECTIONS['3578'] = projection; +} + +/** + * initialize EPSG:3979 projection + * @private + */ +function init3979Projection(): void { + proj4.defs( + Projection.PROJECTION_NAMES[3979], + '+proj=lcc +lat_0=49 +lon_0=-95 +lat_1=49 +lat_2=77 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=-0.991,1.9072,0.5129,-1.25033e-07,-4.6785e-08,-5.6529e-08,0 +units=m +no_defs +type=crs' + ); + register(proj4); + + const projection = olGetProjection(Projection.PROJECTION_NAMES[3979]); + + if (projection) Projection.PROJECTIONS['3979'] = projection; +} + +/** + * initialize EPSG:102184 (ESRI:102184) projection + * @private + */ +function init102184Projection(): void { + proj4.defs( + Projection.PROJECTION_NAMES[102184], + '+proj=tmerc +lat_0=0 +lon_0=-115 +k=0.9992 +x_0=500000 +y_0=0 +datum=NAD83 +units=m +no_defs +type=crs' + ); + register(proj4); + + const projection = olGetProjection(Projection.PROJECTION_NAMES[102184]); + + if (projection) Projection.PROJECTIONS['102184'] = projection; +} + +/** + * initialize EPSG:102190 (ESRI:102190) projection + * @private + */ +function init102190Projection(): void { + proj4.defs( + Projection.PROJECTION_NAMES[102190], + '+proj=aea +lat_0=45 +lon_0=-126 +lat_1=50 +lat_2=58.5 +x_0=1000000 +y_0=0 +datum=NAD83 +units=m +no_defs +type=crs' + ); + register(proj4); + + const projection = olGetProjection(Projection.PROJECTION_NAMES[102190]); + + if (projection) Projection.PROJECTIONS['102190'] = projection; +} + // Initialize the supported projections initCRS84Projection(); initWMProjection(); initLCCProjection(); initCSRSProjection(); initCSRS98Projection(); +init3578Projection(); +init3979Projection(); +init102184Projection(); +init102190Projection(); logger.logInfo('Projections initialized'); 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 d85e0f59c3d..37f7395b578 100644 --- a/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts +++ b/packages/geoview-core/src/geo/utils/renderer/esri-renderer.ts @@ -341,16 +341,20 @@ function processUniqueValueRenderer(renderer: EsriUniqueValueRenderer): TypeLaye }); } }); - const styleGeometry = getStyleGeometry(uniqueValueStyleInfo[0].settings); - const styleSettings: TypeLayerStyleSettings = { - type: 'uniqueValue', - hasDefault: false, - fields, - info: uniqueValueStyleInfo, - }; - if (styleGeometry) { - style[styleGeometry] = styleSettings; - return style; + + // If any found + if (uniqueValueStyleInfo.length > 0) { + const styleGeometry = getStyleGeometry(uniqueValueStyleInfo[0].settings); + const styleSettings: TypeLayerStyleSettings = { + type: 'uniqueValue', + hasDefault: false, + fields, + info: uniqueValueStyleInfo, + }; + if (styleGeometry) { + style[styleGeometry] = styleSettings; + return style; + } } return undefined; } @@ -431,17 +435,20 @@ function processClassBreakRenderer(EsriRenderer: EsriClassBreakRenderer): TypeLa }); } - const styleGeometry = getStyleGeometry(classBreakStyleInfo[0].settings); - if (styleGeometry) { - const styleSettings: TypeLayerStyleSettings = { - type: 'classBreaks', - fields: [field], - hasDefault, - info: classBreakStyleInfo, - }; + // If any found + if (classBreakStyleInfo.length > 0) { + const styleGeometry = getStyleGeometry(classBreakStyleInfo[0].settings); + if (styleGeometry) { + const styleSettings: TypeLayerStyleSettings = { + type: 'classBreaks', + fields: [field], + hasDefault, + info: classBreakStyleInfo, + }; - style[styleGeometry] = styleSettings; - return style; + style[styleGeometry] = styleSettings; + return style; + } } return undefined; }