From cf54d797a7c8479023cc888350946ceb35fcc11b Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Mon, 15 Jun 2026 14:08:44 +0100 Subject: [PATCH 01/10] IM-360 added flood-zones-cc --- demo/js/esri-datasets.js | 35 +++++++++++++++++++ .../src/adapters/esri/esriLayerAdapter.js | 3 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 7591a512..62c27f53 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -8,10 +8,45 @@ import { vtsMapStyles27700 } from './mapStyles.js' import { transformGeocodeRequest, transformVtsRequest3857, setupEsriConfig } from './auth.js' const datasets = [ + { + id: 'flood-zones-cc', + label: 'Flood Zones Climate Change', + groupLabel: 'Datasets', + groupId: 'flood-zones', + tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_CCP1_NON_PRODUCTION/VectorTileServer`, + showInKey: true, + showInMenu: true, + sublayers: [ + { + id: 'flood-zones', + label: 'Climate change (2070 to 2125)', + styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', + showInKey: true, + showInMenu: false, + style: { + fill: { outdoor: '#F4A582', dark: '#BF3D4A' }, + stroke: 'none' + }, + }, + { + id: 'data-unavailable', + label: 'Climate change data unavailable', + styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + showInKey: true, + showInMenu: false, + style: { + fillPattern: 'dot', + fillPatternForegroundColor: { outdoor: '#000000', dark: '#ffffff' }, + stroke: { outdoor: '#000000', dark: '#ffffff' }, + }, + } + ] + }, { id: 'flood-zones', label: 'Flood Zones', groupLabel: 'Datasets', + groupId: 'flood-zones', tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_NON_PRODUCTION/VectorTileServer`, showInKey: true, showInMenu: true, diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index 94526656..fa9d1b63 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -9,11 +9,12 @@ export default class EsriLayerAdapter { } async init (mapStyle) { - console.log('EsriLayerAdapter init', mapStyle) + // TODO - move some of this into a super LayerAdapter class that this extends datasetRegistry.forEachDataset(registryDataset => this._addLayers(registryDataset, mapStyle)) } _addLayers (registryDataset, mapStyle) { + console.log('Creating Esri VectorTileLayer for dataset', registryDataset.id) const vectorTileLayer = new VectorTileLayer({ id: registryDataset.id, url: registryDataset.tiles, From cc47b28f3a69b20c947730ec4766b5c5cb04d806 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Tue, 16 Jun 2026 12:07:20 +0100 Subject: [PATCH 02/10] IM-360 wip - setting esri styles at runTime --- demo/js/esri-datasets.js | 16 ++++- .../src/adapters/esri/esriLayerAdapter.js | 60 ++++++++++++++++++- .../maplibre/registry/mapLibreDataset.js | 2 - plugins/beta/datasets/src/registry/dataset.js | 3 + 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 62c27f53..8280d061 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -16,14 +16,17 @@ const datasets = [ tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_CCP1_NON_PRODUCTION/VectorTileServer`, showInKey: true, showInMenu: true, + removeStyles: true, + sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea CCP1', sublayers: [ { - id: 'flood-zones', + id: 'climate-change', label: 'Climate change (2070 to 2125)', styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', showInKey: true, showInMenu: false, style: { + filter: ['==', '_symbol', 0], fill: { outdoor: '#F4A582', dark: '#BF3D4A' }, stroke: 'none' }, @@ -35,8 +38,10 @@ const datasets = [ showInKey: true, showInMenu: false, style: { + filter: ['==', '_symbol', 1], fillPattern: 'dot', fillPatternForegroundColor: { outdoor: '#000000', dark: '#ffffff' }, + fill: { outdoor: '#0000ff', dark: '#BF3D4A' }, stroke: { outdoor: '#000000', dark: '#ffffff' }, }, } @@ -50,6 +55,7 @@ const datasets = [ tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_NON_PRODUCTION/VectorTileServer`, showInKey: true, showInMenu: true, + sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea', sublayers: [ { id: 'flood-zone-2', @@ -58,7 +64,9 @@ const datasets = [ showInKey: true, showInMenu: false, style: { - fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, + filter: ['==', 'flood_zone', 'FZ2'], + fill: { outdoor: '#ff0000', dark: '#7fcdbb' }, + // fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, stroke: 'none' }, }, @@ -69,7 +77,9 @@ const datasets = [ showInKey: true, showInMenu: false, style: { - fill: { outdoor: '#003078', dark: '#e5f5e0' }, + filter: ['==', 'flood_zone', 'FZ3'], + fill: { outdoor: '#00ff00', dark: '#e5f5e0' }, + // fill: { outdoor: '#003078', dark: '#e5f5e0' }, stroke: 'none' }, } diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index fa9d1b63..d28cd5ad 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -1,11 +1,13 @@ import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js' import { datasetRegistry } from '../../registry/datasetRegistry.js' +import { getValueForStyle } from '../../../../../../src/utils/getValueForStyle.js' export default class EsriLayerAdapter { constructor (mapProvider, symbolRegistry, patternRegistry) { this._mapProvider = mapProvider this._map = mapProvider.map // TODO: Implement symbolRegistry and patternRegistry usage in the adapter + this._mapLayers = {} } async init (mapStyle) { @@ -13,7 +15,7 @@ export default class EsriLayerAdapter { datasetRegistry.forEachDataset(registryDataset => this._addLayers(registryDataset, mapStyle)) } - _addLayers (registryDataset, mapStyle) { + async _addLayers (registryDataset, mapStyle) { console.log('Creating Esri VectorTileLayer for dataset', registryDataset.id) const vectorTileLayer = new VectorTileLayer({ id: registryDataset.id, @@ -21,6 +23,48 @@ export default class EsriLayerAdapter { opacity: 1, visible: registryDataset.visible }) + await vectorTileLayer.load() + await vectorTileLayer.when() + console.log('vectorTileLayer.style', vectorTileLayer.style) + // console.log('VectorTileLayer loaded', vectorTileLayer) + // console.log('VectorTileLayer currentStyleInfo', vectorTileLayer.currentStyleInfo) + const { styleRepository = {} } = vectorTileLayer + // const { layers: styleLayers = [] } = styleRepository + const styleLayers = JSON.parse(JSON.stringify(styleRepository.layers)) + console.log('VectorTileLayer styleLayers', registryDataset.id, JSON.parse(JSON.stringify(styleLayers))) + styleLayers.forEach((styleLayer) => { + console.log('Deleting VectorTileLayer styleLayer', styleLayer.id) + vectorTileLayer.deleteStyleLayer(styleLayer.id) + }) + + registryDataset.sublayers.forEach(sublayer => { + // const style = vectorTileLayer.getStyleLayer(sublayer.styleLayerId) + // console.log('Sublayer', sublayer.id, sublayer.styleLayerId, style) + // vectorTileLayer.deleteStyleLayer(sublayer.styleLayerId) + const newStyle = { + id: sublayer.id, + type: 'fill', + paint: { + 'fill-color': getValueForStyle(sublayer.style.fill, mapStyle.id) + }, + source: 'esri', + 'source-layer': sublayer.sourceLayer, + filter: sublayer.style.filter + } + vectorTileLayer.setStyleLayer(newStyle) + const addedStyle = vectorTileLayer.getStyleLayer(newStyle.id) + console.log('Added style layer', addedStyle) + }) + + console.log('VectorTileLayer styleLayers', JSON.parse(JSON.stringify(styleRepository.layers))) + + // const styleUrl = + // `${registryDataset.tiles}/resources/styles/root.json` + + // const style = await fetch(styleUrl).then(r => r.json()) + // console.log('style', style) + + this._mapLayers[registryDataset.id] = vectorTileLayer this._map.add(vectorTileLayer) } @@ -36,8 +80,18 @@ export default class EsriLayerAdapter { console.log('ESRI: applyStyle', args) } - async applyDatasetVisibility (...args) { - console.log('ESRI: applyDatasetVisibility', args) + async applyDatasetVisibility (datasetId) { + console.log('ESRI: applyDatasetVisibility', datasetId) + const registryDataset = datasetRegistry.getDataset(datasetId) + const { id, isSublayer } = registryDataset + if (isSublayer) { + const { parent, styleLayerId } = registryDataset + const parentLayer = this._mapLayers[parent.id] + parentLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) + return + } + this._mapLayers[id].visible = registryDataset.visible + // this._map.getLayer(id).visible = registryDataset.visible } async applyGlobalVisibility (...args) { diff --git a/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js b/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js index 0bd1e77d..a274fe37 100644 --- a/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +++ b/plugins/beta/datasets/src/adapters/maplibre/registry/mapLibreDataset.js @@ -5,8 +5,6 @@ import { logger } from '../../../../../../../src/services/logger.js' const MAX_TILE_ZOOM = 22 export class MapLibreDataset extends Dataset { - get visibility () { return this.visible ? 'visible' : 'none' } - get fillLayerId () { if (this.hasSublayers) { return null diff --git a/plugins/beta/datasets/src/registry/dataset.js b/plugins/beta/datasets/src/registry/dataset.js index 2793ca96..14ea0ab7 100644 --- a/plugins/beta/datasets/src/registry/dataset.js +++ b/plugins/beta/datasets/src/registry/dataset.js @@ -83,6 +83,9 @@ export class Dataset { return this._datasetDefinition.visible && getGlobalVisibility() } + get styleLayerId () { return this._datasetDefinition.styleLayerId } + get visibility () { return this.visible ? 'visible' : 'none' } + get symbolAnchor () { if (this.style?.symbolAnchor) { return this.style.symbolAnchor From 412db3203c4d18854e9bdbac40305a6a40b46f6b Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 17 Jun 2026 10:07:05 +0100 Subject: [PATCH 03/10] IM-360 adding vtls with some styling --- .../src/adapters/esri/esriLayerAdapter.js | 78 +++++++++---------- .../datasets/src/components/Key/KeyItem.jsx | 3 + .../datasets/src/initialise/DatasetsInit.jsx | 4 +- .../src/initialise/initialiseDatasets.js | 2 +- plugins/beta/datasets/src/registry/dataset.js | 16 +++- .../datasets/src/registry/datasetRegistry.js | 7 +- 6 files changed, 63 insertions(+), 47 deletions(-) diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index d28cd5ad..63ddd40a 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -12,7 +12,10 @@ export default class EsriLayerAdapter { async init (mapStyle) { // TODO - move some of this into a super LayerAdapter class that this extends - datasetRegistry.forEachDataset(registryDataset => this._addLayers(registryDataset, mapStyle)) + console.log('adding layers') + const topLevelDatasets = datasetRegistry.topLevelDatasets() + await Promise.all(topLevelDatasets.map(registryDataset => this._addLayers(registryDataset, mapStyle))) + await Promise.all(topLevelDatasets.map(registryDataset => this.applyDatasetVisibility(registryDataset.id))) } async _addLayers (registryDataset, mapStyle) { @@ -21,51 +24,32 @@ export default class EsriLayerAdapter { id: registryDataset.id, url: registryDataset.tiles, opacity: 1, - visible: registryDataset.visible + visible: false }) await vectorTileLayer.load() - await vectorTileLayer.when() - console.log('vectorTileLayer.style', vectorTileLayer.style) - // console.log('VectorTileLayer loaded', vectorTileLayer) - // console.log('VectorTileLayer currentStyleInfo', vectorTileLayer.currentStyleInfo) - const { styleRepository = {} } = vectorTileLayer - // const { layers: styleLayers = [] } = styleRepository - const styleLayers = JSON.parse(JSON.stringify(styleRepository.layers)) - console.log('VectorTileLayer styleLayers', registryDataset.id, JSON.parse(JSON.stringify(styleLayers))) - styleLayers.forEach((styleLayer) => { - console.log('Deleting VectorTileLayer styleLayer', styleLayer.id) - vectorTileLayer.deleteStyleLayer(styleLayer.id) - }) registryDataset.sublayers.forEach(sublayer => { - // const style = vectorTileLayer.getStyleLayer(sublayer.styleLayerId) - // console.log('Sublayer', sublayer.id, sublayer.styleLayerId, style) - // vectorTileLayer.deleteStyleLayer(sublayer.styleLayerId) - const newStyle = { - id: sublayer.id, - type: 'fill', - paint: { - 'fill-color': getValueForStyle(sublayer.style.fill, mapStyle.id) - }, - source: 'esri', - 'source-layer': sublayer.sourceLayer, - filter: sublayer.style.filter + const { styleLayerId, style, hasStroke, hasFill } = sublayer + if (!styleLayerId) { + return + } + const layerPaintProperties = vectorTileLayer.getPaintProperties(styleLayerId) + if (layerPaintProperties) { + console.log('VectorTileLayer styleLayer paint properties', styleLayerId, layerPaintProperties) + if (hasStroke) { + layerPaintProperties['line-color'] = getValueForStyle(style.stroke, mapStyle.id) + } + if (hasFill) { + layerPaintProperties['fill-color'] = getValueForStyle(style.fill, mapStyle.id) + } + console.log('VectorTileLayer updated paint properties', styleLayerId, layerPaintProperties) + vectorTileLayer.setPaintProperties(styleLayerId, layerPaintProperties) } - vectorTileLayer.setStyleLayer(newStyle) - const addedStyle = vectorTileLayer.getStyleLayer(newStyle.id) - console.log('Added style layer', addedStyle) }) - - console.log('VectorTileLayer styleLayers', JSON.parse(JSON.stringify(styleRepository.layers))) - - // const styleUrl = - // `${registryDataset.tiles}/resources/styles/root.json` - - // const style = await fetch(styleUrl).then(r => r.json()) - // console.log('style', style) - this._mapLayers[registryDataset.id] = vectorTileLayer this._map.add(vectorTileLayer) + console.log('finished adding layers') + return vectorTileLayer.when() } async removeDataset (...args) { @@ -83,15 +67,25 @@ export default class EsriLayerAdapter { async applyDatasetVisibility (datasetId) { console.log('ESRI: applyDatasetVisibility', datasetId) const registryDataset = datasetRegistry.getDataset(datasetId) - const { id, isSublayer } = registryDataset + const { id, isSublayer, visible } = registryDataset if (isSublayer) { const { parent, styleLayerId } = registryDataset - const parentLayer = this._mapLayers[parent.id] - parentLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) + const vectorTileLayer = this._mapLayers[parent.id] + console.log('SUBLAYER setStyleLayerVisibility', vectorTileLayer.id, styleLayerId) + vectorTileLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) return + } else if (visible) { + const vectorTileLayer = this._mapLayers[datasetId] + registryDataset.sublayers.forEach(sublayer => { + const { styleLayerId } = sublayer + if (!styleLayerId) { + return + } + console.log('setStyleLayerVisibility', datasetId, styleLayerId) + vectorTileLayer.setStyleLayerVisibility(styleLayerId, sublayer.visibility) + }) } this._mapLayers[id].visible = registryDataset.visible - // this._map.getLayer(id).visible = registryDataset.visible } async applyGlobalVisibility (...args) { diff --git a/plugins/beta/datasets/src/components/Key/KeyItem.jsx b/plugins/beta/datasets/src/components/Key/KeyItem.jsx index f931a7dc..b47a1ccc 100644 --- a/plugins/beta/datasets/src/components/Key/KeyItem.jsx +++ b/plugins/beta/datasets/src/components/Key/KeyItem.jsx @@ -2,6 +2,9 @@ import { getValueForStyle } from '../../../../../../src/utils/getValueForStyle.j import { KeySvg } from './KeySvg.jsx' export const KeyItem = ({ registryDataset, symbolRegistry, patternRegistry, mapStyle }) => { + if (!registryDataset.showInKey) { + return null + } return (
diff --git a/plugins/beta/datasets/src/initialise/DatasetsInit.jsx b/plugins/beta/datasets/src/initialise/DatasetsInit.jsx index 418ed7de..0f45026a 100755 --- a/plugins/beta/datasets/src/initialise/DatasetsInit.jsx +++ b/plugins/beta/datasets/src/initialise/DatasetsInit.jsx @@ -49,8 +49,8 @@ export function DatasetsInit ({ pluginConfig, pluginState, appState, mapState, m initDatasets() }, [isBaseMapReady, appState.mode]) - useEffect(() => datasetRegistry.attach(pluginState.mappedDatasets, pluginState.orderedDatasets), - [pluginState.mappedDatasets, pluginState.orderedDatasets]) + useEffect(() => datasetRegistry.attach(pluginState.mappedDatasets, pluginState.orderedDatasets, mapState.mapStyle), + [pluginState.mappedDatasets, pluginState.orderedDatasets, mapState.mapStyle]) useEffect(() => attachGlobalState(pluginState.globals), [pluginState.globals]) diff --git a/plugins/beta/datasets/src/initialise/initialiseDatasets.js b/plugins/beta/datasets/src/initialise/initialiseDatasets.js index 069c1683..7772866a 100644 --- a/plugins/beta/datasets/src/initialise/initialiseDatasets.js +++ b/plugins/beta/datasets/src/initialise/initialiseDatasets.js @@ -27,7 +27,7 @@ export const initialiseDatasets = ({ if (adapter.createDataset) { datasetRegistry.attachCreateDataset(adapter.createDataset) } - datasetRegistry.attach(mappedDatasets) + datasetRegistry.attach(mappedDatasets, orderedDatasets, mapStyle) adapter.init(mapStyle).then(() => { datasetRegistry.forEachDataset(registryDataset => { if (!registryDataset.hasDynamicGeoJSON) { diff --git a/plugins/beta/datasets/src/registry/dataset.js b/plugins/beta/datasets/src/registry/dataset.js index 14ea0ab7..c3aede2b 100644 --- a/plugins/beta/datasets/src/registry/dataset.js +++ b/plugins/beta/datasets/src/registry/dataset.js @@ -22,12 +22,20 @@ export class Dataset { get parentId () { return this._datasetDefinition.parentId } get minZoom () { return this._datasetDefinition.minZoom || this.parent?.minZoom } get maxZoom () { return this._datasetDefinition.maxZoom || this.parent?.maxZoom } + get showInKey () { const own = this._datasetDefinition.showInKey if (own !== undefined) { return own } return this.parent?.showInKey ?? false } + get visibleWhen () { + if (this._datasetDefinition.visibleWhen === undefined) { + return this.parent?.visibleWhen + } + return this._datasetDefinition.visibleWhen + } + get groupLabel () { return this._datasetDefinition.groupLabel } get opacity () { @@ -84,7 +92,13 @@ export class Dataset { } get styleLayerId () { return this._datasetDefinition.styleLayerId } - get visibility () { return this.visible ? 'visible' : 'none' } + get visibility () { + const visible = this.visible + if (visible && this.visibleWhen?.mapStyleId) { + return this.visibleWhen?.mapStyleId.includes(datasetRegistry.mapStyle.id) ? 'visible' : 'none' + } + return visible ? 'visible' : 'none' + } get symbolAnchor () { if (this.style?.symbolAnchor) { diff --git a/plugins/beta/datasets/src/registry/datasetRegistry.js b/plugins/beta/datasets/src/registry/datasetRegistry.js index a8fcbfd8..5766efb5 100644 --- a/plugins/beta/datasets/src/registry/datasetRegistry.js +++ b/plugins/beta/datasets/src/registry/datasetRegistry.js @@ -2,9 +2,10 @@ import { createDataset } from './createDataset.js' import { DatasetDefinitionCache } from './datasetDefinitionCache.js' const datasetRegistry = { - attach (datasetsRef, orderedDatasetsRef) { + attach (datasetsRef, orderedDatasetsRef, mapStyle) { this._datasets = datasetsRef this._orderedDatasets = orderedDatasetsRef + this._mapStyle = mapStyle this._invalidateChangedDatasets() }, _definitionCache: new DatasetDefinitionCache(), @@ -23,6 +24,10 @@ const datasetRegistry = { attachCreateDataset (createDataset) { this._createDataset = createDataset }, _createDataset: (datasetDefinition) => createDataset(datasetDefinition), + get mapStyle () { + return this._mapStyle + }, + // getDataset retrieves a dataset by id, creating a new Dataset instance that wraps the definition getDataset (id) { const definition = this.datasets[id] From 50cf656d44017a353ee9e3e33fa9c3e6f3a04a53 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 17 Jun 2026 12:16:08 +0100 Subject: [PATCH 04/10] IM-360 refactored to use an esriDataset adapter --- demo/js/esri-datasets.js | 44 +++++++++++++------ .../src/adapters/esri/esriLayerAdapter.js | 38 ++++++++-------- .../src/adapters/esri/registry/esriDataset.js | 16 +++++++ 3 files changed, 65 insertions(+), 33 deletions(-) create mode 100644 plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 8280d061..508296a1 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -17,6 +17,7 @@ const datasets = [ showInKey: true, showInMenu: true, removeStyles: true, + visible: true, sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea CCP1', sublayers: [ { @@ -26,7 +27,6 @@ const datasets = [ showInKey: true, showInMenu: false, style: { - filter: ['==', '_symbol', 0], fill: { outdoor: '#F4A582', dark: '#BF3D4A' }, stroke: 'none' }, @@ -34,16 +34,36 @@ const datasets = [ { id: 'data-unavailable', label: 'Climate change data unavailable', - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + 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' }, + }, showInKey: true, showInMenu: false, + }, + { + id: 'data-unavailable-outline', style: { - filter: ['==', '_symbol', 1], - fillPattern: 'dot', - fillPatternForegroundColor: { outdoor: '#000000', dark: '#ffffff' }, - fill: { outdoor: '#0000ff', dark: '#BF3D4A' }, - stroke: { outdoor: '#000000', dark: '#ffffff' }, + stroke: { outdoor: '#000000', dark: '#FFFFFF' }, }, + styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/0', + showInKey: false, + showInMenu: false, + }, + { + id: 'data-unavailable-light', + visibleWhen: { mapStyleId: ['outdoor', 'black-and-white'] }, + styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + showInKey: false, + showInMenu: false, + }, + { + id: 'data-unavailable-dark', + visibleWhen: { mapStyleId: ['dark'] }, + styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/2', + showInKey: false, + showInMenu: false, } ] }, @@ -64,9 +84,8 @@ const datasets = [ showInKey: true, showInMenu: false, style: { - filter: ['==', 'flood_zone', 'FZ2'], - fill: { outdoor: '#ff0000', dark: '#7fcdbb' }, - // fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, + // filter: ['==', 'flood_zone', 'FZ2'], + fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, stroke: 'none' }, }, @@ -77,9 +96,8 @@ const datasets = [ showInKey: true, showInMenu: false, style: { - filter: ['==', 'flood_zone', 'FZ3'], - fill: { outdoor: '#00ff00', dark: '#e5f5e0' }, - // fill: { outdoor: '#003078', dark: '#e5f5e0' }, + // filter: ['==', 'flood_zone', 'FZ3'], + fill: { outdoor: '#003078', dark: '#e5f5e0' }, stroke: 'none' }, } diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index 63ddd40a..fe2ba159 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -1,6 +1,6 @@ import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js' import { datasetRegistry } from '../../registry/datasetRegistry.js' -import { getValueForStyle } from '../../../../../../src/utils/getValueForStyle.js' +import { EsriDataset } from './registry/esriDataset.js' export default class EsriLayerAdapter { constructor (mapProvider, symbolRegistry, patternRegistry) { @@ -10,16 +10,23 @@ export default class EsriLayerAdapter { this._mapLayers = {} } + createDataset (datasetDefinition) { + return new EsriDataset(datasetDefinition) + } + async init (mapStyle) { // TODO - move some of this into a super LayerAdapter class that this extends - console.log('adding layers') const topLevelDatasets = datasetRegistry.topLevelDatasets() - await Promise.all(topLevelDatasets.map(registryDataset => this._addLayers(registryDataset, mapStyle))) + // ensure the datasets are added in order + for await (const registryDataset of topLevelDatasets) { + await this._addLayers(registryDataset, mapStyle) + } await Promise.all(topLevelDatasets.map(registryDataset => this.applyDatasetVisibility(registryDataset.id))) + console.log(this._map.layers.items.map(layer => layer.id)) } async _addLayers (registryDataset, mapStyle) { - console.log('Creating Esri VectorTileLayer for dataset', registryDataset.id) + console.log('Adding VectorTileLayer for dataset', registryDataset.id) const vectorTileLayer = new VectorTileLayer({ id: registryDataset.id, url: registryDataset.tiles, @@ -27,28 +34,20 @@ export default class EsriLayerAdapter { visible: false }) await vectorTileLayer.load() - + console.log('VectorTileLayer loaded for dataset', registryDataset.id) registryDataset.sublayers.forEach(sublayer => { - const { styleLayerId, style, hasStroke, hasFill } = sublayer + const { styleLayerId } = sublayer if (!styleLayerId) { return } const layerPaintProperties = vectorTileLayer.getPaintProperties(styleLayerId) if (layerPaintProperties) { - console.log('VectorTileLayer styleLayer paint properties', styleLayerId, layerPaintProperties) - if (hasStroke) { - layerPaintProperties['line-color'] = getValueForStyle(style.stroke, mapStyle.id) - } - if (hasFill) { - layerPaintProperties['fill-color'] = getValueForStyle(style.fill, mapStyle.id) - } - console.log('VectorTileLayer updated paint properties', styleLayerId, layerPaintProperties) - vectorTileLayer.setPaintProperties(styleLayerId, layerPaintProperties) + vectorTileLayer.setPaintProperties(styleLayerId, sublayer.applyLayerPaintProperties(layerPaintProperties)) } }) this._mapLayers[registryDataset.id] = vectorTileLayer this._map.add(vectorTileLayer) - console.log('finished adding layers') + console.log('Added VectorTileLayer for dataset', registryDataset.id) return vectorTileLayer.when() } @@ -65,13 +64,12 @@ export default class EsriLayerAdapter { } async applyDatasetVisibility (datasetId) { - console.log('ESRI: applyDatasetVisibility', datasetId) const registryDataset = datasetRegistry.getDataset(datasetId) const { id, isSublayer, visible } = registryDataset if (isSublayer) { const { parent, styleLayerId } = registryDataset const vectorTileLayer = this._mapLayers[parent.id] - console.log('SUBLAYER setStyleLayerVisibility', vectorTileLayer.id, styleLayerId) + // console.log('SUBLAYER setStyleLayerVisibility', vectorTileLayer.id, styleLayerId) vectorTileLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) return } else if (visible) { @@ -81,7 +79,7 @@ export default class EsriLayerAdapter { if (!styleLayerId) { return } - console.log('setStyleLayerVisibility', datasetId, styleLayerId) + // console.log('setStyleLayerVisibility', datasetId, styleLayerId) vectorTileLayer.setStyleLayerVisibility(styleLayerId, sublayer.visibility) }) } @@ -97,7 +95,7 @@ export default class EsriLayerAdapter { } async applyGlobalOpacity (...args) { - console.log('ESRI: applyGlobalOpacity', args) + // console.log('ESRI: applyGlobalOpacity', args) } async addDataset (...args) { diff --git a/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js b/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js new file mode 100644 index 00000000..304921d4 --- /dev/null +++ b/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js @@ -0,0 +1,16 @@ +import { Dataset } from '../../../registry/dataset.js' +import { datasetRegistry } from '../../../registry/datasetRegistry.js' +import { getValueForStyle } from '../../../../../../../src/utils/getValueForStyle.js' + +export class EsriDataset extends Dataset { + applyLayerPaintProperties (layerPaintProperties) { + const { mapStyle } = datasetRegistry + if (this.hasStroke) { + layerPaintProperties['line-color'] = getValueForStyle(this.style.stroke, mapStyle.id) + } + if (this.hasFill) { + layerPaintProperties['fill-color'] = getValueForStyle(this.style.fill, mapStyle.id) + } + return layerPaintProperties + } +} From eb2809998fae4320d01ac2be1953af8776c7192e Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 17 Jun 2026 14:54:47 +0100 Subject: [PATCH 05/10] IM-360 map style change working --- demo/js/esri-datasets.js | 8 +-- .../src/adapters/esri/esriLayerAdapter.js | 69 ++++++++++--------- .../datasets/src/registry/datasetRegistry.js | 10 ++- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 508296a1..0893e7b9 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -12,17 +12,17 @@ const datasets = [ id: 'flood-zones-cc', label: 'Flood Zones Climate Change', groupLabel: 'Datasets', - groupId: 'flood-zones', + esriGroupId: 'flood-zones', tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_CCP1_NON_PRODUCTION/VectorTileServer`, showInKey: true, showInMenu: true, - removeStyles: true, visible: true, sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea CCP1', sublayers: [ { id: 'climate-change', label: 'Climate change (2070 to 2125)', + // TODO change styleLayerId to esriStyleLayerId styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', showInKey: true, showInMenu: false, @@ -71,7 +71,7 @@ const datasets = [ id: 'flood-zones', label: 'Flood Zones', groupLabel: 'Datasets', - groupId: 'flood-zones', + esriGroupId: 'flood-zones', tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_NON_PRODUCTION/VectorTileServer`, showInKey: true, showInMenu: true, @@ -81,7 +81,6 @@ const datasets = [ id: 'flood-zone-2', label: 'Flood Zone 2', styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1', - showInKey: true, showInMenu: false, style: { // filter: ['==', 'flood_zone', 'FZ2'], @@ -93,7 +92,6 @@ const datasets = [ id: 'flood-zone-3', label: 'Flood Zone 3', styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1', - showInKey: true, showInMenu: false, style: { // filter: ['==', 'flood_zone', 'FZ3'], diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index fe2ba159..65faac99 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -21,12 +21,13 @@ export default class EsriLayerAdapter { for await (const registryDataset of topLevelDatasets) { await this._addLayers(registryDataset, mapStyle) } + // Handles showing and hiding sublayers based on the mapStyle + // and updating the paint properties of the layers based on the dataset/mapStyle style + await this.onMapStyleChange(datasetRegistry.mapStyle, null) await Promise.all(topLevelDatasets.map(registryDataset => this.applyDatasetVisibility(registryDataset.id))) - console.log(this._map.layers.items.map(layer => layer.id)) } async _addLayers (registryDataset, mapStyle) { - console.log('Adding VectorTileLayer for dataset', registryDataset.id) const vectorTileLayer = new VectorTileLayer({ id: registryDataset.id, url: registryDataset.tiles, @@ -34,20 +35,8 @@ export default class EsriLayerAdapter { visible: false }) await vectorTileLayer.load() - console.log('VectorTileLayer loaded for dataset', registryDataset.id) - registryDataset.sublayers.forEach(sublayer => { - const { styleLayerId } = sublayer - if (!styleLayerId) { - return - } - const layerPaintProperties = vectorTileLayer.getPaintProperties(styleLayerId) - if (layerPaintProperties) { - vectorTileLayer.setPaintProperties(styleLayerId, sublayer.applyLayerPaintProperties(layerPaintProperties)) - } - }) this._mapLayers[registryDataset.id] = vectorTileLayer this._map.add(vectorTileLayer) - console.log('Added VectorTileLayer for dataset', registryDataset.id) return vectorTileLayer.when() } @@ -63,27 +52,25 @@ export default class EsriLayerAdapter { console.log('ESRI: applyStyle', args) } + _applyStyleLayerVisibility (sublayer, vectorTileLayer) { + const { styleLayerId } = sublayer + if (!styleLayerId) { + return + } + vectorTileLayer.setStyleLayerVisibility(styleLayerId, sublayer.visibility) + } + async applyDatasetVisibility (datasetId) { const registryDataset = datasetRegistry.getDataset(datasetId) - const { id, isSublayer, visible } = registryDataset + const { id, isSublayer, visible, parentId } = registryDataset + const vectorTileLayer = this._mapLayers[isSublayer ? parentId : id] if (isSublayer) { - const { parent, styleLayerId } = registryDataset - const vectorTileLayer = this._mapLayers[parent.id] - // console.log('SUBLAYER setStyleLayerVisibility', vectorTileLayer.id, styleLayerId) - vectorTileLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) + this._applyStyleLayerVisibility(registryDataset, vectorTileLayer) return } else if (visible) { - const vectorTileLayer = this._mapLayers[datasetId] - registryDataset.sublayers.forEach(sublayer => { - const { styleLayerId } = sublayer - if (!styleLayerId) { - return - } - // console.log('setStyleLayerVisibility', datasetId, styleLayerId) - vectorTileLayer.setStyleLayerVisibility(styleLayerId, sublayer.visibility) - }) + registryDataset.sublayers.forEach(sublayer => this._applyStyleLayerVisibility(sublayer, vectorTileLayer)) } - this._mapLayers[id].visible = registryDataset.visible + this._mapLayers[id].visible = visible } async applyGlobalVisibility (...args) { @@ -106,8 +93,28 @@ export default class EsriLayerAdapter { console.log('ESRI: applyFeatureFilter', args) } - async onMapStyleChange (...args) { - console.log('ESRI: onMapStyleChange', args) + async onMapStyleChange (newMapStyle, dynamicSources) { + if (datasetRegistry.mapStyle.id !== newMapStyle.id) { + // Ensure the datasetRegistry is aware of the new mapStyle so that the visibility and style properties of the datasets can be correctly applied + // TODO - this is a bit of a hack, we should probably have a better way to handle this + // such as having DatasetsInit listen for a mapStyle change event and then call datasetRegistry.attach with the new mapStyle + // and finally trigger this method + datasetRegistry.attach(datasetRegistry.datasets, datasetRegistry._orderedDatasets, newMapStyle) + } + datasetRegistry.forEach(registryDataset => { + const { id, isSublayer, styleLayerId, parent } = registryDataset + const vectorTileLayer = this._mapLayers[isSublayer ? parent.id : id] + if (vectorTileLayer && styleLayerId) { + // Show hide the style layer based on the dataset's mapStyle visibility + vectorTileLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) + // Update the paint properties of the style layer based on the dataset's mapStyle style + const layerPaintProperties = vectorTileLayer.getPaintProperties(styleLayerId) + if (layerPaintProperties) { + vectorTileLayer.setPaintProperties(styleLayerId, registryDataset.applyLayerPaintProperties(layerPaintProperties)) + } + } + }) + // TODO - handle dynamic sources } async onMapSizeChange (...args) { diff --git a/plugins/beta/datasets/src/registry/datasetRegistry.js b/plugins/beta/datasets/src/registry/datasetRegistry.js index 5766efb5..f7939a58 100644 --- a/plugins/beta/datasets/src/registry/datasetRegistry.js +++ b/plugins/beta/datasets/src/registry/datasetRegistry.js @@ -5,20 +5,26 @@ const datasetRegistry = { attach (datasetsRef, orderedDatasetsRef, mapStyle) { this._datasets = datasetsRef this._orderedDatasets = orderedDatasetsRef - this._mapStyle = mapStyle + if (mapStyle) { + this._mapStyle = mapStyle + } this._invalidateChangedDatasets() }, + _definitionCache: new DatasetDefinitionCache(), + _invalidateCache () { // used in tests to clear the cache between runs this._definitionCache = new DatasetDefinitionCache() }, - _invalidateChangedDatasets () { // used in tests to clear the cache between runs + + _invalidateChangedDatasets () { if (this._datasets) { this._definitionCache.invalidateChangedDatasets(Object.values(this._datasets)) } else { this._invalidateCache() } }, + // createDataset defaults to a generic dataset factory function, but can be overridden by calling // attachCreateDataset, which allows the layer adapter to provide its own createDataset function, attachCreateDataset (createDataset) { this._createDataset = createDataset }, From 6252b7a9cb0117b9d1fb89ff9e3b50ce997534f1 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 17 Jun 2026 16:57:22 +0100 Subject: [PATCH 06/10] IM-360 replaced styleLayerId with esriStyleLayerId --- demo/js/esri-datasets.js | 19 +++++++++---------- .../src/adapters/esri/esriLayerAdapter.js | 16 ++++++++-------- .../datasets/src/reducers/datasetsToMenu.js | 18 +++++++++++++++--- plugins/beta/datasets/src/registry/dataset.js | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 0893e7b9..2c48e206 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -22,8 +22,7 @@ const datasets = [ { id: 'climate-change', label: 'Climate change (2070 to 2125)', - // TODO change styleLayerId to esriStyleLayerId - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Flood Zones plus climate change/1', showInKey: true, showInMenu: false, style: { @@ -47,21 +46,21 @@ const datasets = [ style: { stroke: { outdoor: '#000000', dark: '#FFFFFF' }, }, - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/0', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/0', showInKey: false, showInMenu: false, }, { id: 'data-unavailable-light', visibleWhen: { mapStyleId: ['outdoor', 'black-and-white'] }, - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', showInKey: false, showInMenu: false, }, { id: 'data-unavailable-dark', visibleWhen: { mapStyleId: ['dark'] }, - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/2', + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/2', showInKey: false, showInMenu: false, } @@ -74,14 +73,14 @@ const datasets = [ esriGroupId: 'flood-zones', tiles: `https://tiles.arcgis.com/tiles/JZM7qJpmv7vJ0Hzx/arcgis/rest/services/Flood_Zones_2_and_3_Rivers_and_Sea_NON_PRODUCTION/VectorTileServer`, showInKey: true, - showInMenu: true, + // showInMenu: true, sourceLayer: 'Flood Zones 2 and 3 Rivers and Sea', sublayers: [ { id: 'flood-zone-2', label: 'Flood Zone 2', - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1', - showInMenu: false, + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1', + showInMenu: true, style: { // filter: ['==', 'flood_zone', 'FZ2'], fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, @@ -91,8 +90,8 @@ const datasets = [ { id: 'flood-zone-3', label: 'Flood Zone 3', - styleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1', - showInMenu: false, + esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1', + showInMenu: true, style: { // filter: ['==', 'flood_zone', 'FZ3'], fill: { outdoor: '#003078', dark: '#e5f5e0' }, diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index 65faac99..e1d64eb5 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -53,11 +53,11 @@ export default class EsriLayerAdapter { } _applyStyleLayerVisibility (sublayer, vectorTileLayer) { - const { styleLayerId } = sublayer - if (!styleLayerId) { + const { esriStyleLayerId } = sublayer + if (!esriStyleLayerId) { return } - vectorTileLayer.setStyleLayerVisibility(styleLayerId, sublayer.visibility) + vectorTileLayer.setStyleLayerVisibility(esriStyleLayerId, sublayer.visibility) } async applyDatasetVisibility (datasetId) { @@ -102,15 +102,15 @@ export default class EsriLayerAdapter { datasetRegistry.attach(datasetRegistry.datasets, datasetRegistry._orderedDatasets, newMapStyle) } datasetRegistry.forEach(registryDataset => { - const { id, isSublayer, styleLayerId, parent } = registryDataset + const { id, isSublayer, esriStyleLayerId, parent } = registryDataset const vectorTileLayer = this._mapLayers[isSublayer ? parent.id : id] - if (vectorTileLayer && styleLayerId) { + if (vectorTileLayer && esriStyleLayerId) { // Show hide the style layer based on the dataset's mapStyle visibility - vectorTileLayer.setStyleLayerVisibility(styleLayerId, registryDataset.visibility) + vectorTileLayer.setStyleLayerVisibility(esriStyleLayerId, registryDataset.visibility) // Update the paint properties of the style layer based on the dataset's mapStyle style - const layerPaintProperties = vectorTileLayer.getPaintProperties(styleLayerId) + const layerPaintProperties = vectorTileLayer.getPaintProperties(esriStyleLayerId) if (layerPaintProperties) { - vectorTileLayer.setPaintProperties(styleLayerId, registryDataset.applyLayerPaintProperties(layerPaintProperties)) + vectorTileLayer.setPaintProperties(esriStyleLayerId, registryDataset.applyLayerPaintProperties(layerPaintProperties)) } } }) diff --git a/plugins/beta/datasets/src/reducers/datasetsToMenu.js b/plugins/beta/datasets/src/reducers/datasetsToMenu.js index fc6521e2..52307929 100644 --- a/plugins/beta/datasets/src/reducers/datasetsToMenu.js +++ b/plugins/beta/datasets/src/reducers/datasetsToMenu.js @@ -10,8 +10,10 @@ export const datasetsToMenu = (state) => { const visibleSublayers = dataset.sublayers.filter(sublayer => dataset.showInMenu ? sublayer.showInMenu !== false : sublayer.showInMenu ) - if (!visibleSublayers.length) { return } - menu.push({ + if (dataset.showInMenu === false && !visibleSublayers.length) { + return + } + const groupObject = { id: dataset.id, groupLabel: dataset.label, visibleWhen: true, @@ -20,7 +22,17 @@ export const datasetsToMenu = (state) => { id: `${dataset.id}-${sublayer.id}`, label: sublayer.label })) - }) + } + // Temporary change until we handle multiple datasets with sublayers, that have a groupLabel + // So that the esri datasets with sublayers are displayed in the menu, even though they have a groupLabel + if (groupObject.items.length === 0) { + delete groupObject.groupLabel + groupObject.items.push({ + id: dataset.id, + label: dataset.label + }) + } + menu.push(groupObject) } else if (dataset.groupLabel) { // Check for existing group object for this groupLabel, or create it if it doesn't exist, // then add the dataset to its items and push it to the menu if it's new diff --git a/plugins/beta/datasets/src/registry/dataset.js b/plugins/beta/datasets/src/registry/dataset.js index c3aede2b..c20c0c81 100644 --- a/plugins/beta/datasets/src/registry/dataset.js +++ b/plugins/beta/datasets/src/registry/dataset.js @@ -91,7 +91,7 @@ export class Dataset { return this._datasetDefinition.visible && getGlobalVisibility() } - get styleLayerId () { return this._datasetDefinition.styleLayerId } + get esriStyleLayerId () { return this._datasetDefinition.esriStyleLayerId } get visibility () { const visible = this.visible if (visible && this.visibleWhen?.mapStyleId) { From f2168317eda8b57503fab4d8a9d6317d8a94ede8 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Wed, 17 Jun 2026 17:10:35 +0100 Subject: [PATCH 07/10] IM-360 implemented global visibility --- demo/js/esri-datasets.js | 11 +++++++++-- .../datasets/src/adapters/esri/esriLayerAdapter.js | 12 +++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index 2c48e206..b144d7b9 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -111,8 +111,6 @@ const datasetsPlugin = createDatasetsPlugin({ datasets }) - - const interactiveMap = new InteractiveMap('map', { behaviour: 'mapOnly', mapProvider: esriProvider({ setupConfig: setupEsriConfig }), @@ -139,4 +137,13 @@ const interactiveMap = new InteractiveMap('map', { } }) ] +}) + +const testGlobalVisibility = () => { + setTimeout(() => datasetsPlugin.setDatasetVisibility(false), 3000) + setTimeout(() => datasetsPlugin.setDatasetVisibility(true), 6000) +} + +interactiveMap.on('datasets:ready', function () { + testGlobalVisibility() }) \ No newline at end of file diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index e1d64eb5..ee3bb32c 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -60,8 +60,7 @@ export default class EsriLayerAdapter { vectorTileLayer.setStyleLayerVisibility(esriStyleLayerId, sublayer.visibility) } - async applyDatasetVisibility (datasetId) { - const registryDataset = datasetRegistry.getDataset(datasetId) + _applyRegistryDatasetVisibility (registryDataset) { const { id, isSublayer, visible, parentId } = registryDataset const vectorTileLayer = this._mapLayers[isSublayer ? parentId : id] if (isSublayer) { @@ -73,8 +72,15 @@ export default class EsriLayerAdapter { this._mapLayers[id].visible = visible } + async applyDatasetVisibility (datasetId) { + const registryDataset = datasetRegistry.getDataset(datasetId) + if (registryDataset) { + this._applyRegistryDatasetVisibility(registryDataset) + } + } + async applyGlobalVisibility (...args) { - console.log('ESRI: applyGlobalVisibility', args) + datasetRegistry.forEachDataset(registryDataset => this._applyRegistryDatasetVisibility(registryDataset)) } async applyDatasetOpacity (...args) { From 9e6a11bb46a91fd765ff7de7e2948d408de87535 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Thu, 18 Jun 2026 16:38:12 +0100 Subject: [PATCH 08/10] IM-360 opacity working for esriDatasets --- demo/js/esri-datasets.js | 4 +- .../src/adapters/esri/esriLayerAdapter.js | 110 ++++++++++++++---- .../src/adapters/esri/registry/esriDataset.js | 11 ++ 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/demo/js/esri-datasets.js b/demo/js/esri-datasets.js index b144d7b9..a8200c8a 100644 --- a/demo/js/esri-datasets.js +++ b/demo/js/esri-datasets.js @@ -54,6 +54,7 @@ const datasets = [ id: 'data-unavailable-light', visibleWhen: { mapStyleId: ['outdoor', 'black-and-white'] }, esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1', + esriUseServerStyle: true, showInKey: false, showInMenu: false, }, @@ -61,6 +62,7 @@ const datasets = [ id: 'data-unavailable-dark', visibleWhen: { mapStyleId: ['dark'] }, esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/2', + esriUseServerStyle: true, showInKey: false, showInMenu: false, } @@ -82,7 +84,6 @@ const datasets = [ esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1', showInMenu: true, style: { - // filter: ['==', 'flood_zone', 'FZ2'], fill: { outdoor: '#1d70b8', dark: '#7fcdbb' }, stroke: 'none' }, @@ -93,7 +94,6 @@ const datasets = [ esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1', showInMenu: true, style: { - // filter: ['==', 'flood_zone', 'FZ3'], fill: { outdoor: '#003078', dark: '#e5f5e0' }, stroke: 'none' }, diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index ee3bb32c..4a9c5942 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -1,4 +1,5 @@ import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer.js' +import GroupLayer from '@arcgis/core/layers/GroupLayer.js' import { datasetRegistry } from '../../registry/datasetRegistry.js' import { EsriDataset } from './registry/esriDataset.js' @@ -7,7 +8,19 @@ export default class EsriLayerAdapter { this._mapProvider = mapProvider this._map = mapProvider.map // TODO: Implement symbolRegistry and patternRegistry usage in the adapter - this._mapLayers = {} + + // _vectorTileLayers is a map of datasetId to VectorTileLayer instances + // it includes stand alone vectorTileLayers and vectorTileLayers that are part of a groupLayer + // but does not include group layers themselves, which are tracked in _groupLayers + this._vectorTileLayers = {} + + // _vectorTileOpacityLayers is a map of datasetId to VectorTileLayer/GroupLayer where opacity is applied + // it includes stand alone vectorTileLayers and groupLayers that contain vectorTileLayers + // but does not include vectorTileLayers that are part of a groupLayer + this._vectorTileOpacityLayers = {} + + // _groupLayers is a map of esriGroupId to GroupLayer + this._groupLayers = {} } createDataset (datasetDefinition) { @@ -15,41 +28,78 @@ export default class EsriLayerAdapter { } async init (mapStyle) { - // TODO - move some of this into a super LayerAdapter class that this extends const topLevelDatasets = datasetRegistry.topLevelDatasets() // ensure the datasets are added in order for await (const registryDataset of topLevelDatasets) { await this._addLayers(registryDataset, mapStyle) } - // Handles showing and hiding sublayers based on the mapStyle + + // onMapStyleChange: handles showing and hiding sublayers based on the mapStyle // and updating the paint properties of the layers based on the dataset/mapStyle style await this.onMapStyleChange(datasetRegistry.mapStyle, null) + + // Apply opacity to all layers + await this.applyGlobalOpacity() + + // Finally show all layers that are visible based on the dataset/mapStyle visibility await Promise.all(topLevelDatasets.map(registryDataset => this.applyDatasetVisibility(registryDataset.id))) + + // Log the layers for debugging + // this.logLayers('_vectorTileLayers') + // console.log('this._vectorTileOpacityLayers', this._vectorTileOpacityLayers) + // console.log('this._groupLayers', this._groupLayers) + } + + logLayers (name) { + const obj = this[name] + console.log(`${name}:`) + Object.entries(obj).forEach(([datasetId, layer]) => { + console.log(` ${datasetId}`) + layer.styleRepository.layers.forEach(styleLayer => { + console.log(` ${styleLayer.id}`) + }) + }) + } + + _addGroupLayer (esriGroupId) { + // Either adds a new group layer to the map, or returns an existing one if it already exists + if (!this._groupLayers[esriGroupId]) { + const groupLayer = new GroupLayer({ + id: esriGroupId, + opacity: 1, + visible: true + }) + this._groupLayers[esriGroupId] = groupLayer + this._map.add(groupLayer) + } + return this._groupLayers[esriGroupId] } async _addLayers (registryDataset, mapStyle) { + const { esriGroupId } = registryDataset + const vectorTileParent = esriGroupId ? this._addGroupLayer(esriGroupId) : this._map const vectorTileLayer = new VectorTileLayer({ id: registryDataset.id, url: registryDataset.tiles, opacity: 1, visible: false }) - await vectorTileLayer.load() - this._mapLayers[registryDataset.id] = vectorTileLayer - this._map.add(vectorTileLayer) + this._vectorTileLayers[registryDataset.id] = vectorTileLayer + this._vectorTileOpacityLayers[registryDataset.id] = esriGroupId ? vectorTileParent : vectorTileLayer + vectorTileParent.add(vectorTileLayer) return vectorTileLayer.when() } async removeDataset (...args) { - console.log('ESRI: removeDataset', args) + console.log('TODO: removeDataset', args) } async setData (...args) { - console.log('ESRI: setData', args) + console.log('TODO: setData', args) } async applyStyle (...args) { - console.log('ESRI: applyStyle', args) + console.log('TODO: applyStyle', args) } _applyStyleLayerVisibility (sublayer, vectorTileLayer) { @@ -61,15 +111,20 @@ export default class EsriLayerAdapter { } _applyRegistryDatasetVisibility (registryDataset) { + // if this is a sublayer, we need to apply the visibility to the vectorTileLayers style sheet + // if this is a top level dataset, we need to apply the visibility to the vectorTileLayer/ groupLayer itself const { id, isSublayer, visible, parentId } = registryDataset - const vectorTileLayer = this._mapLayers[isSublayer ? parentId : id] + const vectorTileLayer = this._vectorTileLayers[isSublayer ? parentId : id] + if (isSublayer) { this._applyStyleLayerVisibility(registryDataset, vectorTileLayer) + // Don't apply the visibility change to the parent, since the parent may have other sublayers that are visible return } else if (visible) { + // No need to apply style layer visibility for datasets that are hidden registryDataset.sublayers.forEach(sublayer => this._applyStyleLayerVisibility(sublayer, vectorTileLayer)) } - this._mapLayers[id].visible = visible + vectorTileLayer.visible = visible } async applyDatasetVisibility (datasetId) { @@ -79,24 +134,33 @@ export default class EsriLayerAdapter { } } - async applyGlobalVisibility (...args) { + async applyGlobalVisibility () { datasetRegistry.forEachDataset(registryDataset => this._applyRegistryDatasetVisibility(registryDataset)) } - async applyDatasetOpacity (...args) { - console.log('ESRI: applyDatasetOpacity', args) + async applyDatasetOpacity (datasetId) { + const vectorTileLayer = this._vectorTileOpacityLayers[datasetId] + const registryDataset = datasetRegistry.getDataset(datasetId) + if (vectorTileLayer && registryDataset) { + vectorTileLayer.opacity = registryDataset.opacity + } } async applyGlobalOpacity (...args) { - // console.log('ESRI: applyGlobalOpacity', args) + Object.entries(this._vectorTileOpacityLayers).forEach(([datasetId, vectorTileLayer]) => { + const registryDataset = datasetRegistry.getDataset(datasetId) + if (registryDataset) { + vectorTileLayer.opacity = registryDataset.opacity + } + }) } async addDataset (...args) { - console.log('ESRI: addDataset', args) + console.log('TODO: addDataset', args) } async applyFeatureFilter (...args) { - console.log('ESRI: applyFeatureFilter', args) + console.log('TODO: applyFeatureFilter', args) } async onMapStyleChange (newMapStyle, dynamicSources) { @@ -109,13 +173,18 @@ export default class EsriLayerAdapter { } datasetRegistry.forEach(registryDataset => { const { id, isSublayer, esriStyleLayerId, parent } = registryDataset - const vectorTileLayer = this._mapLayers[isSublayer ? parent.id : id] + const vectorTileLayer = this._vectorTileLayers[isSublayer ? parent.id : id] if (vectorTileLayer && esriStyleLayerId) { // Show hide the style layer based on the dataset's mapStyle visibility vectorTileLayer.setStyleLayerVisibility(esriStyleLayerId, registryDataset.visibility) + if (registryDataset.useServerStyle) { + // If the dataset is using the server style, we don't need to apply any paint properties + return + } // Update the paint properties of the style layer based on the dataset's mapStyle style const layerPaintProperties = vectorTileLayer.getPaintProperties(esriStyleLayerId) if (layerPaintProperties) { + registryDataset.applyLayerPaintProperties(layerPaintProperties) vectorTileLayer.setPaintProperties(esriStyleLayerId, registryDataset.applyLayerPaintProperties(layerPaintProperties)) } } @@ -123,7 +192,6 @@ export default class EsriLayerAdapter { // TODO - handle dynamic sources } - async onMapSizeChange (...args) { - console.log('ESRI: onMapSizeChange', args) - } + // onMapSizeChange is not applicable to the esriLayerAdapter + async onMapSizeChange (_mapStyle) {} } diff --git a/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js b/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js index 304921d4..e3466716 100644 --- a/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js +++ b/plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js @@ -13,4 +13,15 @@ export class EsriDataset extends Dataset { } return layerPaintProperties } + + get esriGroupId () { + if (this._datasetDefinition.esriGroupId === undefined) { + return this.parent?.esriGroupId + } + return this._datasetDefinition.esriGroupId + } + + get useServerStyle () { + return Boolean(this._datasetDefinition.esriUseServerStyle) + } } From 9a4cf6dee0cd95144b4b44b3d5dfaf957b2ded2f Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Thu, 18 Jun 2026 17:01:49 +0100 Subject: [PATCH 09/10] IM-360 added comment to visibleWhen, removed unused ...args --- plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js | 2 +- plugins/beta/datasets/src/registry/dataset.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js index 4a9c5942..05e36b01 100644 --- a/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js @@ -146,7 +146,7 @@ export default class EsriLayerAdapter { } } - async applyGlobalOpacity (...args) { + async applyGlobalOpacity () { Object.entries(this._vectorTileOpacityLayers).forEach(([datasetId, vectorTileLayer]) => { const registryDataset = datasetRegistry.getDataset(datasetId) if (registryDataset) { diff --git a/plugins/beta/datasets/src/registry/dataset.js b/plugins/beta/datasets/src/registry/dataset.js index c20c0c81..dce6f78f 100644 --- a/plugins/beta/datasets/src/registry/dataset.js +++ b/plugins/beta/datasets/src/registry/dataset.js @@ -29,6 +29,9 @@ export class Dataset { return this.parent?.showInKey ?? false } + // Note visibleWhen is used in combination with visible. + // both must be true for the dataset to be visible. + // visibleWhen is used to show/hide datasets based on mapStyle. get visibleWhen () { if (this._datasetDefinition.visibleWhen === undefined) { return this.parent?.visibleWhen From 60249198e43dedcf168befea1e4338837d16f185 Mon Sep 17 00:00:00 2001 From: Mark Fee Date: Fri, 19 Jun 2026 08:50:07 +0100 Subject: [PATCH 10/10] IM-360 corrected unit test after menu change --- plugins/beta/datasets/src/reducers/datasetsToMenu.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js b/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js index 3da636b1..3c3a68a5 100644 --- a/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js +++ b/plugins/beta/datasets/src/reducers/datasetsToMenu.test.js @@ -76,9 +76,9 @@ describe('datasetsToMenu', () => { expect(result[0].items).toEqual([{ id: 'test-a', label: 'A' }]) }) - it('does not add a menu entry when dataset has showInMenu: true but all sublayers opt out', () => { + it('Should have a single menu entry for the parent dataset when showInMenu: true but all sublayers opt out', () => { const result = datasetsToMenu({ datasets: [sublayerDataset(true, false)] }) - expect(result).toHaveLength(0) + expect(result).toEqual([{ id: 'test', items: [{ id: 'test', label: 'Test' }], type: 'checkbox', visibleWhen: true }]) }) }) })