Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions demo/js/ml-datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const landCoversDataset = {
label: 'Permanent grassland 2',
filter: ['in', ['get', 'dominant_land_cover'], ['literal', ['130', '131']]], // 'dominant_land_cover = "130"'
showInMenu: true,
visible: false,
style: {
stroke: { outdoor: '#00897B', dark: '#ffffff' },
fillPattern: 'diagonal-cross-hatch',
Expand Down Expand Up @@ -299,7 +300,11 @@ const hedgeControlDataset = {

const datasetsPlugin = createDatasetsPlugin({
layerAdapter: maplibreLayerAdapter,
// Example: Dynamic bbox-based fetching (uncomment to test)
globals: {
opacityMode: 'multiply', // 'dataset', 'global' or 'multiply'
opacity: 0.75,
visible: true
},
datasets: [
landCoversDataset,
existingFieldsDataset,
Expand Down Expand Up @@ -368,13 +373,16 @@ const testVisibility = () => {
setTimeout(() => datasetsPlugin.setDatasetVisibility(true, { datasetId: 'land-covers' }), 4000)
// now reshow show landcovers-130-131
setTimeout(() => datasetsPlugin.setDatasetVisibility(true, { datasetId: 'land-covers', sublayerId: '130-131' }), 5000)

// TODO
// setTimeout(() => datasetsPlugin.setDatasetVisibility(false, { rememberOriginalValues: false }), 5000)
}

const testGlobalVisibility = () => {
setTimeout(() => datasetsPlugin.setDatasetVisibility(false), 1000)
setTimeout(() => datasetsPlugin.setDatasetVisibility(true), 5000)
setTimeout(() => datasetsPlugin.setDatasetVisibility(true, { datasetId: 'hedge-control' }), 500)
setTimeout(() => datasetsPlugin.setStyle({ stroke: { outdoor: '#0000ff' }, }, { datasetId: 'hedge-control' }), 2000)
setTimeout(() => datasetsPlugin.setDatasetVisibility(true), 2000)
// setTimeout(() => datasetsPlugin.setDatasetVisibility(true, { datasetId: 'hedge-control' }), 500)
// setTimeout(() => datasetsPlugin.setStyle({ stroke: { outdoor: '#0000ff' }, }, { datasetId: 'hedge-control' }), 2000)
}

const testFeatureVisibility = () => {
Expand All @@ -385,13 +393,18 @@ const testFeatureVisibility = () => {
setTimeout(() => datasetsPlugin.setFeatureVisibility(false, [16], { datasetId: 'land-covers-permanent-grassland-2', idProperty: null }), 2000)
}

const testOpacity = () => {
setTimeout(() => datasetsPlugin.setOpacity(0.5, { datasetId: 'land-covers', sublayerId: '130-131' }), 500)
setTimeout(() => datasetsPlugin.setOpacity(0, { datasetId: 'land-covers' }), 500)
setTimeout(() => datasetsPlugin.setOpacity(0.8, { datasetId: 'land-covers', sublayerId: '130-131' }), 1000)
setTimeout(() => datasetsPlugin.setOpacity(0.3, { datasetId: 'land-covers', sublayerId: '130-131' }), 1500)
setTimeout(() => datasetsPlugin.setOpacity(0.97, { datasetId: 'land-covers' }), 2000)
// setTimeout(() => datasetsPlugin.setOpacity(1, { datasetId: 'land-covers', sublayerId: '130-131' }), 4000)
const testSetOpacity = () => {
setTimeout(() => datasetsPlugin.setOpacity(0.8, { datasetId: 'land-covers' }), 500)
setTimeout(() => datasetsPlugin.setOpacity(0.2, { datasetId: 'land-covers', sublayerId: '130-131' }), 2000)
// setTimeout(() => datasetsPlugin.setOpacity(0.8, { datasetId: 'land-covers', sublayerId: '130-131' }), 2000)
// setTimeout(() => datasetsPlugin.setOpacity(0.3, { datasetId: 'land-covers', sublayerId: '130-131' }), 2500)
setTimeout(() => datasetsPlugin.setOpacity(0.97, { datasetId: 'land-covers' }), 4000)
// setTimeout(() => datasetsPlugin.setOpacity(1, { datasetId: 'land-covers', sublayerId: '130-131' }), 6000)

setTimeout(() => datasetsPlugin.setOpacity(0), 8000)
setTimeout(() => datasetsPlugin.setOpacity(1), 10000)
// TODO:
// setTimeout(() => datasetsPlugin.setGlobal({ opacityMode: 'multiply' }), 2000)
}

const testSetStyle = () => {
Expand Down Expand Up @@ -459,15 +472,15 @@ const testSetData = () => {
}

interactiveMap.on('datasets:ready', function () {
testGetters()
testInvalidApiCalls()
testFeatureVisibility()
testOpacity()
testSetStyle()
testVisibility()
testGlobalVisibility()
testRemoveAndAddDataset()
testSetData()
// testGetters()
// testInvalidApiCalls()
// testFeatureVisibility()
testSetOpacity()
// testSetStyle()
// testVisibility()
// testGlobalVisibility()
// testRemoveAndAddDataset()
// testSetData()
})

// Ref to the selected features
Expand Down
9 changes: 8 additions & 1 deletion plugins/beta/datasets/src/DatasetsInit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useRef } from 'react'
import { EVENTS } from '../../../../src/config/events.js'
import { createDatasets } from './datasets.js'
import { datasetRegistry } from './registry/datasetRegistry.js'
import { attachGlobalState } from './registry/globalDataset.js'

const useLayerAdapterActions = (methodName, dispatch, pluginState, dependencies) =>
useEffect(() => {
Expand Down Expand Up @@ -76,9 +77,15 @@ export function DatasetsInit ({ pluginConfig, pluginState, appState, mapState, m
useEffect(() => {
datasetRegistry.attach(datasetsRef.current, pluginState.orderedDatasets)
}, [pluginState.mappedDatasets, pluginState.orderedDatasets])

useEffect(() => {
attachGlobalState(pluginState.globals)
}, [pluginState.globals])

useLayerAdapterActions('applyStyle', dispatch, pluginState, [pluginState.layerAdapterActions.applyStyle])
useLayerAdapterActions('applyDatasetVisibility', dispatch, pluginState, [pluginState.layerAdapterActions.applyDatasetVisibility])
useLayerAdapterActions('setOpacity', dispatch, pluginState, [pluginState.layerAdapterActions.setOpacity])
useLayerAdapterActions('applyDatasetOpacity', dispatch, pluginState, [pluginState.layerAdapterActions.applyDatasetOpacity])
useLayerAdapterActions('applyGlobalOpacity', dispatch, pluginState, [pluginState.layerAdapterActions.applyGlobalOpacity])
useLayerAdapterActions('addDataset', dispatch, pluginState, [pluginState.layerAdapterActions.addDataset])
useLayerAdapterActions('applyFeatureFilter', dispatch, pluginState, [pluginState.layerAdapterActions.applyFeatureFilter])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,34 @@ export class MapLibreDataset extends Dataset {
return [this.symbolLayerId, this.fillLayerId, this.strokeLayerId].filter(Boolean)
}

getLayersWithFilters () {
getLayersWithValue (valueName, condition = false) {
const response = []
if (this.hasHiddenFeatures) {
if (condition === false || this[condition]) {
const layerIds = [this.symbolLayerId, this.fillLayerId, this.strokeLayerId].filter(Boolean)
const { filter } = this
response.push({ layerIds, filter })
if (layerIds.length) {
const value = this[valueName]
response.push({ layerIds, [valueName]: value })
}
}

if (this.hasSublayers) {
this.sublayers.forEach((sublayer) => {
if (sublayer.hasHiddenFeatures) {
response.push(sublayer.getLayersWithFilters()[0])
if (condition === false || sublayer[condition]) {
response.push(sublayer.getLayersWithValue(valueName)[0])
}
})
}
return response
}

getLayersWithOpacity () {
return this.getLayersWithValue('opacity')
}

getLayersWithFilters () {
return this.getLayersWithValue('filter', 'hasHiddenFeatures')
}

get sourceId () {
if (this.isSublayer) { return this.parent.sourceId }
if (this.hasDynamicGeoJSON) { return this.dynamicGeoJSON.sourceId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,37 @@ describe('MapLibreDataset', () => {
})
})

describe('getLayersWithOpacity', () => {
it('returns layerIds and opacity for a sublayer with no sublayers', () => {
const dataset = datasetRegistry.getDataset('existing-fields')
expect(dataset.getLayersWithOpacity()).toEqual([{ layerIds: ['existing-fields', 'existing-fields-stroke'], opacity: 1 }])
})

it('returns layerIds and opacity for a sublayer', () => {
const dataset = datasetRegistry.getDataset('land-covers-130-131')
const result = dataset.getLayersWithOpacity()
expect(result).toEqual([{ layerIds: ['land-covers-130-131', 'land-covers-130-131-stroke'], opacity: 1 }])
})

it('returns layerIds and opacity for a sublayer with sublayers with specific opacity', () => {
const parentDef = { id: 'parent-ds', sublayerIds: ['parent-ds-sub'] }
const subDef = { id: 'parent-ds-sub', parentId: 'parent-ds', style: { stroke: '#ff0000', fill: '#00ff00', opacity: 0.75 } }
datasetRegistry.attach({ 'parent-ds': parentDef, 'parent-ds-sub': subDef })
const dataset = datasetRegistry.getDataset('parent-ds')
const result = dataset.getLayersWithOpacity()
expect(result).toEqual([{ layerIds: ['parent-ds-sub', 'parent-ds-sub-stroke'], opacity: 0.75 }])
})

it('returns layerIds and opacity for all sublayers', () => {
const dataset = datasetRegistry.getDataset('historic-monuments')
expect(dataset.getLayersWithOpacity()).toEqual([
{ layerIds: ['historic-monuments-prehistoric'], opacity: 1 },
{ layerIds: ['historic-monuments-roman'], opacity: 1 },
{ layerIds: ['historic-monuments-medieval'], opacity: 1 }
])
})
})

describe('fillLayerId, strokeLayerId, symbolLayerId — hasSublayers returns null', () => {
it('fillLayerId returns null for a dataset with sublayers', () => {
const dataset = datasetRegistry.getDataset('land-covers')
Expand All @@ -129,8 +160,7 @@ describe('MapLibreDataset', () => {

it('returns an entry with layerIds and filter when the dataset has hidden features', () => {
const dataset = datasetRegistry.getDataset('land-covers-130-131')
const result = dataset.getLayersWithFilters()
expect(result).toEqual([{
expect(dataset.getLayersWithFilters()).toEqual([{
layerIds: ['land-covers-130-131', 'land-covers-130-131-stroke'],
filter: ['all',
['!', ['in', ['to-string', ['get', 'id']], ['literal', ['42']]]],
Expand All @@ -140,12 +170,13 @@ describe('MapLibreDataset', () => {

it('includes sublayer entries when a sublayer has hidden features', () => {
const parentDef = { id: 'parent-ds', sublayerIds: ['parent-ds-sub'] }
const subDef = { id: 'parent-ds-sub', parentId: 'parent-ds', hiddenFeatures: [7], style: { stroke: '#ff0000' } }
const subDef = { id: 'parent-ds-sub', parentId: 'parent-ds', hiddenFeatures: [7], style: { stroke: '#ff0000', fill: '#00ff00' } }
datasetRegistry.attach({ 'parent-ds': parentDef, 'parent-ds-sub': subDef })
const dataset = datasetRegistry.getDataset('parent-ds')
const result = dataset.getLayersWithFilters()
expect(result).toHaveLength(1)
expect(result[0].layerIds).toContain('parent-ds-sub')
expect(dataset.getLayersWithFilters()).toEqual([{
layerIds: ['parent-ds-sub', 'parent-ds-sub-stroke'],
filter: ['!', ['in', ['to-string', ['id']], ['literal', ['7']]]]
}])
})

it('returns an empty array when sublayers exist but none have hidden features', () => {
Expand Down
33 changes: 23 additions & 10 deletions plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,19 +203,32 @@ export default class MaplibreLayerAdapter {
}

/**
* Set opacity for all layers belonging to a dataset.
* Uses setPaintProperty directly — safe to call on every slider tick.
* Apply opacity for all layers with datasetId
* @param {string} datasetId
* @param {number} opacity
*/
setOpacity (datasetId, opacity) {
const style = this._map.getStyle()
if (!style?.layers) {
return
applyDatasetOpacity (datasetId) {
const registryDataset = datasetRegistry.getDataset(datasetId)
if (registryDataset) {
this._applyRegistryDatasetOpacity(registryDataset)
}
style.layers
.filter(layer => layer.id === datasetId || layer.id.startsWith(`${datasetId}-`))
.forEach(layer => this._setPaintOpacity(layer.id, opacity))
}

/**
* Apply opacity for all layers belonging to a registryDataset.
* Uses setPaintProperty directly — safe to call on every slider tick.
* @param {Object} registryDataset
*/
_applyRegistryDatasetOpacity (registryDataset) {
registryDataset.getLayersWithOpacity().forEach(({ layerIds, opacity }) => {
layerIds.forEach(layerId => this._setPaintOpacity(layerId, opacity))
})
}

/**
* Apply opacity for all layers
*/
applyGlobalOpacity () {
datasetRegistry.forEachDataset(registryDataset => this._applyRegistryDatasetOpacity(registryDataset))
}

/**
Expand Down
9 changes: 5 additions & 4 deletions plugins/beta/datasets/src/api/setOpacity.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const setOpacity = ({ pluginState: { dispatch } }, opacity, { datasetId, sublayerId }) => {
datasetId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
if (datasetId) {
dispatch({ type: 'SET_OPACITY', payload: { datasetId, opacity } })
export const setOpacity = ({ pluginState: { dispatch } }, opacity, options = {}) => {
const { datasetId, sublayerId } = options
const fullId = sublayerId ? `${datasetId}-${sublayerId}` : datasetId
if (fullId) {
dispatch({ type: 'SET_OPACITY', payload: { datasetId: fullId, opacity } })
} else {
// Global update
dispatch({ type: 'SET_GLOBAL_OPACITY', payload: { opacity } })
Expand Down
5 changes: 4 additions & 1 deletion plugins/beta/datasets/src/datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ export const createDatasets = ({
eventBus
}) => {
const { datasets } = pluginConfig

const dynamicSources = new Map()

if (pluginConfig.globals) {
dispatch({ type: 'INITIALISE_GLOBAL_STATE', payload: pluginConfig.globals })
}

// Initialise all datasets via the adapter, then set up dynamic sources
const processedDatasets = datasets.map(d => applyDatasetDefaults(d, datasetDefaults))
const { mappedDatasets, orderedDatasets } = mappedDatasetsReducer({ datasets })
Expand Down
26 changes: 18 additions & 8 deletions plugins/beta/datasets/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ const initialState = {
globals: {
visible: true,
opacity: 1,
// overrideDatasetOpacity:
// 'local': registryDataset opacity is used instead if set;
opacityMode: 'dataset'
// 'dataset': registryDataset opacity is used instead if set;
// 'global': registryDataset opacity is ignored
// 'multiply': registryDataset opacity is multiplied by global opacity
overrideDatasetOpacity: 'global'
// 'multiply': registryDataset opacity is multiplied by parent opacity and global opacity
},
key: {
items: [],
Expand All @@ -20,7 +19,8 @@ const initialState = {
layerAdapterActions: {
applyStyle: [],
applyDatasetVisibility: [],
setOpacity: [],
applyDatasetOpacity: [],
applyGlobalOpacity: [],
addDataset: [],
applyFeatureFilter: []
}
Expand All @@ -34,6 +34,13 @@ const validateDatasetExists = (state, datasetId, prefix, suffix = 'not found') =
return true
}

const initialiseGlobalState = (state, payload) => {
return {
...state,
globals: { ...state.globals, ...payload }
}
}

const setDatasets = (state, payload) => {
const { datasets, mappedDatasets, orderedDatasets } = payload
const menu = payload.menu || datasetsToMenu({ datasets })
Expand Down Expand Up @@ -164,18 +171,20 @@ const setOpacity = (state, payload) => {
}
const style = { ...state.mappedDatasets[datasetId].style, opacity }
const dataset = { ...state.mappedDatasets[datasetId], style }
const setOpacity = [...state.layerAdapterActions.setOpacity, [datasetId, opacity]]
const applyDatasetOpacity = [...state.layerAdapterActions.applyDatasetOpacity, [datasetId]]
return {
...state,
layerAdapterActions: { ...state.layerAdapterActions, setOpacity },
layerAdapterActions: { ...state.layerAdapterActions, applyDatasetOpacity },
mappedDatasets: { ...state.mappedDatasets, [datasetId]: dataset }
}
}

const setGlobalOpacity = (state, payload) => {
const { opacity } = payload
const applyGlobalOpacity = [...state.layerAdapterActions.applyDatasetOpacity, []]
return {
...state,
layerAdapterActions: { ...state.layerAdapterActions, applyGlobalOpacity },
globals: { ...state.globals, opacity }
}
}
Expand All @@ -194,7 +203,8 @@ const actions = {
HIDE_FEATURES: hideFeatures,
SHOW_FEATURES: showFeatures,
SET_LAYER_ADAPTER: setLayerAdapter,
SET_LAYER_ADAPTER_ACTIONS: setLayerAdapterActions
SET_LAYER_ADAPTER_ACTIONS: setLayerAdapterActions,
INITIALISE_GLOBAL_STATE: initialiseGlobalState
}

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { mappedDatasetsReducer } from '../../reducers/mappedDatasetsReducer.js'
import { datasets as datasetDefinitions } from '../../reducers/__data__/demoDatasets.js'
import { attachGlobalState } from '../globalDataset.js'
const { datasetRegistry } = jest.requireActual('../datasetRegistry.js')
const { mappedDatasets, orderedDatasets } = mappedDatasetsReducer({ datasets: datasetDefinitions })

const globalState = {
opacityMode: 'dataset',
opacity: 1,
visible: true
}

// By adding jest.mock('<path-to>/datasetRegistry.js')
// to a test file, any import of datasetRegistry from that file will get this
// version with the demo datasets attached for testing.
Expand All @@ -11,6 +18,7 @@ const { mappedDatasets, orderedDatasets } = mappedDatasetsReducer({ datasets: da
// and attach it in the specific test
beforeEach(() => {
datasetRegistry.attach(mappedDatasets, orderedDatasets)
attachGlobalState(globalState)
})

datasetRegistry.mockExtend = (extraDatasets) => datasetRegistry.attach(
Expand Down
Loading
Loading