From 405d23867018d203a4aa4a78ff7b23e2cf089311 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Tue, 21 Jul 2026 16:04:24 +0100 Subject: [PATCH 1/3] FCRM-6965 map page now uses datasets plugin --- client/js/map/colours.js | 19 +- client/js/map/datasets/datasetsMenu.js | 72 +++ client/js/map/datasets/datasetsPlugin.js | 37 ++ client/js/map/datasets/featureLayers.js | 82 +++ client/js/map/datasets/floodZones.js | 109 ++++ client/js/map/datasets/surfaceWater.js | 133 +++++ .../datasets/surfaceWaterDatasetGenerator.js | 130 +++++ client/js/map/index.js | 471 +----------------- client/js/map/terms.js | 3 +- client/js/map/tokens.js | 8 +- client/sass/map/index.scss | 1 + 11 files changed, 594 insertions(+), 471 deletions(-) create mode 100644 client/js/map/datasets/datasetsMenu.js create mode 100644 client/js/map/datasets/datasetsPlugin.js create mode 100644 client/js/map/datasets/featureLayers.js create mode 100644 client/js/map/datasets/floodZones.js create mode 100644 client/js/map/datasets/surfaceWater.js create mode 100644 client/js/map/datasets/surfaceWaterDatasetGenerator.js diff --git a/client/js/map/colours.js b/client/js/map/colours.js index 8be7888c..dc019fc0 100644 --- a/client/js/map/colours.js +++ b/client/js/map/colours.js @@ -25,10 +25,10 @@ export const COLOURS = { floodZone3: { default: '#003078', dark: '#e5f5e0' }, floodZone2: { default: '#1d70b8', dark: '#41ab5d' }, - floodZoneClimateChange: { default: '#F4A582', dark: '#BF3D4A' }, + floodZoneClimateChange: { default: floodZoneCCLight, dark: floodZoneCCDark }, floodZoneClimateChangeNoData: { default: darkTeal, dark: white }, - floodDefences: { default: '#f47738', dark: '#f47738' }, + floodDefences: { default: floodDefences, dark: floodDefences }, waterStorageAreas: { default: darkTeal, dark: white }, mainRivers: { default: darkTeal, dark: white } } @@ -39,7 +39,16 @@ const nonFloodZoneDepthBandsLight = [COLOURS.depthOver2300.default, COLOURS.dept const nonFloodZoneDepthBandsDark = [COLOURS.depthOver2300.dark, COLOURS.depth2300.dark, COLOURS.depth1200.dark, COLOURS.depth900.dark, COLOURS.depth600.dark, COLOURS.depth300.dark, COLOURS.depth150.dark] export const colours = { // [default, dark] + nonFloodZoneLight, + nonFloodZoneDark, nonFloodZone: [nonFloodZoneLight, nonFloodZoneDark], + depthOver2300: { default: '#7f2704', dark: '#238b45' }, + depth2300: { default: '#a63603', dark: '#41ab5d' }, + depth1200: { default: '#d94801', dark: '#74c476' }, + depth900: { default: '#f16913', dark: '#a1d99b' }, + depth600: { default: '#fd8d3c', dark: '#c7e9c0' }, + depth300: { default: '#fdae6b', dark: '#e5f5e0' }, + depth150: { default: '#fdd0a2', dark: '#f7fcf5' }, nonFloodZoneDepthBands: [ [nonFloodZoneDepthBandsLight[0], nonFloodZoneDepthBandsDark[0]], [nonFloodZoneDepthBandsLight[1], nonFloodZoneDepthBandsDark[1]], @@ -53,9 +62,9 @@ export const colours = { // [default, dark] floodZone3: [floodZone3Light, floodZone3Dark], floodZoneClimateChange: [floodZoneCCLight, floodZoneCCDark], floodZoneClimateChangeNoData: [black, white], - waterStorageAreas: [darkTeal, white], - mainRivers: [darkTeal, white], - floodDefences: [floodDefences, floodDefences], + waterStorageAreas: { default: darkTeal, dark: white }, + mainRivers: { default: darkTeal, dark: white }, + floodDefences: { default: floodDefences, dark: floodDefences }, floodExtents: [nonFloodZoneLight, nonFloodZoneDark] } diff --git a/client/js/map/datasets/datasetsMenu.js b/client/js/map/datasets/datasetsMenu.js new file mode 100644 index 00000000..98c220f1 --- /dev/null +++ b/client/js/map/datasets/datasetsMenu.js @@ -0,0 +1,72 @@ +export const menu = [ + { + id: 'dataset', + label: 'Datasets', + urlKey: 'dataset', + visibleWhen: true, + type: 'radio', // 'checkbox' or 'radio' + value: 'floodzones', // this is the default value for the menu, it should be one of the items' id + items: [ + { id: 'floodzones', label: 'Flood zones' }, + { id: 'surfacewater', label: 'Surface water' }, + { id: 'none', label: 'None', }, + ], + }, + { + id: 'timeframe', + label: 'Timeframe', + urlKey: 'dataset', + urlIndex: 1, // eg: surfacewater-presentday-high-depth or floodzones-climatechange + type: 'radio', + visibleWhen: { menu: { dataset: ['floodzones', 'surfacewater'] } }, + value: 'presentday', + items: [ + { id: 'presentday', label: 'Present day' }, + { id: 'climatechange', label: '2070 to 2125', visibleWhen: { menu: { dataset: ['floodzones'] } } }, + { id: 'climatechange', label: '2061 to 2125', visibleWhen: { menu: { dataset: ['surfacewater'] } } }, + ] + }, { + id: 'aep', + label: 'Annual likelihood of flooding', + urlKey: 'dataset', + urlIndex: 2, + type: 'radio', + visibleWhen: { menu: { dataset: ['surfacewater'] } }, + value: 'medium', + items: [ + { id: 'high', label: '1 in 30' }, + { id: 'medium', label: '1 in 100' }, + { id: 'low', label: '1 in 1000' }, + ] + }, { + id: 'depth', + label: 'Depth', + urlKey: 'dataset', + urlIndex: 3, + type: 'radio', + visibleWhen: { menu: { dataset: ['surfacewater'] } }, + subMenu: true, + value: 'depthAll', + items: [ + { id: 'depthAll', label: 'All depths', }, + { id: 'depth150', label: 'Full extent of flooding', }, + { id: 'depth300', label: 'Extent over 150mm', }, + { id: 'depth600', label: 'Extent over 300mm', }, + { id: 'depth900', label: 'Extent over 600mm', }, + { id: 'depth1200', label: 'Extent over 900mm', }, + { id: 'depth2300', label: 'Extent over 1200mm', }, + { id: 'depthOver2300', label: 'Extent over 2300mm', }, + ] + }, { + id: 'features', + groupLabel: 'Map features', + urlKey: 'features', + type: 'checkbox', + visibleWhen: true, + items: [ + { id: 'waterstorage', label: 'Water storage' }, + { id: 'flooddefence', label: 'Flood defence' }, + { id: 'mainrivers', label: 'Main rivers' }, + ] + } +] diff --git a/client/js/map/datasets/datasetsPlugin.js b/client/js/map/datasets/datasetsPlugin.js new file mode 100644 index 00000000..bfbde3a0 --- /dev/null +++ b/client/js/map/datasets/datasetsPlugin.js @@ -0,0 +1,37 @@ +import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets' +import { surfaceWaterDatasets, surfaceWaterExtentsKey } from './surfaceWater.js' +import { floodZonesDatasets } from './floodZones.js' +import { featureLayers } from './featureLayers.js' +import { menu } from './datasetsMenu.js' + +export const datasetsPlugin = ({ agolServiceUrl, agolVectorTileUrl, layerNameSuffix }) => { + const datasets = [ + ...floodZonesDatasets({ agolVectorTileUrl, layerNameSuffix }), + surfaceWaterExtentsKey, + ...surfaceWaterDatasets({ agolVectorTileUrl, layerNameSuffix }), + ...featureLayers(agolServiceUrl, layerNameSuffix), + ] + + return createDatasetsPlugin({ + manifest: { + panels: [{ + id: 'datasetsLayers', + desktop: { open: true, slot: 'side', width: '280px', dismissible: false }, + tablet: { slot: 'side', width: '280px', modal: true } + }], + buttons: [ + { + id: 'datasetsLayers', + excludeWhen: ({ appState }) => (appState?.breakpoint === 'desktop'), + } + ] + }, + globals: { + opacityMode: 'global', + opacity: 0.75, + visible: true + }, + datasets, + menu + }) +} diff --git a/client/js/map/datasets/featureLayers.js b/client/js/map/datasets/featureLayers.js new file mode 100644 index 00000000..e2ed6a1b --- /dev/null +++ b/client/js/map/datasets/featureLayers.js @@ -0,0 +1,82 @@ +import { terms } from '../terms.js' +import { colours } from '../colours.js' + +export const featureLayers = (agolServiceUrl, layerNameSuffix) => { + const datasetMainRivers = { + id: 'mainrivers', + label: terms.labels.mainRivers, + groupLabel: terms.labels.mapFeatures, + type: 'FeatureService', + // NOTE: Main Rivers does not have the _NON_PRODUCTION ${layerNameSuffix} + tiles: `${agolServiceUrl}/Statutory_Main_River_Map/FeatureServer`, + showInKey: true, + sourceLayer: 'Statutory_Main_River_Map', + visible: false, + style: { + renderer: { + type: 'simple', + symbol: { + type: 'simple-line', + width: '3px', + color: { outdoor: colours.mainRivers.default, dark: colours.mainRivers.dark }, + } + }, + stroke: { outdoor: colours.mainRivers.default, dark: colours.mainRivers.dark }, + strokeWidth: 3 + } + } + + const datasetWaterStorageAreas = { + id: 'waterstorage', + label: terms.labels.waterStorage, + groupLabel: terms.labels.mapFeatures, + type: 'FeatureService', + tiles: `${agolServiceUrl}/Flood_Storage_Areas${layerNameSuffix}/FeatureServer`, + showInKey: true, + sourceLayer: 'Flood_Storage_Areas', + visible: false, + style: { + renderer: { + type: 'simple', + symbol: { + type: 'simple-fill', + style: 'diagonal-cross', + color: { outdoor: colours.waterStorageAreas.default, dark: colours.waterStorageAreas.dark }, + outline: { + color: { outdoor: colours.waterStorageAreas.default, dark: colours.waterStorageAreas.dark }, + width: 1 + } + } + }, + stroke: { outdoor: colours.waterStorageAreas.default, dark: colours.waterStorageAreas.dark }, + strokeWidth: 1, + fillPattern: 'diagonal-cross-hatch', + fillPatternForegroundColor: { outdoor: colours.waterStorageAreas.default, dark: colours.waterStorageAreas.dark }, + fillPatternBackgroundColor: 'transparent' + } + } + + const datasetFloodDefences = { + id: 'flooddefence', + label: terms.labels.floodDefence, + groupLabel: terms.labels.mapFeatures, + type: 'FeatureService', + tiles: `${agolServiceUrl}/Defences${layerNameSuffix}/FeatureServer`, + showInKey: true, + sourceLayer: 'Defences', + visible: false, + style: { + renderer: { + type: 'simple', + symbol: { + type: 'simple-line', + width: '3px', + color: { outdoor: colours.floodDefences.default, dark: colours.floodDefences.dark }, + } + }, + stroke: { outdoor: colours.floodDefences.default, dark: colours.floodDefences.dark }, + strokeWidth: 3 + } + } + return [datasetMainRivers, datasetWaterStorageAreas, datasetFloodDefences] +} diff --git a/client/js/map/datasets/floodZones.js b/client/js/map/datasets/floodZones.js new file mode 100644 index 00000000..a5cf68c6 --- /dev/null +++ b/client/js/map/datasets/floodZones.js @@ -0,0 +1,109 @@ +export const floodZonesDatasets = ({ agolVectorTileUrl, layerNameSuffix }) => { + const datasetFloodZonesCC = { + id: 'floodzonescc', + label: 'Flood Zones Climate Change', + groupLabel: 'Datasets', + esriGroupId: 'floodzones-group', + tiles: `${agolVectorTileUrl}/Flood_Zones_2_and_3_Rivers_and_Sea_CCP1${layerNameSuffix}/VectorTileServer`, + showInKey: true, + visible: true, + sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea CCP1', + sublayers: [ + { + id: 'climate-change', + label: 'Climate change (2070 to 2125)', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', + showInKey: true, + visibleWhen: { + menu: { + dataset: ['floodzones'], timeframe: ['climatechange'] + } + }, + style: { + fill: { outdoor: '#F4A582', dark: '#BF3D4A' }, + stroke: 'none' + }, + }, + { + id: 'data-unavailable', + label: 'Climate change data unavailable', + showInKey: true, + visibleWhen: { + menu: { + dataset: ['floodzones'], timeframe: ['climatechange'] + } + }, + style: { // This is used just for the key - so that it renders the pattern correctly. + fillPattern: 'dot', + fillPatternForegroundColor: { outdoor: '#000000', dark: '#ffffff' }, + stroke: { outdoor: '#000000', dark: '#FFFFFF' }, + } + }, + { + id: 'data-unavailable-outline', + showInKey: false, + visibleWhen: { + menu: { dataset: ['floodzones'], timeframe: ['climatechange'] } + }, + style: { + stroke: { outdoor: '#000000', dark: '#FFFFFF' }, + }, + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/0' + }, + { + id: 'data-unavailable-light', + visibleWhen: { + mapStyleId: ['outdoor', 'black-and-white'], + menu: { dataset: ['floodzones'], timeframe: ['climatechange'] } + }, + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + esriUseServerStyle: true, + showInKey: false, + }, + { + id: 'data-unavailable-dark', + visibleWhen: { + menu: { dataset: ['floodzones'], timeframe: ['climatechange'] }, + mapStyleId: ['dark'] + }, + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/2', + esriUseServerStyle: true, + showInKey: false, + } + ] + } + + const datasetFloodZones = { + id: 'floodzones', + label: 'Flood Zones', + groupLabel: 'Datasets', + esriGroupId: 'floodzones-group', + tiles: `${agolVectorTileUrl}/Flood_Zones_2_and_3_Rivers_and_Sea${layerNameSuffix}/VectorTileServer`, + showInKey: true, + sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea', + visibleWhen: { + menu: { dataset: ['floodzones'] } + }, + sublayers: [ + { + id: 'flood-zone-2', + label: 'Flood Zone 2', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1', + style: { + fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, + stroke: 'none' + }, + }, + { + id: 'flood-zone-3', + label: 'Flood Zone 3', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1', + style: { + fill: { outdoor: '#003078', dark: '#e5f5e0' }, + stroke: 'none' + }, + } + ] + } + return [datasetFloodZonesCC, datasetFloodZones] +} diff --git a/client/js/map/datasets/surfaceWater.js b/client/js/map/datasets/surfaceWater.js new file mode 100644 index 00000000..74684a48 --- /dev/null +++ b/client/js/map/datasets/surfaceWater.js @@ -0,0 +1,133 @@ +import { terms } from '../terms.js' +import { colours } from '../colours.js' +import { surfaceWaterDatasetGenerator } from './surfaceWaterDatasetGenerator.js' + +export const surfaceWaterDatasets = ({ agolVectorTileUrl, layerNameSuffix }) => { + const datasets = [ + // Surface Water Present Day + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-presentday-low', + tileName: 'Surface_Water_Spatial_Planning_1_in_1000_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 1000 Depths', + timeframe: ['presentday'], + aep: ['low'], + }), + + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-presentday-medium', + tileName: 'Surface_Water_Spatial_Planning_1_in_100_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 100 Depths', + timeframe: ['presentday'], + aep: ['medium'], + }), + + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-presentday-high', + tileName: 'Surface_Water_Spatial_Planning_1_in_30_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 30 Depths', + timeframe: ['presentday'], + aep: ['high'], + }), + + // Surface Water Climate Change + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-climatechange-low', + tileName: 'Surface_Water_Spatial_Planning_1_in_1000_CCP1_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 1000 CCP1 Depths', + timeframe: ['climatechange'], + aep: ['low'], + }), + + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-climatechange-medium', + tileName: 'Surface_Water_Spatial_Planning_1_in_100_CCP1_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 100 CCP1 Depths', + timeframe: ['climatechange'], + aep: ['medium'], + }), + + ...surfaceWaterDatasetGenerator({ + agolVectorTileUrl, + layerNameSuffix, + id: 'surfacewater-climatechange-high', + tileName: 'Surface_Water_Spatial_Planning_1_in_30_CCP1_Depths', + sourceLayer: 'Surface Water Spatial Planning 1 in 30 CCP1 Depths', + timeframe: ['climatechange'], + aep: ['high'], + }) + ] + return datasets +} +const surfaceWaterExtentsKeyStyle = { + stroke: { outdoor: colours.nonFloodZoneLight, dark: colours.nonFloodZoneDark }, + fill: { outdoor: colours.nonFloodZoneLight, dark: colours.nonFloodZoneDark }, +} + +export const surfaceWaterExtentsKey = { + id: 'surfacewater-extents-key', + label: terms.labels.surfaceWater, + // groupLabel: 'Datasets', + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + sublayers: [ + { + id: 'key-150', + label: terms.depth.depth150, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth150'] } } + }, + { + id: 'key-300', + label: terms.depth.depth300, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth300'] } } + }, + { + id: 'key-600', + label: terms.depth.depth600, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth600'] } } + }, + { + id: 'key-900', + label: terms.depth.depth900, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth900'] } } + }, + { + id: 'key-1200', + label: terms.depth.depth1200, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth1200'] } } + }, + { + id: 'key-2300', + label: terms.depth.depth2300, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depth2300'] } } + }, + { + id: 'key-over-2300', + label: terms.depth.depthOver2300, + showInKey: true, + style: surfaceWaterExtentsKeyStyle, + visibleWhen: { menu: { dataset: ['surfacewater'], depth: ['depthOver2300'] } } + } + ] +} diff --git a/client/js/map/datasets/surfaceWaterDatasetGenerator.js b/client/js/map/datasets/surfaceWaterDatasetGenerator.js new file mode 100644 index 00000000..df04bf1d --- /dev/null +++ b/client/js/map/datasets/surfaceWaterDatasetGenerator.js @@ -0,0 +1,130 @@ +import { terms } from '../terms.js' +import { colours } from '../colours.js' + +export const surfaceWaterDatasetGenerator = ({ agolVectorTileUrl, layerNameSuffix, id, tileName, sourceLayer, timeframe, aep }) => { + const visibleWhenMenu = { dataset: ['surfacewater'], timeframe, aep } + + const extentsDataset = { + id: `${id}-extents`, + label: terms.labels.surfaceWater, + groupLabel: terms.labels.datasets, + tiles: `${agolVectorTileUrl}/${tileName}${layerNameSuffix}/VectorTileServer`, + showInKey: true, + sourceLayer, + style: { fill: { outdoor: colours.nonFloodZoneLight, dark: colours.nonFloodZoneDark }, }, + visibleWhen: { menu: visibleWhenMenu }, + sublayers: [ + { + id: 'depthOver2300', + esriStyleLayerId: `${sourceLayer}/>2300mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300', 'depth600', 'depth900', 'depth1200', 'depth2300', 'depthOver2300'] } }, + }, + { + id: 'depth2300', + esriStyleLayerId: `${sourceLayer}/1200-2300mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300', 'depth600', 'depth900', 'depth1200', 'depth2300'] } }, + }, + { + id: 'depth1200', + esriStyleLayerId: `${sourceLayer}/900-1200mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300', 'depth600', 'depth900', 'depth1200'] } }, + }, + { + id: 'depth900', + esriStyleLayerId: `${sourceLayer}/600-900mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300', 'depth600', 'depth900'] } }, + }, + { + id: 'depth600', + esriStyleLayerId: `${sourceLayer}/300-600mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300', 'depth600'] } }, + }, + { + id: 'depth300', + esriStyleLayerId: `${sourceLayer}/150-300mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150', 'depth300'] } }, + }, + { + id: 'depth150', + esriStyleLayerId: `${sourceLayer}/<150mm/1`, + showInKey: false, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depth150'] } }, + }, + ] + } + + const depthDataset = { + id: `${id}-depths`, + label: terms.labels.surfaceWaterDepthInMillimetres, + groupLabel: terms.labels.datasets, + tiles: `${agolVectorTileUrl}/${tileName}${layerNameSuffix}/VectorTileServer`, + showInKey: true, + sourceLayer, + visibleWhen: { menu: { ...visibleWhenMenu, depth: ['depthAll'] } }, + sublayers: [ + { + id: 'depthOver2300', + esriStyleLayerId: `${sourceLayer}/>2300mm/1`, + label: terms.depth.key.depthOver2300, + style: { + fill: { outdoor: colours.depthOver2300.default, dark: colours.depthOver2300.dark }, + } + }, + { + id: 'depth2300', + esriStyleLayerId: `${sourceLayer}/1200-2300mm/1`, + label: terms.depth.key.depth2300, + style: { + fill: { outdoor: colours.depth2300.default, dark: colours.depth2300.dark }, + } + }, + { + id: 'depth1200', + esriStyleLayerId: `${sourceLayer}/900-1200mm/1`, + label: terms.depth.key.depth1200, + style: { + fill: { outdoor: colours.depth1200.default, dark: colours.depth1200.dark }, + } + }, + { + id: 'depth900', + esriStyleLayerId: `${sourceLayer}/600-900mm/1`, + label: terms.depth.key.depth900, + style: { + fill: { outdoor: colours.depth900.default, dark: colours.depth900.dark }, + } + }, + { + id: 'depth600', + esriStyleLayerId: `${sourceLayer}/300-600mm/1`, + label: terms.depth.key.depth600, + style: { + fill: { outdoor: colours.depth600.default, dark: colours.depth600.dark }, + } + }, + { + id: 'depth300', + esriStyleLayerId: `${sourceLayer}/150-300mm/1`, + label: terms.depth.key.depth300, + style: { + fill: { outdoor: colours.depth300.default, dark: colours.depth300.dark }, + } + }, + { + id: 'depth150', + esriStyleLayerId: `${sourceLayer}/<150mm/1`, + label: terms.depth.key.depth150, + style: { + fill: { outdoor: colours.depth150.default, dark: colours.depth150.dark }, + } + }, + ] + } + return [extentsDataset, depthDataset] +} diff --git a/client/js/map/index.js b/client/js/map/index.js index e497ed8c..4281da5a 100644 --- a/client/js/map/index.js +++ b/client/js/map/index.js @@ -7,25 +7,19 @@ import createScaleBarPlugin from '@defra/interactive-map/plugins/scale-bar' import createSearchPlugin from '@defra/interactive-map/plugins/search' import { interactPlugin, attachInteractPlugin } from './interactive-map-helpers/interact' -import { setupEsriConfig, getRequest, getDefraMapConfig, setEsriConfig } from './tokens.js' -import { terms } from './terms.js' -import { colours, getKeyItemFill } from './colours.js' -import { attachLayers, vtLayers, FloodMapLayer } from './mapLayers/index.js' -import { addFeatureLayers } from './mapLayers/featureLayers/featureLayers.js' +import { setupEsriConfig, getRequest, getDefraMapConfig } from './tokens.js' +import { FloodMapLayer } from './mapLayers/index.js' import { setUpBaseMaps } from './baseMap.js' import { checkParamsForPolygon, encodePolygon } from '../../../server/services/shape-utils.js' -import { sliderMarkUp, initialiseSlider } from './slider/index.js' +// TODO: add the slider to the dataset plugin +// import { sliderMarkUp, initialiseSlider } from './slider/index.js' import { getInfoPanel } from './infoPanel.js' // -import { renderMenuHTML } from './interactive-map-helpers/menu.js' -import { renderKeyHTML, attachKeyHandlers } from './interactive-map-helpers/key.js' +import { datasetsPlugin } from './datasets/datasetsPlugin.js' import { drawPlugin, framePlugin, attachDrawPluginHandlers } from './interactive-map-helpers/draw.js' - // -const feature = null// TODO - make this non global - const mapDiv = document.getElementById('map') const symbols = { @@ -35,103 +29,7 @@ const symbols = { mainRivers: '/assets/images/main-rivers.svg' } -const MAX_POLYGON_AREA = 3000000 - -const keyItemDefinitions = { - floodZone2: { - label: 'Flood zone 2', - fill: getKeyItemFill(colours.floodZone2) - }, - floodZone3: { - label: 'Flood zone 3', - fill: getKeyItemFill(colours.floodZone3) - }, - floodZone2PresentDay: { - label: 'Flood zone 2 (present day)', - fill: getKeyItemFill(colours.floodZone2) - }, - floodZone3PresentDay: { - label: 'Flood zone 3 (present day)', - fill: getKeyItemFill(colours.floodZone3) - }, - floodZone3CC: { - label: terms.labels.floodZoneClimateChange, - fill: getKeyItemFill(colours.floodZoneClimateChange) - }, - floodZoneClimateChangeNoData: { - label: terms.labels.noData, - icon: symbols.noData, - fill: getKeyItemFill(colours.floodZoneClimateChangeNoData) - }, - waterStorageAreas: { - id: 'fsa', - label: 'Water storage', - icon: symbols.waterStorageAreas, - fill: getKeyItemFill(colours.waterStorageAreas) - }, - floodDefences: { - id: 'fd', - label: 'Flood defence', - icon: symbols.floodDefences, - fill: getKeyItemFill(colours.floodDefences) - }, - mainRivers: { - id: 'mainr', - label: 'Main Rivers', - icon: symbols.mainRivers, - fill: getKeyItemFill(colours.mainRivers) - }, - floodExtents: { - // id: 'fz2', - label: 'Flood extent', - fill: getKeyItemFill(colours.floodExtents) - }, - surfaceWater0: { - label: '2300', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[0]) - }, - surfaceWater1: { - label: '1200', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[1]) - }, - surfaceWater2: { - label: '900', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[2]) - }, - surfaceWater3: { - label: '600', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[3]) - }, - surfaceWater4: { - label: '300', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[4]) - }, - surfaceWater5: { - label: '150', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[5]) - }, - surfaceWater6: { - label: '', - fill: getKeyItemFill(colours.nonFloodZoneDepthBands[6]) - }, - surfaceWaterDepth150: { label: terms.depth.depth150, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepth300: { label: terms.depth.depth300, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepth600: { label: terms.depth.depth600, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepth900: { label: terms.depth.depth900, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepth1200: { label: terms.depth.depth1200, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepth2300: { label: terms.depth.depth2300, fill: getKeyItemFill(colours.nonFloodZone) }, - surfaceWaterDepthOver2300: { label: terms.depth.depthOver2300, fill: getKeyItemFill(colours.nonFloodZone) } -} - -keyItemDefinitions.common = { - heading: terms.labels.mapFeatures, - collapse: 'collapse', - items: [ - keyItemDefinitions.waterStorageAreas, - keyItemDefinitions.floodDefences, - keyItemDefinitions.mainRivers - ] -} +// const MAX_POLYGON_AREA = 3000000 // capture polygon from query string const queryParams = new URLSearchParams(window.location.search) @@ -147,18 +45,10 @@ const calculateExtent = (polygonToCalculate) => { return calculatedExtent } -let featureQuery, extent +let extent if (queryParams.get('encodedPolygon') || queryParams.get('polygon')) { const { polygon: polygonString } = checkParamsForPolygon({ encodedPolygon: queryParams.get('encodedPolygon'), polygon: queryParams.get('polygon'), encode: false }) const polygon = JSON.parse(polygonString) - - featureQuery = { - type: 'feature', - geometry: { - type: 'polygon', - coordinates: polygon - } - } extent = calculateExtent(polygon) } @@ -190,6 +80,7 @@ getDefraMapConfig().then((defraMapConfig) => { setupConfig: setupEsriConfig }), plugins: [ + datasetsPlugin(defraMapConfig), mapStylePlugin, createScaleBarPlugin({ units: 'metric' }), createSearchPlugin({ @@ -213,7 +104,6 @@ getDefraMapConfig().then((defraMapConfig) => { containerHeight: '100%', enableZoomControls: true, symbols: [symbols.waterStorageAreas, symbols.floodDefences, symbols.mainRivers, symbols.noData], - // interceptorsCallback: getInterceptors, warningPosition: 'top', search: { label: 'Search for a place', @@ -221,316 +111,7 @@ getDefraMapConfig().then((defraMapConfig) => { isExpanded: false, country: 'england' }, - legend: { - htmlAfter: sliderMarkUp, - width: '280px', - isVisible: true, - keyWidth: '360px', - keyDisplay: 'min', - segments: [{ - heading: 'Datasets', - collapse: 'collapse', - items: [ - { - id: 'fz', - label: 'Flood zones 2 and 3' - }, - // Left in place for reinstating later - // { - // id: 'rsd', - // label: 'River and sea with defences' - // }, - // { - // id: 'rsu', - // label: 'River and sea without defences' - // }, - { - id: 'sw', - label: 'Surface water' - }, - { - id: 'mo', - label: 'None' - } - ] - }, - { - id: 'tf', - heading: terms.labels.climateChange, - collapse: 'collapse', - parentIds: ['fz'], - items: [ - { - id: 'fzpd', - label: terms.labels.presentDay - }, - { - id: 'fzcl', - label: terms.labels.fzClimateChange - } - ] - }, - { - id: 'tf', - heading: terms.labels.climateChange, - collapse: 'collapse', - parentIds: ['rsd', 'rsu'], - items: [ - { - id: 'pd', - label: terms.labels.presentDay - }, - { - id: 'cl', - label: 'Years 2070 to 2125' - } - ] - }, - { - id: 'tf', - heading: terms.labels.climateChange, - collapse: 'collapse', - parentIds: ['sw'], - items: [ - { - id: 'pd', - label: terms.labels.presentDay - }, - { - id: 'cl', - label: '2061 to 2125' - } - ] - }, - { - id: 'af1', - heading: terms.labels.annualLikelihood, - collapse: 'collapse', - parentIds: ['rsd'], - items: [ - { - id: 'hr', - label: terms.chance.rsHigh - }, - { - id: 'mr', - label: terms.chance.rsMedium - }, - { - id: 'lr', - label: terms.chance.rsLow - } - ] - }, - { - id: 'sw1', - heading: terms.labels.annualLikelihood, - collapse: 'collapse', - parentIds: ['sw'], - items: [ - { - id: 'hr', - label: terms.chance.swHigh - }, - { - id: 'mr', - label: terms.chance.swMedium, - isSelected: true - }, - { - id: 'lr', - label: terms.chance.swLow - } - ] - }, - { - id: 'sw2', - heading: terms.labels.depth, - collapse: 'collapse', - parentIds: ['sw'], - items: [ - { - id: 'depthAll', - label: terms.depth.depthAll - }, - { - id: 'depth150', - label: terms.depth.depth150 - }, - { - id: 'depth300', - label: terms.depth.depth300 - }, - { - id: 'depth600', - label: terms.depth.depth600 - }, - { - id: 'depth900', - label: terms.depth.depth900 - }, - { - id: 'depth1200', - label: terms.depth.depth1200 - }, - { - id: 'depth2300', - label: terms.depth.depth2300 - }, - { - id: 'depthOver2300', - label: terms.depth.depthOver2300 - } - ] - }, - { - id: 'af2', - heading: terms.labels.annualLikelihood, - parentIds: ['rsu'], - items: [ - { - id: 'mr', - label: terms.chance.rsMedium - }, - { - id: 'lr', - label: terms.chance.rsLow - } - ] - } - ], - key: [ - { - heading: terms.labels.mapFeatures, - collapse: 'collapse', - parentIds: ['fzpd'], - items: [ - keyItemDefinitions.floodZone2, - keyItemDefinitions.floodZone3, - keyItemDefinitions.waterStorageAreas, - keyItemDefinitions.floodDefences, - keyItemDefinitions.mainRivers - ] - }, - { - heading: terms.labels.mapFeatures, - collapse: 'collapse', - parentIds: ['fzcl'], - items: [ - keyItemDefinitions.floodZone2PresentDay, - keyItemDefinitions.floodZone3PresentDay, - keyItemDefinitions.floodZone3CC, - keyItemDefinitions.floodZoneClimateChangeNoData, - keyItemDefinitions.waterStorageAreas, - keyItemDefinitions.floodDefences, - keyItemDefinitions.mainRivers - ] - }, - { // Surface Water DepthAll - heading: terms.labels.mapFeatures, - collapse: 'collapse', - parentIds: ['rsd', 'rsu', 'depthAll'], - items: [ - keyItemDefinitions.waterStorageAreas, - keyItemDefinitions.floodDefences, - keyItemDefinitions.mainRivers, - { - label: 'Surface water depth in millimetres', - display: 'ramp', - numLabels: 1, - items: [ - keyItemDefinitions.surfaceWater6, - keyItemDefinitions.surfaceWater5, - keyItemDefinitions.surfaceWater4, - keyItemDefinitions.surfaceWater3, - keyItemDefinitions.surfaceWater2, - keyItemDefinitions.surfaceWater1, - keyItemDefinitions.surfaceWater0 - ] - } - ] - }, - // Surface Water Extents: - { - parentIds: ['depth150'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth150] - }, - { - parentIds: ['depth300'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth300] - }, - { - parentIds: ['depth600'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth600] - }, - { - parentIds: ['depth900'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth900] - }, - { - parentIds: ['depth1200'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth1200] - }, - { - parentIds: ['depth2300'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepth2300] - }, - { - parentIds: ['depthOver2300'], - ...keyItemDefinitions.common, - items: [...keyItemDefinitions.common.items, keyItemDefinitions.surfaceWaterDepthOver2300] - }, - { - heading: terms.labels.mapFeatures, - collapse: 'collapse', - parentIds: ['mo'], - items: [ - keyItemDefinitions.waterStorageAreas, - keyItemDefinitions.floodDefences, - keyItemDefinitions.mainRivers - ] - } - ] - }, scaleBar: 'metric', - queryArea: { - collapse: 'collapse', - heading: 'Get data for your location', - submitLabel: 'Get summary report', - keyLabel: 'Location boundary', - summary: 'Add or edit a location boundary', - maxZoom: 22, - drawTools: ['polygon', 'square'], - areaUnits: 'hectares', - feature: featureQuery, // feature derived from polygon query string or null if not present - onShapeUpdate: ({ area, geometry }) => { - // We seem to be getting this when we are not editing a shape = one to ask Dan about. - if (!area || !geometry) { - return {} - } - const isValid = area <= MAX_POLYGON_AREA - const warningText = isValid ? null : 'Boundary must be under 300 hectares to order data. You can still download a flood map.' - // This longer version was Rachel's initial suggestion, but was reduced to fit on screen, - // with css we can make it fit, but awaiting opinions from the design team. - // const warningText = isValid ? null : 'Reduce your boundary size to under 300 hectares to order detailed flood risk information (product 4). You can still download a flood map (product 1).' - mapState.shapeIsValid = isValid - return { warningText, allowShape: true } - } - }, - queryLocation: { - layers: vtLayers.map(vtLayer => vtLayer.name) - } - }, (esriMapObjects) => { - const { esriConfig } = esriMapObjects - mapState.esriConfig = esriConfig - mapState.polygon = featureQuery?.geometry?.coordinates - setEsriConfig(esriConfig) }) let reported = false interactiveMap.addEventListener = () => { @@ -539,9 +120,6 @@ getDefraMapConfig().then((defraMapConfig) => { reported = true } } - attachLayers(interactiveMap, defraMapConfig) - addFeatureLayers(interactiveMap, defraMapConfig) - attachKeyHandlers(interactiveMap) attachDrawPluginHandlers(interactiveMap) attachInteractPlugin(interactiveMap) @@ -554,37 +132,8 @@ getDefraMapConfig().then((defraMapConfig) => { tablet: { slot: 'right-top', showLabel: false, order: 1 }, desktop: { slot: 'right-top', showLabel: true, order: 1 } }) - interactiveMap.addButton('menu', { - label: 'Menu', - panelId: 'menu', - iconSvgContent: '', - mobile: { slot: 'top-left', order: 1, showLabel: false }, - tablet: { slot: 'top-left', order: 2 }, - desktop: { slot: 'top-left', order: 2 } - }) - interactiveMap.addButton('key', { - label: 'Key', - panelId: 'key', - iconSvgContent: '', - mobile: { slot: 'top-left', order: 2, showLabel: false }, - tablet: { slot: 'top-left', order: 3 }, - desktop: { slot: 'top-left', order: 3 } - }) - interactiveMap.addPanel('menu', { - label: 'Menu', - html: renderMenuHTML(feature), - mobile: { slot: 'side', modal: true, open: false, dismissible: true }, - tablet: { slot: 'side', width: '260px', open: true, dismissible: true }, - desktop: { slot: 'side', width: '280px', open: true, dismissible: false } - }) - interactiveMap.addPanel('key', { - label: 'Key', - html: renderKeyHTML(), - mobile: { slot: 'drawer', open: false, exclusive: true }, - tablet: { slot: 'left-top', width: '260px', open: false, exclusive: true }, - desktop: { slot: 'left-top', width: '280px', open: true, exclusive: false } - }) - initialiseSlider(interactiveMap) + // TODO: add the slider to the dataset plugin + // initialiseSlider(interactiveMap) }) interactiveMap.on('map:ready', function ({ map, view, mapStyleId, mapSize, crs }) { diff --git a/client/js/map/terms.js b/client/js/map/terms.js index bf3c575e..3df5f4d6 100644 --- a/client/js/map/terms.js +++ b/client/js/map/terms.js @@ -6,8 +6,10 @@ const terms = { fzNoData: 'FZNODATA' }, labels: { + datasets: 'Datasets', floodZones: 'Flood zones', surfaceWater: 'Surface water', + surfaceWaterDepthInMillimetres: 'Surface water depth in millimetres', floodZone2: 'Flood zone 2', floodZone3: 'Flood zone 3', floodZone2PresentDay: 'Flood zone 2 (present day)', @@ -24,7 +26,6 @@ const terms = { waterStorage: 'Water storage', floodDefence: 'Flood defence', mainRivers: 'Main rivers', - }, chance: { rsHigh: 'Rivers and sea 1 in 30', diff --git a/client/js/map/tokens.js b/client/js/map/tokens.js index 9ca20c16..d7d359bf 100644 --- a/client/js/map/tokens.js +++ b/client/js/map/tokens.js @@ -25,7 +25,10 @@ export const getOsToken = async () => { return osAuth } +let _esriConfig export const setupEsriConfig = async (esriConfig) => { + _esriConfig = esriConfig + // Set ESRI API key (using cached token) esriConfig.apiKey = await getEsriToken() @@ -33,7 +36,7 @@ export const setupEsriConfig = async (esriConfig) => { getInterceptors().forEach((interceptor) => esriConfig.request.interceptors.push(interceptor)) } -export const getInterceptors = () => { +const getInterceptors = () => { return [{ urls: 'https://api.os.uk/maps/vector/v1/vts', before: async params => { @@ -100,9 +103,6 @@ export const getDefraMapConfig = async () => { return defraMapConfig } -let _esriConfig -export const setEsriConfig = (esriConfig) => (_esriConfig = esriConfig) - const refreshEsriToken = async () => { if (_esriConfig) { const { token } = await getEsriToken(true) // forceRefresh = true diff --git a/client/sass/map/index.scss b/client/sass/map/index.scss index 60b2161c..e3ec410b 100644 --- a/client/sass/map/index.scss +++ b/client/sass/map/index.scss @@ -10,6 +10,7 @@ $govuk-new-typography-scale: true; @import '../../../node_modules/govuk-frontend/dist/govuk/govuk-frontend.min.css'; @import '../../../node_modules/@defra/interactive-map/dist/css/index.css'; +@import '../../../node_modules/@defra/interactive-map/plugins/datasets/dist/css/index.css'; @import '../../../node_modules/@defra/interactive-map/plugins/search/dist/css/index.css'; @import '../../../node_modules/@defra/interactive-map/plugins/beta/map-styles/dist/css/index.css'; @import '../../../node_modules/@defra/interactive-map/plugins/beta/frame/dist/css/index.css'; From df26442bc3b96a20635cf375f65cc9da48d4cc7d Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 22 Jul 2026 16:50:49 +0100 Subject: [PATCH 2/3] FCRM-6965 updated esri core to 5.1.13 and interactive map to 0.0.35-alpha --- package-lock.json | 329 ++++++++++++++++++++++++++++++++-------------- package.json | 4 +- 2 files changed, 235 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83fb4a30..f8781d6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,13 @@ "hasInstallScript": true, "license": "ISC", "dependencies": { - "@arcgis/core": "^5.0.9", + "@arcgis/core": "^5.1.13", "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.25.9", "@defra/flood-map": "^0.6.8", "@defra/fmp-utilities": "github:DEFRA/fmp-utilities", - "@defra/interactive-map": "^0.0.34-alpha", + "@defra/interactive-map": "^0.0.35-alpha", "@esri/arcgis-rest-request": "^4.2.3", "@hapi/boom": "^10.0.1", "@hapi/catbox": "^12.1.1", @@ -78,9 +78,9 @@ } }, "node_modules/@amcharts/amcharts5": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@amcharts/amcharts5/-/amcharts5-5.15.6.tgz", - "integrity": "sha512-6gVWngHgkMN+v6dJhZ4XJE3fcHjvD5hyjgyNnfvVE1H0ukmHpfFmhI7cCX0cgy0NRyZc4fwgxDMZsiNA7yYKjA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@amcharts/amcharts5/-/amcharts5-5.18.0.tgz", + "integrity": "sha512-2pIka1IrHO0mnTI7A9haq8aaq8leZZhFDythxNA7qYNg/iyGWuNcuzoyzJkybJLlNSlR5crkeNqZQL5up+IF3A==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@types/d3": "^7.0.0", @@ -103,37 +103,51 @@ "d3-voronoi-treemap": "^1.1.2", "flatpickr": "^4.6.13", "markerjs2": "^2.29.4", - "pdfmake": "^0.2.2", + "pdfmake": "~0.3.9", "polylabel": "^1.1.0", "seedrandom": "^3.0.5", "svg-arc-to-cubic-bezier": "^3.2.0", "tslib": "^2.2.0" } }, + "node_modules/@amcharts/amcharts5/node_modules/pdfmake": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.3.11.tgz", + "integrity": "sha512-Uc49J9hUMyuqJk+U+PxlpBpPr96A4HOOfesGx609EPr2ue82+5/Smq/KTAkEqh0/jUGSi1fumvqZ5yAWijJTJg==", + "license": "MIT", + "dependencies": { + "linebreak": "^1.1.0", + "pdfkit": "^0.19.1", + "xmldoc": "^2.0.3" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@arcgis/core": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-5.0.10.tgz", - "integrity": "sha512-FKGkVe3zDw5L2jS3mrFhXm+4O5dNZ6uNwQQGf7CwVjCeYXtrKbxk2xTB9d64ccEnXg2XwL7IOyR6wGIlmA3oMQ==", + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-5.1.13.tgz", + "integrity": "sha512-jpU2HMaHXZ7arde5RAzrulw4ZQrOydcqLsQ/o94XQy0ud/MAsoHZctZ0ncGoocYILYH0FPddBacRPg+vmu6AQQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@amcharts/amcharts5": "~5.15.5", - "@arcgis/toolkit": "^5.0.0", + "@amcharts/amcharts5": "~5.18.0", + "@arcgis/toolkit": "^5.1.0", "@esri/arcgis-html-sanitizer": "~4.1.0", - "@esri/calcite-components": "^5.0.2", - "@vaadin/grid": "~25.0.3", - "@zip.js/zip.js": "~2.8.16", + "@esri/calcite-components": "^5.1.1", + "@vaadin/grid": "~25.1.4", + "@zip.js/zip.js": "~2.8.26", "luxon": "~3.7.2", - "marked": "~17.0.3", + "marked": "~18.0.5", "tslib": "^2.8.1" } }, "node_modules/@arcgis/lumina": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@arcgis/lumina/-/lumina-5.0.10.tgz", - "integrity": "sha512-3Bk63oYbTVkNmHXREFFUnedvxPvAEm/aVXE7mXXwJSEqodq0mJhe/EubnZNI2PMSQa1gjEZTzHZjIzJnqyJkJQ==", + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@arcgis/lumina/-/lumina-5.1.13.tgz", + "integrity": "sha512-jDaOKgTWZzrCLiGAJoBAgq2xkq1XqnyIiZCDZHwMjOIuVXc69Yvi83Q3Ao6Rzm1TtX0p7NGRi5wuKn22TZcqBw==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@arcgis/toolkit": "~5.0.10", + "@arcgis/toolkit": "~5.1.13", "csstype": "^3.1.3", "tslib": "^2.8.1" }, @@ -148,9 +162,9 @@ } }, "node_modules/@arcgis/toolkit": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@arcgis/toolkit/-/toolkit-5.0.10.tgz", - "integrity": "sha512-ZEKolhmoAOl5wl4DoMjn57YqPLNu1QM+x1e3Z4KhwegI9B+An2LXQBM+/UwWPKavKthB8ePPAqXnfctREd2OOQ==", + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@arcgis/toolkit/-/toolkit-5.1.13.tgz", + "integrity": "sha512-Zo2RV3Wr5WvHpprgJK1wzeOg3dLMMfY0e2HvUJS4yBH/3IUpyPnFR33DF65JPaVLeMY0B+V2LiOr99C5lhZo1Q==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "tslib": "^2.8.1" @@ -2371,9 +2385,9 @@ } }, "node_modules/@defra/interactive-map": { - "version": "0.0.34-alpha", - "resolved": "https://registry.npmjs.org/@defra/interactive-map/-/interactive-map-0.0.34-alpha.tgz", - "integrity": "sha512-NYEapl6cpFS5aiWXKNJnCQXt6f9RVcmyizxIkMrgYahjGL4Q/d4ijWo3IX/E0SLHze43topDSrtD1j/YZ0ZYYw==", + "version": "0.0.35-alpha", + "resolved": "https://registry.npmjs.org/@defra/interactive-map/-/interactive-map-0.0.35-alpha.tgz", + "integrity": "sha512-v7BgPY8ON9NlmRdwtk6IJ5dEY2qlqYYGOYc3XMogUtakVARI78oMiryRTgEpFxkLvxRfxHDOEeQRpEoruK0iZg==", "dependencies": { "@babel/runtime": "^7.28.6", "@turf/area": "^7.2.0", @@ -2743,21 +2757,21 @@ } }, "node_modules/@esri/calcite-components": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-5.0.2.tgz", - "integrity": "sha512-AE8AQBsdpWWAPJEBc/nabJbdN+H+iuVBJv215mm8xjlyqpHd6BLsD8nG/K8IywDFHeB9TyHLQdYCk/sNiyQo7w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-5.1.2.tgz", + "integrity": "sha512-KdCk68T+UzVeAMA+kHW5UZ8msedBAH10k1J9Vkhqc8DtpJ0L/j2rw0ve9pbq1sW+hAAbhnTj+0OyP3R5bp1dEg==", "license": "SEE LICENSE.md", "dependencies": { - "@arcgis/lumina": ">=5.0.0-next.144 <6.0.0", - "@arcgis/toolkit": ">=5.0.0-next.144 <6.0.0", - "@esri/calcite-ui-icons": "4.4.0", + "@arcgis/lumina": ">=5.1.0-next.96 <6.0.0", + "@arcgis/toolkit": ">=5.1.0-next.96 <6.0.0", + "@esri/calcite-ui-icons": "4.5.0", "@floating-ui/dom": "^1.6.12", "@floating-ui/utils": "^0.2.8", "@types/sortablejs": "^1.15.8", "color": "^5.0.3", "composed-offset-position": "^0.0.6", "es-toolkit": "^1.39.8", - "focus-trap": "^7.6.5", + "focus-trap": "^8.2.1", "interactjs": "^1.10.27", "lit": "^3.3.0", "sortablejs": "^1.15.6", @@ -2765,10 +2779,19 @@ "type-fest": "^4.30.1" } }, + "node_modules/@esri/calcite-components/node_modules/focus-trap": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.2.2.tgz", + "integrity": "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.5.0" + } + }, "node_modules/@esri/calcite-ui-icons": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@esri/calcite-ui-icons/-/calcite-ui-icons-4.4.0.tgz", - "integrity": "sha512-PcCGId6vKBysRuuoCIYrVsaC+YVv1lFMqVUQn3Qna0BHOIyf7Mursf3NStELNjYdmL0MRYfmLDEDYQwnAJ/tSA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@esri/calcite-ui-icons/-/calcite-ui-icons-4.5.0.tgz", + "integrity": "sha512-MuXK8o5qefXXV/vdDucQUm8zno+9Q0KjadCX82gYX1VgHMLPp0x2+pbQzG3c0MDbRqkidhTqL/zwq/eVq282qg==", "license": "SEE LICENSE.md", "bin": { "spriter": "bin/spriter.js" @@ -4206,6 +4229,30 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nolyfill/is-core-module": { "version": "1.0.39", "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", @@ -4674,6 +4721,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@turf/along": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/along/-/along-7.2.0.tgz", @@ -7800,34 +7856,34 @@ ] }, "node_modules/@vaadin/a11y-base": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-25.0.7.tgz", - "integrity": "sha512-Un2XCTK/wseP02KSUPR/JCuBgm3+uRpjm1S2YFQFaXdXFG1xsFOX0KAs9yRj2MxUsdCK75AU3Upnfbwln173lg==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-25.1.8.tgz", + "integrity": "sha512-CcAvMfHB/NeFuRNzkgDoqhFOjRzdm7qXEvEFErP9mB7nnsE6NY6wNlEpcUcxo5UtHADI37j8O5ESMTfgMHwHeQ==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/component-base": "~25.0.7", + "@vaadin/component-base": "~25.1.8", "lit": "^3.0.0" } }, "node_modules/@vaadin/checkbox": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-25.0.7.tgz", - "integrity": "sha512-wVweFiU5TDvwZE+BdGMIYrlv+409Vwv4dLv8jPOGOU05dS2koVYQLJVrlSQO4+3PUj799yqV9r8I3u7avcYIUg==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-25.1.8.tgz", + "integrity": "sha512-2PoppbhSGgzjHaK6HkRzyy+mmUMbPCIEdjRt+NdAzpCBMVBlr0Bc5xGS1NU8hk9snu3T11Ve7blWPGoA5++28A==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~25.0.7", - "@vaadin/component-base": "~25.0.7", - "@vaadin/field-base": "~25.0.7", - "@vaadin/vaadin-themable-mixin": "~25.0.7", + "@vaadin/a11y-base": "~25.1.8", + "@vaadin/component-base": "~25.1.8", + "@vaadin/field-base": "~25.1.8", + "@vaadin/vaadin-themable-mixin": "~25.1.8", "lit": "^3.0.0" } }, "node_modules/@vaadin/component-base": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-25.0.7.tgz", - "integrity": "sha512-HzZiw+sHHWbYbXguNpegwK85KqCepjc1+saOJ9z/b7tyIr4XJUkP0Cg3IUC9AdBIVdYYQ8RoD+sSgcAwp31zCg==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-25.1.8.tgz", + "integrity": "sha512-hk/mFUCiJGj8y7snjOYNGPBa6RSZfACQawZ+QbyG1molkl9o66G5DPskWTLZHo5ps6vssKCXuOSm8dt4nhLfaQ==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", @@ -7837,30 +7893,30 @@ } }, "node_modules/@vaadin/field-base": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-25.0.7.tgz", - "integrity": "sha512-phr1GrHgl+UuakbByzCywqwtDdJSPuHFriPqe8fZ4GeD+OMVOBHJJy24frncIlvZVM+rJHA59GMj2iTlF3Zhrg==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-25.1.8.tgz", + "integrity": "sha512-zJOFNodbPE02drccgLzPV+6CxI3l0BvkP2lVJfZLkJQEkJv/3vOiLbuC/5ZqgLU+AS2xpjfSDZ77obqNm8CTlg==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~25.0.7", - "@vaadin/component-base": "~25.0.7", + "@vaadin/a11y-base": "~25.1.8", + "@vaadin/component-base": "~25.1.8", "lit": "^3.0.0" } }, "node_modules/@vaadin/grid": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-25.0.7.tgz", - "integrity": "sha512-rUzoISbqkZL6FaRyZJh5+A/U5nEd8Rnx9n+agHrZ6w6x04Wh6UdW327ZKRGHXJiN4u8anTX0m14YJ3j50V9ChA==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-25.1.8.tgz", + "integrity": "sha512-TL24sncop8l1S9EYcIXQZ54295GoymehWoxGBMFDOINGCqID6hUUFeVmRCg0gnRD/sKRYQML81oX36lFeH47ww==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~25.0.7", - "@vaadin/checkbox": "~25.0.7", - "@vaadin/component-base": "~25.0.7", - "@vaadin/lit-renderer": "~25.0.7", - "@vaadin/text-field": "~25.0.7", - "@vaadin/vaadin-themable-mixin": "~25.0.7", + "@vaadin/a11y-base": "~25.1.8", + "@vaadin/checkbox": "~25.1.8", + "@vaadin/component-base": "~25.1.8", + "@vaadin/lit-renderer": "~25.1.8", + "@vaadin/text-field": "~25.1.8", + "@vaadin/vaadin-themable-mixin": "~25.1.8", "lit": "^3.0.0" } }, @@ -7903,37 +7959,37 @@ } }, "node_modules/@vaadin/input-container": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-25.0.7.tgz", - "integrity": "sha512-ECmJ1rm6b/T920JCP9eQkUMffKiX5xjzLDKbRyWVf6JS3+ZU0s/U0iOsY1YXnAMvbuJlv0cvbrkgA7mrbmKtKw==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-25.1.8.tgz", + "integrity": "sha512-GKHbCaCutC65wn23jfNFi/IOCkDBxTDfDToC+zlEhkuEl8clYaU/BF9cIiQxJL67K8N7yroDPeZqdNY+oc3PRQ==", "license": "Apache-2.0", "dependencies": { - "@vaadin/component-base": "~25.0.7", - "@vaadin/vaadin-themable-mixin": "~25.0.7", + "@vaadin/component-base": "~25.1.8", + "@vaadin/vaadin-themable-mixin": "~25.1.8", "lit": "^3.0.0" } }, "node_modules/@vaadin/lit-renderer": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-25.0.7.tgz", - "integrity": "sha512-ZSGmsXz28P+PzVupYvYx9amSMsGUPkoSRc56s6moggK/2InVwbud56T5+TczdujjRSjRg7HyQ/LXUjRUF6CBEQ==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-25.1.8.tgz", + "integrity": "sha512-J+TTNfy/qAXDjXlvOJ0Sar0Cl6kpCxv00BSgDaUAMh0oNnR1WkeJ2Muq76wGt5u8FOtGWn9TzYi9+H/NcBTdPQ==", "license": "Apache-2.0", "dependencies": { "lit": "^3.0.0" } }, "node_modules/@vaadin/text-field": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-25.0.7.tgz", - "integrity": "sha512-oYcpT2Mp8SCy3H1j+blN2pKkIv/QhXHnlYMzU+RdeIC1ef+NJUthsbf1A7UL8LDnWuPUORo6Eri7Z6Izxxjn+w==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-25.1.8.tgz", + "integrity": "sha512-Mxt/QavVGiOJYAtdlAqm1QllwyWk5GVf92jlV8JKQdRdVCi46rbSifXLaZQOFKlS5yOKGGQolWL1FFYRkz3Eqg==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/a11y-base": "~25.0.7", - "@vaadin/component-base": "~25.0.7", - "@vaadin/field-base": "~25.0.7", - "@vaadin/input-container": "~25.0.7", - "@vaadin/vaadin-themable-mixin": "~25.0.7", + "@vaadin/a11y-base": "~25.1.8", + "@vaadin/component-base": "~25.1.8", + "@vaadin/field-base": "~25.1.8", + "@vaadin/input-container": "~25.1.8", + "@vaadin/vaadin-themable-mixin": "~25.1.8", "lit": "^3.0.0" } }, @@ -8015,13 +8071,13 @@ } }, "node_modules/@vaadin/vaadin-themable-mixin": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-25.0.7.tgz", - "integrity": "sha512-HpDsQ0N+8Dcw6ur7AakXoEJsTUN28aGH3k29YUVdOAAtquN++siOrCpNhosVS8laI1X0uYI7w1dvQLSNAbZccg==", + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-25.1.8.tgz", + "integrity": "sha512-eTGecAh1tzG8lXjIoYI1vMqaMm7CTBAQZhFL+c2xIYGQsGfuqk6+5ikdmq9KyDozR2i1K3qw00+fQQPZHNIKKg==", "license": "Apache-2.0", "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "@vaadin/component-base": "~25.0.7", + "@vaadin/component-base": "~25.1.8", "lit": "^3.0.0" } }, @@ -8247,9 +8303,9 @@ "license": "Apache-2.0" }, "node_modules/@zip.js/zip.js": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.23.tgz", - "integrity": "sha512-RB+RLnxPJFPrGvQ9rgO+4JOcsob6lD32OcF0QE0yg24oeW9q8KnTTNlugcDaIveEcCbclobJcZP+fLQ++sH0bw==", + "version": "2.8.33", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.33.tgz", + "integrity": "sha512-Mc+s4DdDl9lmhFmkOmNDryC/b4rZm7y2/qBUQTCwPYGkQ8dkCeykILqEYBd+14ATuj93XWZJBrPe2x+cF9LcGA==", "license": "BSD-3-Clause", "engines": { "bun": ">=0.7.0", @@ -8474,7 +8530,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -9002,6 +9058,21 @@ "base64-js": "^1.1.2" } }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserify-zlib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -11596,6 +11667,23 @@ } } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -11680,6 +11768,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -14093,6 +14182,12 @@ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT" }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14308,6 +14403,25 @@ "node": ">= 0.8.0" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -14782,9 +14896,9 @@ "license": "AGPL-3.0" }, "node_modules/marked": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz", - "integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==", + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.7.tgz", + "integrity": "sha512-iDVQ5ldaiKXn6b2JroX5kgRfmwgqolW7NpaEzTl1k/2Zh1njIEN9yniyLV/mOvWwtsE8OGgkjsCYvijuPk1dtA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -15756,6 +15870,20 @@ "pbf": "bin/pbf" } }, + "node_modules/pdfkit": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.19.1.tgz", + "integrity": "sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA==", + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.0.0", + "@noble/hashes": "^1.6.0", + "fontkit": "^2.0.4", + "js-md5": "^0.8.3", + "linebreak": "^1.1.0", + "png-js": "^1.1.0" + } + }, "node_modules/pdfmake": { "version": "0.2.23", "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.23.tgz", @@ -15926,9 +16054,12 @@ } }, "node_modules/png-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", - "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz", + "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==", + "dependencies": { + "browserify-zlib": "^0.2.0" + } }, "node_modules/point-in-polygon": { "version": "1.1.0", @@ -16890,6 +17021,12 @@ "protocol-buffers-schema": "^3.3.1" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, "node_modules/robust-predicates": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", @@ -17904,9 +18041,9 @@ } }, "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", "license": "MIT" }, "node_modules/tapable": { diff --git a/package.json b/package.json index 19dbb94e..ebcc941c 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,13 @@ "author": "defra", "license": "ISC", "dependencies": { - "@arcgis/core": "^5.0.9", + "@arcgis/core": "^5.1.13", "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.25.9", "@defra/flood-map": "^0.6.8", "@defra/fmp-utilities": "github:DEFRA/fmp-utilities", - "@defra/interactive-map": "^0.0.34-alpha", + "@defra/interactive-map": "^0.0.35-alpha", "@esri/arcgis-rest-request": "^4.2.3", "@hapi/boom": "^10.0.1", "@hapi/catbox": "^12.1.1", From fbf323a1b1a3b0ed8bcfa91eceb34e1674198f0c Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 22 Jul 2026 16:59:57 +0100 Subject: [PATCH 3/3] FCRM-6965 remember the hovered over styleLayerId - ready for info panel --- client/js/map/datasets/floodZones.js | 1 + client/js/map/index.js | 55 +++++++++++++++++++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/client/js/map/datasets/floodZones.js b/client/js/map/datasets/floodZones.js index a5cf68c6..28aedc68 100644 --- a/client/js/map/datasets/floodZones.js +++ b/client/js/map/datasets/floodZones.js @@ -8,6 +8,7 @@ export const floodZonesDatasets = ({ agolVectorTileUrl, layerNameSuffix }) => { showInKey: true, visible: true, sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea CCP1', + visibleWhen: { menu: { dataset: ['floodzones'] } }, sublayers: [ { id: 'climate-change', diff --git a/client/js/map/index.js b/client/js/map/index.js index 4281da5a..355eb450 100644 --- a/client/js/map/index.js +++ b/client/js/map/index.js @@ -8,7 +8,6 @@ import createSearchPlugin from '@defra/interactive-map/plugins/search' import { interactPlugin, attachInteractPlugin } from './interactive-map-helpers/interact' import { setupEsriConfig, getRequest, getDefraMapConfig } from './tokens.js' -import { FloodMapLayer } from './mapLayers/index.js' import { setUpBaseMaps } from './baseMap.js' import { checkParamsForPolygon, encodePolygon } from '../../../server/services/shape-utils.js' // TODO: add the slider to the dataset plugin @@ -136,8 +135,14 @@ getDefraMapConfig().then((defraMapConfig) => { // initialiseSlider(interactiveMap) }) - interactiveMap.on('map:ready', function ({ map, view, mapStyleId, mapSize, crs }) { - initPointerMove(view) + interactiveMap.on('datasets:ready', function () { + updateVisibleLayers() + initPointerMove(mapState.view) + }) + + interactiveMap.on('map:ready', function ({ map, view, _mapStyleId, _mapSize, _crs }) { + mapState.map = map + mapState.view = view interactiveMap.addPanel('help-banner', { label: 'Click on the flood zones for information', @@ -157,6 +162,7 @@ getDefraMapConfig().then((defraMapConfig) => { }) const mapState = { + map: null, isDark: false, isRamp: false, layers: [], @@ -165,32 +171,53 @@ getDefraMapConfig().then((defraMapConfig) => { isFloodZone: false } + const updateVisibleLayers = () => { + mapState.visibleLayers = mapState.map.allLayers.items.filter((item) => (item.type === 'group' || item.type === 'vector-tile') && item.visible === true && item.id !== 'baselayer') + console.log('visibleLayers', mapState.visibleLayers) + } + const initPointerMove = (view) => { let lastHit = 0 const throttleMs = 20 // Throttle to reduce hitTest usage const minScale = 250000 // vector tile layers use minScale value from arcgis online config for visibility - view.on('pointer-move', e => { + view.on('pointer-enter', updateVisibleLayers) + + view.on('pointer-move', event => { const now = Date.now() - if (!FloodMapLayer.visibleLayer || now - lastHit < throttleMs || view.scale > minScale) { + if (!mapState.visibleLayers || now - lastHit < throttleMs || view.scale > minScale) { return } lastHit = now - const layersToTest = FloodMapLayer.visibleLayer.allLayers || [FloodMapLayer.visibleLayer] - view.hitTest(e, { include: layersToTest }).then((response) => { + view.hitTest(event, { include: mapState.visibleLayers }).then((response) => { + let topVisibleStyleLayerId = null if (response?.results?.length > 0) { - // Now do an additional check for the SW layers, in case we are hovering over a hidden SW style layer - // if it is NOT a SW layer, then FloodMapLayer.visibleLayer.isStyleLayerIdVisible will always return true. - const { layerId } = response?.results?.[0]?.graphic?.origin || {} - document.body.style.cursor = FloodMapLayer.visibleLayer.isStyleLayerIdVisible(layerId) ? 'pointer' : 'default' - return + const visibleStyleLayerIds = response?.results.reduce((layerIds, result) => { + const { layerId } = result.graphic?.origin || {} + if (!layerId) { + return layerIds + } + const vtLayer = result.layer + const styleLayer = vtLayer?.getStyleLayer(layerId) + if (styleLayer?.layout?.visibility === 'visible') { + layerIds.push(layerId) + } + return layerIds + }, []) + + topVisibleStyleLayerId = visibleStyleLayerIds?.[0] || null + } + if (mapState.cursorStyleLayer !== topVisibleStyleLayerId) { + mapState.cursorStyleLayer = topVisibleStyleLayerId + console.log('cursorStyleLayer', mapState.cursorStyleLayer) } - document.body.style.cursor = 'default' + document.body.style.cursor = topVisibleStyleLayerId ? 'pointer' : 'default' }) }) - view.on('pointer-leave', _e => { + view.on('pointer-leave', () => { document.body.style.cursor = 'default' + mapState.visibleLayers = null }) }