diff --git a/packages/geoview-core/public/configs/navigator/05-layer-zoom-levels.json b/packages/geoview-core/public/configs/navigator/05-layer-zoom-levels.json
new file mode 100644
index 00000000000..394ea4ae369
--- /dev/null
+++ b/packages/geoview-core/public/configs/navigator/05-layer-zoom-levels.json
@@ -0,0 +1,76 @@
+{
+ "map": {
+ "interaction": "dynamic",
+ "viewSettings": {
+ "projection": 3978
+ },
+ "basemapOptions": {
+ "basemapId": "transport",
+ "shaded": false,
+ "labeled": true
+ },
+ "listOfGeoviewLayerConfig": [
+ {
+ "geoviewLayerId": "wmsLYR1",
+ "geoviewLayerName": "earthquakes",
+ "metadataAccessPath": "https://maps-cartes.services.geo.ca/server_serveur/rest/services/NRCan/earthquakes_en/MapServer/",
+ "geoviewLayerType": "esriDynamic",
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "0",
+ "layerName": "Limited by Scale - Earthquakes 1980 - 1990, by Magnitude",
+ "maxScale": 10000000
+ }
+ ]
+ },
+ {
+ "geoviewLayerId": "89d44ba6-7236-48ed-afab-f25a98c846ef",
+ "geoviewLayerType": "geoCore",
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "pub:WHSE_IMAGERY_AND_BASE_MAPS.MOT_CULVERTS_SP",
+ "layerName": "Limited by Service - BC Minitstry of Transportation Culverts"
+ }
+ ]
+ },
+ {
+ "geoviewLayerId": "uniqueValueId",
+ "geoviewLayerName": "uniqueValue",
+ "metadataAccessPath": "https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer/",
+ "geoviewLayerType": "esriDynamic",
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "4",
+ "layerName": "Limited by Zoom - Water Quality",
+ "entryType": "group",
+ "initialSettings": {
+ "minZoom": 5
+ },
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "5",
+ "initialSettings": {
+ "minZoom": 7
+ }
+ },
+ {
+ "layerId": "6"
+ },
+ {
+ "layerId": "7"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "components": ["overview-map"],
+ "footerBar": {
+ "tabs": {
+ "core": ["legend", "layers", "details", "data-table"]
+ }
+ },
+ "corePackages": [],
+ "theme": "geo.ca"
+}
diff --git a/packages/geoview-core/public/configs/navigator/15-xyz-tile.json b/packages/geoview-core/public/configs/navigator/15-xyz-tile.json
index 8dea89f19f3..d0166a2f38f 100644
--- a/packages/geoview-core/public/configs/navigator/15-xyz-tile.json
+++ b/packages/geoview-core/public/configs/navigator/15-xyz-tile.json
@@ -22,7 +22,22 @@
"initialSettings": { "minZoom": 3, "maxZoom": 8 }
}
]
- }
+ },
+ {
+ "geoviewLayerId": "xyzTilesLYR2",
+ "geoviewLayerName": "GNOSIS_Blue_Marble",
+ "metadataAccessPath": "https://maps.gnosis.earth/ogcapi/collections/blueMarble/map/tiles/WebMercatorQuad",
+ "geoviewLayerType": "xyzTiles",
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "blueMarble",
+ "layerName": "GNOSIS Blue Marble",
+ "source": {
+ "dataAccessPath": "https://maps.gnosis.earth/ogcapi/collections/blueMarble/map/tiles/WebMercatorQuad/{z}/{y}/{x}.jpg"
+ }
+ }
+ ]
+ }
]
},
"components": ["overview-map"],
diff --git a/packages/geoview-core/public/configs/navigator/24-vector-tile.json b/packages/geoview-core/public/configs/navigator/24-vector-tile.json
index 68344d53a34..2dba3eab5da 100644
--- a/packages/geoview-core/public/configs/navigator/24-vector-tile.json
+++ b/packages/geoview-core/public/configs/navigator/24-vector-tile.json
@@ -12,12 +12,26 @@
"listOfGeoviewLayerConfig": [
{
"geoviewLayerId": "vectorTilesLYR1",
- "geoviewLayerName": "new basemap",
+ "geoviewLayerName": "CBCT - French Basemap",
"geoviewLayerType": "vectorTiles",
"metadataAccessPath": "https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer/",
"listOfLayerEntryConfig": [
{
"layerId": "CBMT_CBCT_3978_V_OSM",
+ "styleUrl": "https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/88ad9e2ef6e040a19472985e6606a2f9/resources/styles/root.json",
+ "initialSettings": { "minZoom": 3, "maxZoom": 18 }
+ }
+ ]
+ },
+ {
+ "geoviewLayerId": "vectorTilesLYR2",
+ "geoviewLayerName": "CBMT - English Basemap",
+ "geoviewLayerType": "vectorTiles",
+ "metadataAccessPath": "https://tiles.arcgis.com/tiles/HsjBaDykC1mjhXz9/arcgis/rest/services/CBMT_CBCT_3978_V_OSM/VectorTileServer/",
+ "listOfLayerEntryConfig": [
+ {
+ "layerId": "CBMT_CBCT_3978_V_OSM",
+ "styleUrl": "https://nrcan-rncan.maps.arcgis.com/sharing/rest/content/items/708e92c1f00941e3af3dd3c092ae4a0a/resources/styles/root.json",
"initialSettings": { "minZoom": 3, "maxZoom": 18 }
}
]
diff --git a/packages/geoview-core/public/templates/demos-navigator.html b/packages/geoview-core/public/templates/demos-navigator.html
index 928c4fa6052..038f57440ea 100644
--- a/packages/geoview-core/public/templates/demos-navigator.html
+++ b/packages/geoview-core/public/templates/demos-navigator.html
@@ -125,6 +125,7 @@
Configurations Navigator
+
diff --git a/packages/geoview-core/public/templates/sandbox.html b/packages/geoview-core/public/templates/sandbox.html
index 97ce867e2f0..09d5fe58db2 100644
--- a/packages/geoview-core/public/templates/sandbox.html
+++ b/packages/geoview-core/public/templates/sandbox.html
@@ -198,9 +198,10 @@ Sanbox Map
try {
// get config and test if JSON is valid
const configArea = document.getElementById('configGeoview');
- const configJSON = JSON.parse(configArea.value.replaceAll(`'`, `"`));
+ const regexExp = /(?Sanbox Map
// TODO: the delete has a timeout so we need to wait before trying to recreate the map...
// TO.DOCONT: this should be cleaner
setTimeout(() => {
- cgpv.api.createMapFromConfig('sandboxMap', document.getElementById('configGeoview').value.replaceAll("'", '"'))
+ // Regex expression to cature all single quotes (') except those preceded by a backslash (\)
+ const regexExp = /(? listenToLegendLayerSetChanges('sandboxMap-state', 'sandboxMap'));
}, 1500);
});
diff --git a/packages/geoview-core/src/api/config/types/config-constants.ts b/packages/geoview-core/src/api/config/types/config-constants.ts
index ea684915444..17052c0a063 100644
--- a/packages/geoview-core/src/api/config/types/config-constants.ts
+++ b/packages/geoview-core/src/api/config/types/config-constants.ts
@@ -66,7 +66,7 @@ export const CV_CONST_LEAF_LAYER_SCHEMA_PATH: Record = {
IMAGE_STATIC: 'https://cgpv/schema#/definitions/ImageStaticLayerEntryConfig',
GEOPACKAGE: 'https://cgpv/schema#/definitions/VectorLayerEntryConfig',
XYZ_TILES: 'https://cgpv/schema#/definitions/TileLayerEntryConfig',
- VECTOR_TILES: 'Thttps://cgpv/schema#/definitions/TileLayerEntryConfig',
+ VECTOR_TILES: 'https://cgpv/schema#/definitions/TileLayerEntryConfig',
OGC_FEATURE: 'https://cgpv/schema#/definitions/VectorLayerEntryConfig',
CSV: 'https://cgpv/schema#/definitions/VectorLayerEntryConfig',
};
diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts
index fe85acd2cc4..bbf6e0e35aa 100644
--- a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts
+++ b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts
@@ -286,6 +286,8 @@ export class LegendEventProcessor extends AbstractEventProcessor {
layerName,
layerStatus: legendResultSetEntry.layerStatus,
legendQueryStatus: legendResultSetEntry.legendQueryStatus,
+ maxZoom: layerConfig.initialSettings.maxZoom,
+ minZoom: layerConfig.initialSettings.minZoom,
type: layerConfig.entryType as TypeGeoviewLayerType,
canToggle: legendResultSetEntry.data?.type !== CONST_LAYER_TYPES.ESRI_IMAGE,
opacity: layerConfig.initialSettings?.states?.opacity ? layerConfig.initialSettings.states.opacity : 1,
@@ -328,6 +330,8 @@ export class LegendEventProcessor extends AbstractEventProcessor {
layerName,
layerStatus: legendResultSetEntry.layerStatus,
legendQueryStatus: legendResultSetEntry.legendQueryStatus,
+ maxZoom: layerConfig.initialSettings.maxZoom,
+ minZoom: layerConfig.initialSettings.minZoom,
styleConfig: legendResultSetEntry.data?.styleConfig,
type: legendResultSetEntry.data?.type || (layerConfig.entryType as TypeGeoviewLayerType),
canToggle: legendResultSetEntry.data?.type !== CONST_LAYER_TYPES.ESRI_IMAGE,
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 475a9fd6857..1a47f6eca29 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
@@ -430,9 +430,23 @@ export class MapEventProcessor extends AbstractEventProcessor {
if (getAppCrosshairsActive(mapId)) this.getMapViewer(mapId).emitMapSingleClick(clickCoordinates);
}
- static setZoom(mapId: string, zoom: number): void {
+ static setLayerInVisibleRange(mapId: string, layerPath: string, inVisibleRange: boolean): void {
+ const { orderedLayerInfo } = this.getMapStateProtected(mapId);
+ const orderedLayer = orderedLayerInfo.find((layer) => layer.layerPath === layerPath);
+
+ if (orderedLayer && orderedLayer.inVisibleRange !== inVisibleRange) {
+ orderedLayer.inVisibleRange = inVisibleRange;
+ this.setOrderedLayerInfoWithNoOrderChangeState(mapId, orderedLayerInfo);
+ }
+ }
+
+ static setZoom(mapId: string, zoom: number, orderedLayerInfo?: TypeOrderedLayerInfo[]): void {
// Save in store
this.getMapStateProtected(mapId).setterActions.setZoom(zoom);
+
+ if (orderedLayerInfo) {
+ this.setOrderedLayerInfoWithNoOrderChangeState(mapId, orderedLayerInfo);
+ }
}
static setIsMouseInsideMap(mapId: string, inside: boolean): void {
@@ -588,16 +602,17 @@ export class MapEventProcessor extends AbstractEventProcessor {
static getMapLegendCollapsedFromOrderedLayerInfo(mapId: string, layerPath: string): boolean {
// Get legend status of a layer
- const info = this.getMapStateProtected(mapId).orderedLayerInfo;
- const pathInfo = info.find((item) => item.layerPath === layerPath);
- return pathInfo?.legendCollapsed !== false;
+ return this.findMapLayerFromOrderedInfo(mapId, layerPath)?.legendCollapsed !== false;
}
static getMapVisibilityFromOrderedLayerInfo(mapId: string, layerPath: string): boolean {
// Get visibility of a layer
- const info = this.getMapStateProtected(mapId).orderedLayerInfo;
- const pathInfo = info.find((item) => item.layerPath === layerPath);
- return pathInfo?.visible !== false;
+ return this.findMapLayerFromOrderedInfo(mapId, layerPath)?.visible !== false;
+ }
+
+ static getMapInVisibleRangeFromOrderedLayerInfo(mapId: string, layerPath: string): boolean {
+ // Get inVisibleRange of a layer
+ return this.findMapLayerFromOrderedInfo(mapId, layerPath)?.inVisibleRange !== false;
}
static addHighlightedFeature(mapId: string, feature: TypeFeatureInfoEntry): void {
diff --git a/packages/geoview-core/src/app.tsx b/packages/geoview-core/src/app.tsx
index 584d2984909..4dffb0a821b 100644
--- a/packages/geoview-core/src/app.tsx
+++ b/packages/geoview-core/src/app.tsx
@@ -192,8 +192,8 @@ export async function initMapDivFromFunctionCall(mapDiv: HTMLElement, mapConfig:
// Create a data-config attribute and set config value on the div
const att = document.createAttribute(url ? 'data-config-url' : 'data-config');
- // Clean apostrophes in the config
- att.value = mapConfig.replaceAll("'", "\\'");
+ // Clean apostrophes in the config if not escaped already
+ att.value = mapConfig.replaceAll(/(? ({
fontSize: theme.palette.geoViewFontSize.lg,
fontWeight: '600',
},
-
'& .MuiListItem-root': {
height: '100%',
'& .MuiListItemButton-root': {
@@ -93,4 +92,17 @@ export const getSxClasses = (theme: Theme): SxStyles => ({
layersInstructionsBody: {
fontSize: theme.palette.geoViewFontSize.default,
},
+ outOfRange: {
+ '.layer-panel &.MuiListItemButton-root': {
+ backgroundColor: `${theme.palette.grey[200]} !important`,
+ '& .MuiListItemText-primary': {
+ color: `${theme.palette.grey[600]} !important`,
+ fontStyle: 'italic',
+ },
+ '& .MuiListItemText-secondary': {
+ color: theme.palette.grey[500],
+ fontStyle: 'italic',
+ },
+ },
+ },
});
diff --git a/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx b/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx
index 03c30786079..eb0f0668c13 100644
--- a/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx
+++ b/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import _ from 'lodash';
import { animated } from '@react-spring/web';
-import { Theme } from '@mui/material/styles';
+import { useTheme } from '@mui/material/styles';
+import { getSxClasses } from '../../common/layer-list-style';
import {
Collapse,
IconButton,
@@ -30,6 +31,7 @@ import {
useMapStoreActions,
useSelectorLayerLegendCollapsed,
useSelectorLayerVisibility,
+ useSelectorLayerInVisibleRange,
} from '@/core/stores/store-interface-and-intial-values/map-state';
import { DeleteUndoButton } from './delete-undo-button';
import { LayersList } from './layers-list';
@@ -56,6 +58,9 @@ export function SingleLayer({ depth, layer, showLayerDetailsPanel, isFirst, isLa
const { t } = useTranslation();
+ const theme = useTheme();
+ const sxClasses = useMemo(() => getSxClasses(theme), [theme]);
+
// Get store states
const { setSelectedLayerPath, setSelectedLayerSortingArrowId } = useLayerStoreActions();
const { setOrToggleLayerVisibility, setLegendCollapsed, reorderLayer } = useMapStoreActions();
@@ -70,6 +75,7 @@ export function SingleLayer({ depth, layer, showLayerDetailsPanel, isFirst, isLa
useDataTableStoreActions();
const isVisible = useSelectorLayerVisibility(layer.layerPath);
+ const inVisibleRange = useSelectorLayerInVisibleRange(layer.layerPath);
const legendExpanded = !useSelectorLayerLegendCollapsed(layer.layerPath);
// TODO: I think we should favor using this pattern here, with the store, instead of working with the whole 'layer' object from the props
@@ -250,7 +256,7 @@ export function SingleLayer({ depth, layer, showLayerDetailsPanel, isFirst, isLa
sx={{
marginLeft: '0.4rem',
height: '1.5rem',
- backgroundColor: (theme: Theme) => theme.palette.geoViewColor.bgColor.dark[300],
+ backgroundColor: theme.palette.geoViewColor.bgColor.dark[300],
}}
variant="middle"
flexItem
@@ -446,7 +452,8 @@ export function SingleLayer({ depth, layer, showLayerDetailsPanel, isFirst, isLa
void;
}
@@ -32,8 +39,8 @@ const styles = {
// Extracted Header Component
const LegendLayerHeader = memo(
- ({ layer, isCollapsed, isVisible, tooltip, onExpandClick }: LegendLayerHeaderProps): JSX.Element => (
-
+ ({ layer, isCollapsed, isVisible, inVisibleRange, tooltip, onExpandClick }: LegendLayerHeaderProps): JSX.Element => (
+
diff --git a/packages/geoview-core/src/core/components/legend/legend-styles.ts b/packages/geoview-core/src/core/components/legend/legend-styles.ts
index d5e868ac3de..243ae86da59 100644
--- a/packages/geoview-core/src/core/components/legend/legend-styles.ts
+++ b/packages/geoview-core/src/core/components/legend/legend-styles.ts
@@ -94,6 +94,13 @@ export const getSxClasses = (theme: Theme, isFullScreen?: boolean, footerPanelRe
},
},
},
+ '& .outOfRange': {
+ backgroundColor: `${theme.palette.grey[200]}`,
+ '& .layerTitle': {
+ color: `${theme.palette.grey[600]}`,
+ fontStyle: 'italic',
+ },
+ },
},
collapsibleContainer: {
width: '100%',
diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
index 31de4921b42..3ca88d233cd 100644
--- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
+++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/map-state.ts
@@ -82,6 +82,7 @@ export interface IMapState {
resetBasemap: () => Promise;
setLegendCollapsed: (layerPath: string, newValue?: boolean) => void;
setOrToggleLayerVisibility: (layerPath: string, newValue?: boolean) => boolean;
+ setLayerInVisibleRange: (layerPath: string, newValue: boolean) => void;
setMapKeyboardPanInteractions: (panDelta: number) => void;
setZoom: (zoom: number, duration?: number) => void;
setInteraction: (interaction: TypeInteraction) => void;
@@ -347,6 +348,16 @@ export function initializeMapState(set: TypeSetStore, get: TypeGetStore): IMapSt
return MapEventProcessor.setOrToggleMapLayerVisibility(get().mapId, layerPath, newValue);
},
+ /**
+ * Sets or toggles the visibility of a layer.
+ * @param {string} layerPath - The path of the layer.
+ * @param {boolean} [newValue] - The new value of visibility.
+ */
+ setLayerInVisibleRange: (layerPath: string, newValue: boolean): void => {
+ // Redirect to processor
+ MapEventProcessor.setLayerInVisibleRange(get().mapId, layerPath, newValue);
+ },
+
/**
* Sets the map keyboard pan interactions.
* @param {number} panDelta - The pan delta value.
@@ -889,6 +900,7 @@ export interface TypeOrderedLayerInfo {
layerPath: string;
queryable?: boolean;
visible: boolean;
+ inVisibleRange: boolean;
legendCollapsed: boolean;
}
@@ -939,6 +951,15 @@ export const useSelectorLayerVisibility = (layerPath: string): boolean => {
return MapEventProcessor.findMapLayerFromOrderedInfo(geoviewStore.getState().mapId, layerPath, orderedLayerInfo)?.visible || false;
};
+export const useSelectorLayerInVisibleRange = (layerPath: string): boolean => {
+ // Get the store
+ const geoviewStore = useGeoViewStore();
+ // Hook
+ const orderedLayerInfo = useStore(geoviewStore, (state) => state.mapState.orderedLayerInfo);
+ // Redirect
+ return MapEventProcessor.findMapLayerFromOrderedInfo(geoviewStore.getState().mapId, layerPath, orderedLayerInfo)?.inVisibleRange || false;
+};
+
export const useSelectorLayerLegendCollapsed = (layerPath: string): boolean => {
// Get the store
const geoviewStore = useGeoViewStore();
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 20f69c5f0e0..03897308ed4 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
@@ -45,6 +45,12 @@ export abstract class ConfigBaseClass {
/** It is used to link the layer entry config to the GeoView layer config. */
geoviewLayerConfig = {} as TypeGeoviewLayerConfig;
+ /** The min scale that can be reach by the layer. */
+ minScale?: number;
+
+ /** The max scale that can be reach by the layer. */
+ maxScale?: number;
+
/**
* Initial settings to apply to the GeoView layer entry at creation time. Initial settings are inherited from the parent in the
* configuration tree.
diff --git a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/vector-tiles-layer-entry-config.ts b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/vector-tiles-layer-entry-config.ts
index 7361f898536..fceace7bc8c 100644
--- a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/vector-tiles-layer-entry-config.ts
+++ b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/vector-tiles-layer-entry-config.ts
@@ -7,6 +7,8 @@ export class VectorTilesLayerEntryConfig extends TileLayerEntryConfig {
tileGrid!: TypeTileGrid;
+ styleUrl?: string;
+
/**
* The class constructor.
* @param {VectorTilesLayerEntryConfig} layerConfig - The layer configuration we want to instanciate.
@@ -23,9 +25,10 @@ export class VectorTilesLayerEntryConfig extends TileLayerEntryConfig {
if (!this.source) this.source = {};
if (!this.source.dataAccessPath) this.source.dataAccessPath = this.geoviewLayerConfig.metadataAccessPath;
- if (!this.source.dataAccessPath!.toLowerCase().endsWith('.pbf'))
+ if (!this.source.dataAccessPath!.toLowerCase().endsWith('.pbf')) {
this.source.dataAccessPath = this.source.dataAccessPath!.endsWith('/')
- ? `${this.source.dataAccessPath}${this.layerId}`
- : `${this.source.dataAccessPath}/${this.layerId}`;
+ ? `${this.source.dataAccessPath}tile/{z}/{y}/{x}.pbf`
+ : `${this.source.dataAccessPath}/tile/{z}/{y}/{x}.pbf`;
+ }
}
}
diff --git a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/xyz-layer-entry-config.ts b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/xyz-layer-entry-config.ts
index 6ec86db84dd..c9b4e8e0b07 100644
--- a/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/xyz-layer-entry-config.ts
+++ b/packages/geoview-core/src/core/utils/config/validation-classes/raster-validation-classes/xyz-layer-entry-config.ts
@@ -20,7 +20,7 @@ export class XYZTilesLayerEntryConfig extends TileLayerEntryConfig {
if (!this.source) this.source = {};
if (!this.source.dataAccessPath) this.source.dataAccessPath = this.geoviewLayerConfig.metadataAccessPath;
- if (!this.source.dataAccessPath!.endsWith('{z}/{y}/{x}'))
+ if (!this.source.dataAccessPath!.includes('{z}/{y}/{x}'))
this.source.dataAccessPath = this.source.dataAccessPath!.endsWith('/')
? `${this.source.dataAccessPath}tile/{z}/{y}/{x}`
: `${this.source.dataAccessPath}/tile/{z}/{y}/{x}`;
diff --git a/packages/geoview-core/src/core/utils/utilities.ts b/packages/geoview-core/src/core/utils/utilities.ts
index 0c91d78663c..59eff0c35e6 100644
--- a/packages/geoview-core/src/core/utils/utilities.ts
+++ b/packages/geoview-core/src/core/utils/utilities.ts
@@ -2,6 +2,7 @@ import { Root, createRoot } from 'react-dom/client';
import sanitizeHtml from 'sanitize-html';
import { TypeDisplayLanguage } from '@config/types/map-schema-types';
+import View from 'ol/View';
import { Cast, TypeJsonArray, TypeJsonObject, TypeJsonValue } from '@/core/types/global-types';
import { logger } from '@/core/utils/logger';
import i18n from '@/core/translation/i18n';
@@ -551,3 +552,57 @@ export function isElementInViewport(el: Element): boolean {
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
+
+/**
+ * Convert a map scale to zoom level
+ * @param view The view for converting the scale
+ * @param targetScale The desired scale (e.g. 50000 for 1:50,000)
+ * @returns number representing the closest zoom level for the given scale
+ */
+export const getZoomFromScale = (view: View, targetScale: number): number | undefined => {
+ const projection = view.getProjection();
+ const mpu = projection.getMetersPerUnit();
+ const dpi = 25.4 / 0.28; // OpenLayers default DPI
+
+ // Calculate resolution from scale
+ if (!mpu) return undefined;
+ // Resolution = Scale / ( metersPerUnit * inchesPerMeter * DPI )
+ const targetResolution = targetScale / (mpu * 39.37 * dpi);
+
+ // Get the constrained resolution that matches our tile matrix
+ const constrainedResolution = view.getConstrainedResolution(targetResolution);
+
+ // Convert resolution to zoom
+ if (!constrainedResolution) return undefined;
+ return view.getZoomForResolution(constrainedResolution) || undefined;
+};
+
+/**
+ * Convert a map scale to zoom level
+ * @param view The view for converting the zoom
+ * @param zoom The desired zoom (e.g. 50000 for 1:50,000)
+ * @returns number representing the closest scale for the given zoom number
+ */
+export const getScaleFromZoom = (view: View, zoom: number): number | undefined => {
+ const projection = view.getProjection();
+ const mpu = projection.getMetersPerUnit();
+ if (!mpu) return undefined;
+
+ const dpi = 25.4 / 0.28; // OpenLayers default DPI
+
+ // Get resolution for zoom level
+ const resolution = view.getResolutionForZoom(zoom);
+
+ // Calculate scale from resolution
+ // Scale = Resolution * metersPerUnit * inchesPerMeter * DPI
+ return resolution * mpu * 39.37 * dpi;
+};
+
+/**
+ * Get map scale for Web Mercator or Lambert Conformal Conic projections
+ * @param view The view to get the current scale from
+ * @returns number representing scale (e.g. 50000 for 1:50,000)
+ */
+export const getMapScale = (view: View): number | undefined => {
+ return getScaleFromZoom(view, view.getZoom() || 0);
+};
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 e7df3c31e5c..e6c5dae2afd 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
@@ -7,7 +7,7 @@ import cloneDeep from 'lodash/cloneDeep';
import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor';
import { Cast, TypeJsonArray, TypeJsonObject } from '@/core/types/global-types';
-import { getXMLHttpRequest } from '@/core/utils/utilities';
+import { getXMLHttpRequest, getZoomFromScale } from '@/core/utils/utilities';
import { validateExtent, validateExtentWhenDefined } from '@/geo/utils/utilities';
import { Projection } from '@/geo/utils/projection';
import { TimeDimensionESRI, DateMgt } from '@/core/utils/date-mgt';
@@ -341,8 +341,8 @@ export function commonProcessInitialSettings(
if (layerConfig.initialSettings?.states?.visible === undefined)
layerConfig.initialSettings!.states = { visible: !!layerMetadata.defaultVisibility };
// GV TODO: The solution implemented in the following two lines is not right. scale and zoom are not the same things.
- // GV if (layerConfig.initialSettings?.minZoom === undefined && minScale !== 0) layerConfig.initialSettings.minZoom = minScale;
- // GV if (layerConfig.initialSettings?.maxZoom === undefined && maxScale !== 0) layerConfig.initialSettings.maxZoom = maxScale;
+ if (layerConfig.minScale === undefined && layerMetadata.minScale !== 0) layerConfig.minScale = layerMetadata.minScale as number;
+ if (layerConfig.maxScale === undefined && layerMetadata.maxScale !== 0) layerConfig.maxScale = layerMetadata.maxScale as number;
layerConfig.initialSettings.extent = validateExtentWhenDefined(layerConfig.initialSettings.extent);
@@ -365,6 +365,24 @@ export function commonProcessInitialSettings(
}
}
+ // Set zoom limits for max / min zooms
+ // GV Note: minScale is actually the maxZoom and maxScale is actually the minZoom
+ // GV As the scale gets smaller, the zoom gets larger
+ const mapView = layer.getMapViewer().getView();
+ if (layerConfig.minScale) {
+ const maxScaleZoomLevel = getZoomFromScale(mapView, layerConfig.minScale);
+ if (maxScaleZoomLevel && (!layerConfig.initialSettings.maxZoom || maxScaleZoomLevel > layerConfig.initialSettings.maxZoom)) {
+ layerConfig.initialSettings.maxZoom = maxScaleZoomLevel;
+ }
+ }
+
+ if (layerConfig.maxScale) {
+ const minScaleZoomLevel = getZoomFromScale(mapView, layerConfig.maxScale);
+ if (minScaleZoomLevel && (!layerConfig.initialSettings.minZoom || minScaleZoomLevel < layerConfig.initialSettings.minZoom)) {
+ layerConfig.initialSettings.minZoom = minScaleZoomLevel;
+ }
+ }
+
layerConfig.initialSettings!.bounds = validateExtent(layerConfig.initialSettings!.bounds || [-180, -90, 180, 90]);
}
diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/vector-tiles.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/vector-tiles.ts
index 34834f17edf..e8e8253ca87 100644
--- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/vector-tiles.ts
+++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/vector-tiles.ts
@@ -17,7 +17,7 @@ import {
} from '@/geo/map/map-schema-types';
import { TypeJsonObject } from '@/core/types/global-types';
import { validateExtentWhenDefined } from '@/geo/utils/utilities';
-import { api } from '@/app';
+import { api, getZoomFromScale } from '@/app';
import { VectorTilesLayerEntryConfig } from '@/core/utils/config/validation-classes/raster-validation-classes/vector-tiles-layer-entry-config';
import { logger } from '@/core/utils/logger';
import { AbstractBaseLayerEntryConfig } from '@/core/utils/config/validation-classes/abstract-base-layer-entry-config';
@@ -200,13 +200,18 @@ export class VectorTiles extends AbstractGeoViewRaster {
// TODO: Refactor - Layers refactoring. What is this doing? See how we can do this in the new layers. Can it be done before?
const resolutions = sourceOptions.tileGrid.getResolutions();
- if (this.metadata?.defaultStyles)
- applyStyle(olLayer, `${this.metadataAccessPath}${this.metadata.defaultStyles}/root.json`, {
+ let appliedStyle = layerConfig.styleUrl || (this.metadata?.defaultStyles as string);
+
+ if (appliedStyle) {
+ if (!appliedStyle.endsWith('/root.json')) appliedStyle = `${appliedStyle}/root.json`;
+
+ applyStyle(olLayer, appliedStyle, {
resolutions: resolutions?.length ? resolutions : [],
}).catch((error) => {
// Log
logger.logPromiseFailed('applyStyle in processOneLayerEntry in VectorTiles', error);
});
+ }
return Promise.resolve(olLayer);
}
@@ -222,22 +227,57 @@ export class VectorTiles extends AbstractGeoViewRaster {
// GV Layers Refactoring - Obsolete (in config?)
protected override processLayerMetadata(layerConfig: AbstractBaseLayerEntryConfig): Promise {
// Instance check
- if (!(layerConfig instanceof VectorTilesLayerEntryConfig)) throw new Error('Invalid layer configuration type provided');
+ const updatedLayerConfig = layerConfig;
+ if (!(updatedLayerConfig instanceof VectorTilesLayerEntryConfig)) throw new Error('Invalid layer configuration type provided');
if (this.metadata) {
- const { tileInfo, fullExtent } = this.metadata;
+ const { tileInfo, fullExtent, minScale, maxScale, minZoom, maxZoom } = this.metadata;
const newTileGrid: TypeTileGrid = {
extent: [fullExtent.xmin as number, fullExtent.ymin as number, fullExtent.xmax as number, fullExtent.ymax as number],
origin: [tileInfo.origin.x as number, tileInfo.origin.y as number],
resolutions: (tileInfo.lods as Array).map(({ resolution }) => resolution as number),
tileSize: [tileInfo.rows as number, tileInfo.cols as number],
};
- // eslint-disable-next-line no-param-reassign
- layerConfig.source!.tileGrid = newTileGrid;
+ updatedLayerConfig.source!.tileGrid = newTileGrid;
- // eslint-disable-next-line no-param-reassign
- layerConfig.initialSettings.extent = validateExtentWhenDefined(layerConfig.initialSettings.extent);
+ updatedLayerConfig.initialSettings.extent = validateExtentWhenDefined(updatedLayerConfig.initialSettings.extent);
+
+ // Set zoom levels. Vector tiles may be unique as they can have both scale and zoom level properties
+ // First set the min/max scales based on the service / config
+ // * Infinity and -Infinity are used as extreme zoom level values in case the value is undefined
+ if (minScale !== undefined) {
+ updatedLayerConfig.minScale = Math.min(updatedLayerConfig.minScale ?? Infinity, minScale as number);
+ }
+
+ if (maxScale !== undefined) {
+ updatedLayerConfig.maxScale = Math.max(updatedLayerConfig.maxScale ?? -Infinity, maxScale as number);
+ }
+
+ // Second, set the min/max zoom levels based on the service / config
+ if (minZoom !== undefined) {
+ updatedLayerConfig.initialSettings.minZoom = Math.min(updatedLayerConfig.initialSettings.minZoom ?? Infinity, minZoom as number);
+ }
+
+ if (maxZoom !== undefined) {
+ updatedLayerConfig.initialSettings.maxZoom = Math.max(updatedLayerConfig.initialSettings.maxZoom ?? -Infinity, maxZoom as number);
+ }
+
+ // Third, use the now set scale and zoom levels to determine the actual max / min zoom based on both
+ const mapView = this.getMapViewer().getView();
+ if (updatedLayerConfig.minScale) {
+ const maxScaleZoomLevel = getZoomFromScale(mapView, updatedLayerConfig.minScale);
+ if (maxScaleZoomLevel) {
+ updatedLayerConfig.initialSettings.maxZoom = Math.max(updatedLayerConfig.initialSettings.maxZoom ?? -Infinity, maxScaleZoomLevel);
+ }
+ }
+
+ if (updatedLayerConfig.maxScale) {
+ const minScaleZoomLevel = getZoomFromScale(mapView, updatedLayerConfig.maxScale);
+ if (minScaleZoomLevel) {
+ updatedLayerConfig.initialSettings.minZoom = Math.min(updatedLayerConfig.initialSettings.minZoom ?? Infinity, minScaleZoomLevel);
+ }
+ }
}
- return Promise.resolve(layerConfig);
+ return Promise.resolve(updatedLayerConfig);
}
}
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 4f7cdfd91b6..b6bf0b5fcf6 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
@@ -13,7 +13,7 @@ import { AbstractGeoViewRaster } from '@/geo/layer/geoview-layers/raster/abstrac
import { TypeLayerEntryConfig, TypeGeoviewLayerConfig, CONST_LAYER_ENTRY_TYPES, layerEntryIsGroupLayer } from '@/geo/map/map-schema-types';
import { DateMgt } from '@/core/utils/date-mgt';
import { validateExtent, validateExtentWhenDefined } from '@/geo/utils/utilities';
-import { api, WMS_PROXY_URL } from '@/app';
+import { api, WMS_PROXY_URL, getZoomFromScale } from '@/app';
import { MapEventProcessor } from '@/api/event-processors/event-processor-children/map-event-processor';
import { logger } from '@/core/utils/logger';
import { OgcWmsLayerEntryConfig } from '@/core/utils/config/validation-classes/raster-validation-classes/ogc-wms-layer-entry-config';
@@ -379,8 +379,6 @@ export class WMS extends AbstractGeoViewRaster {
if (layer.BoundingBox === undefined) layer.BoundingBox = parentLayer.BoundingBox;
if (layer.Dimension === undefined) layer.Dimension = parentLayer.Dimension;
if (layer.Attribution === undefined) layer.Attribution = parentLayer.Attribution;
- if (layer.MaxScaleDenominator === undefined) layer.MaxScaleDenominator = parentLayer.MaxScaleDenominator;
- if (layer.MaxScaleDenominator === undefined) layer.MaxScaleDenominator = parentLayer.MaxScaleDenominator;
// Table 7 — Inheritance of Layer properties specified in the standard with 'add' behaviour.
// AuthorityURL inheritance is not implemented in the following code.
if (parentLayer.Style) {
@@ -636,17 +634,45 @@ export class WMS extends AbstractGeoViewRaster {
}
if (!layerConfig.source.featureInfo) layerConfig.source.featureInfo = { queryable: !!layerCapabilities.queryable };
MapEventProcessor.setMapLayerQueryable(this.mapId, layerConfig.layerPath, layerConfig.source.featureInfo.queryable);
- // TODO: The solution implemented in the following lines is not right. scale and zoom are not the same things.
- // if (layerConfig.initialSettings?.minZoom === undefined && layerCapabilities.MinScaleDenominator !== undefined)
- // layerConfig.initialSettings.minZoom = layerCapabilities.MinScaleDenominator as number;
- // if (layerConfig.initialSettings?.maxZoom === undefined && layerCapabilities.MaxScaleDenominator !== undefined)
- // layerConfig.initialSettings.maxZoom = layerCapabilities.MaxScaleDenominator as number;
+
+ // TODO Add Scale and Zoom level changes to config
+ // TODO Since web map runs mostly in zoom levels, may not need Scale limits
+ // Set Min/Max Scale Limits
+ if (
+ layerCapabilities.MinScaleDenominator !== undefined &&
+ (layerConfig.minScale === undefined || layerConfig.minScale > (layerCapabilities.MinScaleDenominator as unknown as number))
+ )
+ layerConfig.minScale = layerCapabilities.MinScaleDenominator as number;
+ if (
+ layerCapabilities.MaxScaleDenominator !== undefined &&
+ (layerConfig.maxScale === undefined || layerConfig.maxScale < (layerCapabilities.MaxScaleDenominator as unknown as number))
+ )
+ layerConfig.maxScale = layerCapabilities.MaxScaleDenominator as number;
layerConfig.initialSettings.extent = validateExtentWhenDefined(layerConfig.initialSettings.extent);
if (!layerConfig.initialSettings?.bounds && layerCapabilities.EX_GeographicBoundingBox)
layerConfig.initialSettings!.bounds = validateExtent(layerCapabilities.EX_GeographicBoundingBox as Extent);
+ // Set zoom limits for max / min zooms
+ // GV Note: minScale is actually the maxZoom and maxScale is actually the minZoom
+ // GV As the scale gets smaller, the zoom gets larger
+ const mapView = this.getMapViewer().getView();
+ if (layerConfig.minScale) {
+ const maxScaleZoomLevel = getZoomFromScale(mapView, layerConfig.minScale);
+ if (maxScaleZoomLevel && (!layerConfig.initialSettings.maxZoom || maxScaleZoomLevel > layerConfig.initialSettings.maxZoom)) {
+ layerConfig.initialSettings.maxZoom = maxScaleZoomLevel;
+ }
+ }
+
+ if (layerConfig.maxScale) {
+ const minScaleZoomLevel = getZoomFromScale(mapView, layerConfig.maxScale);
+ if (minScaleZoomLevel && (!layerConfig.initialSettings.minZoom || minScaleZoomLevel < layerConfig.initialSettings.minZoom)) {
+ layerConfig.initialSettings.minZoom = minScaleZoomLevel;
+ }
+ }
+
+ // Set time dimension
if (layerCapabilities.Dimension) {
const temporalDimension: TypeJsonObject | undefined = (layerCapabilities.Dimension as TypeJsonArray).find(
(dimension) => dimension.name === 'time'
diff --git a/packages/geoview-core/src/geo/layer/geoview-layers/raster/xyz-tiles.ts b/packages/geoview-core/src/geo/layer/geoview-layers/raster/xyz-tiles.ts
index b5439e41f5a..662ac430fea 100644
--- a/packages/geoview-core/src/geo/layer/geoview-layers/raster/xyz-tiles.ts
+++ b/packages/geoview-core/src/geo/layer/geoview-layers/raster/xyz-tiles.ts
@@ -13,10 +13,11 @@ import {
TypeGeoviewLayerConfig,
layerEntryIsGroupLayer,
} from '@/geo/map/map-schema-types';
-import { Cast, toJsonObject } from '@/core/types/global-types';
+import { Cast, toJsonObject, TypeJsonArray, TypeJsonObject } from '@/core/types/global-types';
import { validateExtentWhenDefined } from '@/geo/utils/utilities';
import { XYZTilesLayerEntryConfig } from '@/core/utils/config/validation-classes/raster-validation-classes/xyz-layer-entry-config';
import { AbstractBaseLayerEntryConfig } from '@/core/utils/config/validation-classes/abstract-base-layer-entry-config';
+import { getZoomFromScale } from '@/app';
// ? Do we keep this TODO ? Dynamic parameters can be placed on the dataAccessPath and initial settings can be used on xyz-tiles.
// TODO: Implement method to validate XYZ tile service
@@ -142,6 +143,22 @@ export class XYZTiles extends AbstractGeoViewRaster {
return;
}
+ // ESRI MapServer Implementation
+ if (Array.isArray(this.metadata?.layers)) {
+ const metadataLayerList = this.metadata.layers;
+ const foundEntry = metadataLayerList.find((layerMetadata) => layerMetadata.id.toString() === layerConfig.layerId);
+ if (!foundEntry) {
+ this.layerLoadError.push({
+ layer: layerPath,
+ loggerMessage: `XYZ layer not found (mapId: ${this.mapId}, layerPath: ${layerPath})`,
+ });
+ // eslint-disable-next-line no-param-reassign
+ layerConfig.layerStatus = 'error';
+ return;
+ }
+ return;
+ }
+
throw new Error(
`Invalid GeoJSON metadata (listOfLayerEntryConfig) prevent loading of layer (mapId: ${this.mapId}, layerPath: ${layerPath})`
);
@@ -214,20 +231,57 @@ export class XYZTiles extends AbstractGeoViewRaster {
protected override processLayerMetadata(layerConfig: AbstractBaseLayerEntryConfig): Promise {
// Instance check
if (!(layerConfig instanceof XYZTilesLayerEntryConfig)) throw new Error('Invalid layer configuration type provided');
+ const newLayerConfig = layerConfig;
+ // TODO Need to see why the metadata isn't handled properly for ESRI XYZ tiles.
+ // GV Possibly caused by a difference between OGC and ESRI XYZ Tiles, but only have ESRI XYZ Tiles as example currently
+ // GV Also, might be worth checking out OGCMapTile for this? https://openlayers.org/en/latest/examples/ogc-map-tiles-geographic.html
+ // GV Seems like it can deal with less specificity in the url and can handle the x y z internally?
if (this.metadata) {
- const metadataLayerConfigFound = Cast(this.metadata?.listOfLayerEntryConfig).find(
- (metadataLayerConfig) => metadataLayerConfig.layerId === layerConfig.layerId
- );
+ let metadataLayerConfigFound: XYZTilesLayerEntryConfig | TypeJsonObject | undefined;
+ if (this.metadata?.listOfLayerEntryConfig) {
+ metadataLayerConfigFound = Cast(this.metadata?.listOfLayerEntryConfig).find(
+ (metadataLayerConfig) => metadataLayerConfig.layerId === newLayerConfig.layerId
+ );
+ }
+
+ // For ESRI MapServer XYZ Tiles
+ if (this.metadata?.layers) {
+ metadataLayerConfigFound = (this.metadata?.layers as TypeJsonArray).find(
+ (metadataLayerConfig) => metadataLayerConfig.id.toString() === newLayerConfig.layerId
+ );
+ }
+
// metadataLayerConfigFound can not be undefined because we have already validated the config exist
- this.setLayerMetadata(layerConfig.layerPath, toJsonObject(metadataLayerConfigFound));
- // eslint-disable-next-line no-param-reassign
- layerConfig.source = defaultsDeep(layerConfig.source, metadataLayerConfigFound!.source);
- // eslint-disable-next-line no-param-reassign
- layerConfig.initialSettings = defaultsDeep(layerConfig.initialSettings, metadataLayerConfigFound!.initialSettings);
- // eslint-disable-next-line no-param-reassign
- layerConfig.initialSettings.extent = validateExtentWhenDefined(layerConfig.initialSettings.extent);
+ this.setLayerMetadata(newLayerConfig.layerPath, toJsonObject(metadataLayerConfigFound));
+ newLayerConfig.source = defaultsDeep(newLayerConfig.source, metadataLayerConfigFound!.source);
+ newLayerConfig.initialSettings = defaultsDeep(newLayerConfig.initialSettings, metadataLayerConfigFound!.initialSettings);
+ newLayerConfig.initialSettings.extent = validateExtentWhenDefined(newLayerConfig.initialSettings.extent);
+
+ // Set zoom limits for max / min zooms
+ newLayerConfig.maxScale =
+ (metadataLayerConfigFound?.maxScale as number) || ((metadataLayerConfigFound as TypeJsonObject)?.maxScaleDenominator as number);
+
+ newLayerConfig.minScale =
+ (metadataLayerConfigFound?.minScale as number) || ((metadataLayerConfigFound as TypeJsonObject)?.minScaleDenominator as number);
+
+ // GV Note: minScale is actually the maxZoom and maxScale is actually the minZoom
+ // GV As the scale gets smaller, the zoom gets larger
+ const mapView = this.getMapViewer().getView();
+ if (newLayerConfig?.minScale) {
+ const maxScaleZoomLevel = getZoomFromScale(mapView, newLayerConfig.minScale as number);
+ if (maxScaleZoomLevel && (!newLayerConfig.initialSettings.maxZoom || maxScaleZoomLevel > newLayerConfig.initialSettings.maxZoom)) {
+ newLayerConfig.initialSettings.maxZoom = maxScaleZoomLevel;
+ }
+ }
+
+ if (newLayerConfig?.maxScale) {
+ const minScaleZoomLevel = getZoomFromScale(mapView, newLayerConfig.maxScale as number);
+ if (minScaleZoomLevel && (!newLayerConfig.initialSettings.minZoom || minScaleZoomLevel < newLayerConfig.initialSettings.minZoom)) {
+ newLayerConfig.initialSettings.minZoom = minScaleZoomLevel;
+ }
+ }
}
- return Promise.resolve(layerConfig);
+ return Promise.resolve(newLayerConfig);
}
}
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 3602ed48c7e..30823fc0e8b 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
@@ -216,15 +216,34 @@ export abstract class AbstractGVLayer extends AbstractBaseLayer {
return this.#externalFragmentsOrder;
}
+ /**
+ * Gets the in visible range value
+ * @returns {boolean} true if the layer is in visible range
+ */
+ getInVisibleRange(): boolean {
+ const mapZoom = this.getMapViewer().getView().getZoom();
+ return mapZoom! > this.getMinZoom() && mapZoom! <= this.getMaxZoom();
+ }
+
/**
* Overridable method called when the layer has been loaded correctly
*/
protected onLoaded(): void {
+ const layerConfig = this.getLayerConfig();
// Set the layer config status to loaded to keep mirroring the AbstractGeoViewLayer for now
- this.getLayerConfig().layerStatus = 'loaded';
+ layerConfig.layerStatus = 'loaded';
// Now that the layer is loaded, set its visibility correctly (had to be done in the loaded event, not before, per prior note in pre-refactor)
- this.setVisible(this.getLayerConfig().initialSettings?.states?.visible !== false);
+ this.setVisible(layerConfig.initialSettings?.states?.visible !== false);
+
+ // Set the zoom levels here to prevent the layer being stuck endlessly loading
+ if (layerConfig.initialSettings.maxZoom) {
+ this.setMaxZoom(layerConfig.initialSettings.maxZoom);
+ }
+
+ if (layerConfig.initialSettings.minZoom) {
+ this.setMinZoom(layerConfig.initialSettings.minZoom);
+ }
// Emit event
this.#emitIndividualLayerLoaded({ layerPath: this.getLayerPath() });
@@ -666,10 +685,6 @@ export abstract class AbstractGVLayer extends AbstractBaseLayer {
// eslint-disable-next-line no-param-reassign
if (layerConfig.initialSettings?.extent !== undefined) layerOptions.extent = layerConfig.initialSettings.extent;
// eslint-disable-next-line no-param-reassign
- if (layerConfig.initialSettings?.maxZoom !== undefined) layerOptions.maxZoom = layerConfig.initialSettings.maxZoom;
- // eslint-disable-next-line no-param-reassign
- if (layerConfig.initialSettings?.minZoom !== undefined) layerOptions.minZoom = layerConfig.initialSettings.minZoom;
- // eslint-disable-next-line no-param-reassign
if (layerConfig.initialSettings?.states?.opacity !== undefined) layerOptions.opacity = layerConfig.initialSettings.states.opacity;
}
diff --git a/packages/geoview-core/src/geo/layer/gv-layers/gv-group-layer.ts b/packages/geoview-core/src/geo/layer/gv-layers/gv-group-layer.ts
index cd09cc8e80f..227091b964c 100644
--- a/packages/geoview-core/src/geo/layer/gv-layers/gv-group-layer.ts
+++ b/packages/geoview-core/src/geo/layer/gv-layers/gv-group-layer.ts
@@ -10,6 +10,12 @@ import { GroupLayerEntryConfig } from '@/core/utils/config/validation-classes/gr
* @class GVGroupLayer
*/
export class GVGroupLayer extends AbstractBaseLayer {
+ /** Max zoom constant */
+ static readonly MAX_ZOOM = 50;
+
+ /** Min zoom constant */
+ static readonly MIN_ZOOM = 0;
+
/**
* Constructs a Group layer to manage an OpenLayer Group Layer.
* @param {string} mapId - The map id
@@ -19,6 +25,10 @@ export class GVGroupLayer extends AbstractBaseLayer {
public constructor(mapId: string, olLayerGroup: LayerGroup, layerConfig: GroupLayerEntryConfig) {
super(mapId, layerConfig);
this.olLayer = olLayerGroup;
+
+ // Set extreme zoom settings to group layer so sub layers can load
+ this.olLayer.setMaxZoom(GVGroupLayer.MAX_ZOOM);
+ this.olLayer.setMinZoom(GVGroupLayer.MIN_ZOOM);
}
/**
diff --git a/packages/geoview-core/src/geo/layer/layer-sets/abstract-layer-set.ts b/packages/geoview-core/src/geo/layer/layer-sets/abstract-layer-set.ts
index 38ba62cb1e1..d6924050c3e 100644
--- a/packages/geoview-core/src/geo/layer/layer-sets/abstract-layer-set.ts
+++ b/packages/geoview-core/src/geo/layer/layer-sets/abstract-layer-set.ts
@@ -435,6 +435,16 @@ export abstract class AbstractLayerSet {
return !((layer.getLayerConfig() as AbstractBaseLayerEntryConfig)?.initialSettings?.states?.queryable === false);
}
+ /**
+ * Checks if the layer is in visible range.
+ * @param {AbstractGVLayer} layer - The layer
+ * @returns {boolean} True if the state is queryable or undefined
+ */
+ protected static isInVisibleRange(layer: AbstractGVLayer): boolean {
+ // Return false when false or undefined
+ return layer.getInVisibleRange() ?? false;
+ }
+
/**
* Align records with informatiom provided by OutFields from layer config.
* This will update fields in and delete unwanted fields from the arrayOfRecords
diff --git a/packages/geoview-core/src/geo/layer/layer-sets/feature-info-layer-set.ts b/packages/geoview-core/src/geo/layer/layer-sets/feature-info-layer-set.ts
index 9f4671195bc..4a906d61a2f 100644
--- a/packages/geoview-core/src/geo/layer/layer-sets/feature-info-layer-set.ts
+++ b/packages/geoview-core/src/geo/layer/layer-sets/feature-info-layer-set.ts
@@ -137,6 +137,9 @@ export class FeatureInfoLayerSet extends AbstractLayerSet {
// If state is not queryable
if (!AbstractLayerSet.isStateQueryable(layer)) return;
+ // If state is not in visible range
+ if (!AbstractLayerSet.isInVisibleRange(layer)) return;
+
// Flag processing
this.resultSet[layerPath].features = undefined;
this.resultSet[layerPath].queryStatus = 'processing';
diff --git a/packages/geoview-core/src/geo/layer/layer-sets/hover-feature-info-layer-set.ts b/packages/geoview-core/src/geo/layer/layer-sets/hover-feature-info-layer-set.ts
index d977a289a16..b6ed75524ab 100644
--- a/packages/geoview-core/src/geo/layer/layer-sets/hover-feature-info-layer-set.ts
+++ b/packages/geoview-core/src/geo/layer/layer-sets/hover-feature-info-layer-set.ts
@@ -100,7 +100,7 @@ export class HoverFeatureInfoLayerSet extends AbstractLayerSet {
*/
#getOrderedLayerPaths(): string[] {
// Get the map layer order
- const mapLayerOrder = this.layerApi.mapViewer.getMapLayerOrderInfo();
+ const mapLayerOrder = this.layerApi.mapViewer.getMapLayerOrderInfo().filter((layer) => layer.inVisibleRange);
const resultSetLayers = new Set(Object.keys(this.resultSet));
// Filter and order the layers that are in our resultSet
diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts
index c8867330024..ca23b725ec8 100644
--- a/packages/geoview-core/src/geo/layer/layer.ts
+++ b/packages/geoview-core/src/geo/layer/layer.ts
@@ -311,6 +311,7 @@ export class LayerApi {
const addSubLayerPathToLayerOrder = (layerEntryConfig: TypeLayerEntryConfig, layerPath: string): void => {
const subLayerPath = layerPath.endsWith(`/${layerEntryConfig.layerId}`) ? layerPath : `${layerPath}/${layerEntryConfig.layerId}`;
+
const layerInfo: TypeOrderedLayerInfo = {
layerPath: subLayerPath,
visible: layerEntryConfig.initialSettings?.states?.visible !== false,
@@ -321,6 +322,7 @@ export class LayerApi {
layerEntryConfig.initialSettings?.states?.legendCollapsed !== undefined
? layerEntryConfig.initialSettings?.states?.legendCollapsed
: false,
+ inVisibleRange: true,
};
newOrderedLayerInfos.push(layerInfo);
if (layerEntryConfig.listOfLayerEntryConfig?.length) {
@@ -342,6 +344,7 @@ export class LayerApi {
? geoviewLayerConfig.initialSettings?.states?.legendCollapsed
: false,
visible: geoviewLayerConfig.initialSettings?.states?.visible !== false,
+ inVisibleRange: true,
};
newOrderedLayerInfos.push(layerInfo);
(geoviewLayerConfig as TypeGeoviewLayerConfig).listOfLayerEntryConfig.forEach((layerEntryConfig) => {
@@ -589,6 +592,7 @@ export class LayerApi {
queryable: true,
hoverable: true,
legendCollapsed: false,
+ inVisibleRange: true,
};
// GV: This is here as a placeholder so that the layers will appear in the proper order,
@@ -996,6 +1000,17 @@ export class LayerApi {
return gvGroupLayer;
}
+ #setLayerInVisibleRange(layerConfig: TypeLayerEntryConfig): void {
+ if (layerConfig.listOfLayerEntryConfig) {
+ layerConfig.listOfLayerEntryConfig.forEach((subLayerConfig) => this.#setLayerInVisibleRange(subLayerConfig));
+ }
+
+ const zoom = this.mapViewer.getView().getZoom();
+ const { maxZoom, minZoom } = layerConfig.initialSettings;
+ const inVisibleRange = (!maxZoom || zoom! <= maxZoom) && (!minZoom || zoom! > minZoom);
+ MapEventProcessor.setLayerInVisibleRange(this.getMapId(), layerConfig.layerPath, inVisibleRange);
+ }
+
/**
* Continues the addition of the geoview layer.
* Adds the layer to the map if valid. If not (is a string) emits an error.
@@ -1023,6 +1038,9 @@ export class LayerApi {
if (!geoviewLayer.allLayerStatusAreGreaterThanOrEqualTo('error')) {
// Add the OpenLayers layer to the map officially
this.mapViewer.map.addLayer(geoviewLayer.olRootLayer!);
+
+ // Set in visible range property for all newly added layers
+ geoviewLayer.listOfLayerEntryConfig.forEach((layer) => this.#setLayerInVisibleRange(layer));
}
// Log
diff --git a/packages/geoview-core/src/geo/map/map-schema-types.ts b/packages/geoview-core/src/geo/map/map-schema-types.ts
index 65382b66a70..4ba3308ea25 100644
--- a/packages/geoview-core/src/geo/map/map-schema-types.ts
+++ b/packages/geoview-core/src/geo/map/map-schema-types.ts
@@ -385,6 +385,10 @@ export type TypeGeoviewLayerConfig = {
*/
initialSettings?: TypeLayerInitialSettings;
+ /** Min and max scales */
+ minScale?: number;
+ maxScale?: number;
+
/** The layer entries to use from the GeoView layer. */
listOfLayerEntryConfig: TypeLayerEntryConfig[];
};
diff --git a/packages/geoview-core/src/geo/map/map-viewer.ts b/packages/geoview-core/src/geo/map/map-viewer.ts
index 9fefa600284..fa53d9984e4 100644
--- a/packages/geoview-core/src/geo/map/map-viewer.ts
+++ b/packages/geoview-core/src/geo/map/map-viewer.ts
@@ -455,8 +455,20 @@ export class MapViewer {
// Read the zoom value
const zoom = this.getView().getZoom()!;
+ // Get new inVisibleRange values for all layers
+ const newOrderedLayerInfo = this.getMapLayerOrderInfo();
+
+ // GV Used the configs since group GV layers have fake max/min zoom levels
+ // GV The group levels are also missing the zoom level getters, so this worked out well anyway
+ this.layer.getLayerEntryConfigs().forEach((config) => {
+ const { minZoom, maxZoom } = config.initialSettings;
+ const inVisibleRange = (!minZoom || zoom! > minZoom) && (!maxZoom || zoom! <= maxZoom);
+ const foundLayer = newOrderedLayerInfo.find((info) => info.layerPath === config.layerPath);
+ if (foundLayer) foundLayer.inVisibleRange = inVisibleRange;
+ });
+
// Save in the store
- MapEventProcessor.setZoom(this.mapId, zoom);
+ MapEventProcessor.setZoom(this.mapId, zoom, newOrderedLayerInfo);
// Emit to the outside
this.#emitMapZoomEnd({ zoom });