Skip to content
85 changes: 76 additions & 9 deletions demo/js/esri-datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,81 @@ 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',
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,
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,
showInMenu: false,
style: {
fill: { outdoor: '#F4A582', dark: '#BF3D4A' },
stroke: 'none'
},
},
{
id: 'data-unavailable',
label: 'Climate change data unavailable',
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: {
stroke: { outdoor: '#000000', dark: '#FFFFFF' },
},
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'] },
esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea CCP1/Unavailable/1',
esriUseServerStyle: true,
showInKey: false,
showInMenu: false,
},
{
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,
}
]
},
{
id: 'flood-zones',
label: 'Flood Zones',
groupLabel: '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',
showInKey: true,
showInMenu: false,
esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 2/1',
showInMenu: true,
style: {
fill: { outdoor: '#1d70b8', dark: '#7fcdbb' },
stroke: 'none'
Expand All @@ -30,9 +91,8 @@ 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,
esriStyleLayerId: 'Flood Zones 2 and 3 Rivers and Sea/Flood Zone 3/1',
showInMenu: true,
style: {
fill: { outdoor: '#003078', dark: '#e5f5e0' },
stroke: 'none'
Expand All @@ -51,8 +111,6 @@ const datasetsPlugin = createDatasetsPlugin({
datasets
})



const interactiveMap = new InteractiveMap('map', {
behaviour: 'mapOnly',
mapProvider: esriProvider({ setupConfig: setupEsriConfig }),
Expand All @@ -79,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()
})
174 changes: 151 additions & 23 deletions plugins/beta/datasets/src/adapters/esri/esriLayerAdapter.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,197 @@
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'

export default class EsriLayerAdapter {
constructor (mapProvider, symbolRegistry, patternRegistry) {
this._mapProvider = mapProvider
this._map = mapProvider.map
// TODO: Implement symbolRegistry and patternRegistry usage in the adapter

// _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) {
return new EsriDataset(datasetDefinition)
}

async init (mapStyle) {
console.log('EsriLayerAdapter init', mapStyle)
datasetRegistry.forEachDataset(registryDataset => this._addLayers(registryDataset, mapStyle))
const topLevelDatasets = datasetRegistry.topLevelDatasets()
// ensure the datasets are added in order
for await (const registryDataset of topLevelDatasets) {
await this._addLayers(registryDataset, 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]
}

_addLayers (registryDataset, mapStyle) {
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: registryDataset.visible
visible: false
})
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)
}

async applyDatasetVisibility (...args) {
console.log('ESRI: applyDatasetVisibility', args)
_applyStyleLayerVisibility (sublayer, vectorTileLayer) {
const { esriStyleLayerId } = sublayer
if (!esriStyleLayerId) {
return
}
vectorTileLayer.setStyleLayerVisibility(esriStyleLayerId, sublayer.visibility)
}

_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._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))
}
vectorTileLayer.visible = visible
}

async applyGlobalVisibility (...args) {
console.log('ESRI: applyGlobalVisibility', args)
async applyDatasetVisibility (datasetId) {
const registryDataset = datasetRegistry.getDataset(datasetId)
if (registryDataset) {
this._applyRegistryDatasetVisibility(registryDataset)
}
}

async applyDatasetOpacity (...args) {
console.log('ESRI: applyDatasetOpacity', args)
async applyGlobalVisibility () {
datasetRegistry.forEachDataset(registryDataset => this._applyRegistryDatasetVisibility(registryDataset))
}

async applyGlobalOpacity (...args) {
console.log('ESRI: applyGlobalOpacity', args)
async applyDatasetOpacity (datasetId) {
const vectorTileLayer = this._vectorTileOpacityLayers[datasetId]
const registryDataset = datasetRegistry.getDataset(datasetId)
if (vectorTileLayer && registryDataset) {
vectorTileLayer.opacity = registryDataset.opacity
}
}

async applyGlobalOpacity () {
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 (...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, esriStyleLayerId, parent } = registryDataset
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))
}
}
})
// TODO - handle dynamic sources
}

async onMapSizeChange (...args) {
console.log('ESRI: onMapSizeChange', args)
}
// onMapSizeChange is not applicable to the esriLayerAdapter
async onMapSizeChange (_mapStyle) {}
}
27 changes: 27 additions & 0 deletions plugins/beta/datasets/src/adapters/esri/registry/esriDataset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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
}

get esriGroupId () {
if (this._datasetDefinition.esriGroupId === undefined) {
return this.parent?.esriGroupId
}
return this._datasetDefinition.esriGroupId
}

get useServerStyle () {
return Boolean(this._datasetDefinition.esriUseServerStyle)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions plugins/beta/datasets/src/components/Key/KeyItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<dl className='im-c-datasets-key__item'>
<dt className='im-c-datasets-key__item-symbol'>
Expand Down
Loading
Loading