Skip to content

Commit d284877

Browse files
authored
Merge pull request #51 from openearth/feature/handling-loading-layers-workflow
Enhance workflow configuration and output handling
2 parents 67b7d91 + aa4b50a commit d284877

3 files changed

Lines changed: 214 additions & 17 deletions

File tree

src/config/workflow.json

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"id": "administration",
2828
"name": "Administration",
2929
"layerName": "region:nuts_2021_level_3",
30-
"regionIdProperty": "nuts_id",
30+
"regionIdProperty": "nuts_name",
3131
"layerNameForWPS": "region:nuts_2021"
3232
},
3333
{
@@ -47,12 +47,8 @@
4747
"title": "Region Selection",
4848
"drawerTitle": "Select a base map",
4949
"icon": "mdi-map-marker-radius",
50-
"requiredSteps": [
51-
"userCaseSelection"
52-
],
5350
"explanation": "Select a region on the map by clicking it, then confirm to continue.",
54-
"requiresConfirmation": true,
55-
"confirmationSource": "mapClick",
51+
"requiresConfirmation": false,
5652
"components": [
5753
{
5854
"component": "LayerList",
@@ -145,6 +141,135 @@
145141
}
146142
}
147143
]
144+
},
145+
{
146+
"id": "archetypeSelection",
147+
"title": "Archetype Selection",
148+
"drawerTitle": "Select the archetype",
149+
"requiredSteps": [
150+
"uom"
151+
],
152+
"explanation": "Select the archetype and confirm to continue.",
153+
"requiresConfirmation": true,
154+
"confirmationSource": "component",
155+
"wps": {
156+
"identifier": "lare_coastal",
157+
"trigger": "stepComplete",
158+
"inputs": [
159+
{
160+
"id": "sessionid",
161+
"type": "LiteralData",
162+
"source": "store:app.wpsResults.initialSetup.sessionid"
163+
}
164+
],
165+
"storeResultAs": "coastalUseCase",
166+
"outputActions": [
167+
{
168+
"action": "removeLayer",
169+
"fromResultKey": "uom",
170+
"path": "layers"
171+
},
172+
{
173+
"action": "addLayer",
174+
"path": "layers"
175+
}
176+
]
177+
},
178+
"components": [
179+
{
180+
"component": "SelectionList",
181+
"componentProps": {
182+
"label": "Archetypes",
183+
"selectionKey": "archetypeSelection",
184+
"options": [
185+
{
186+
"id": "coastal",
187+
"name": "Urban Coastal"
188+
},
189+
{
190+
"id": "agricultural_land",
191+
"name": "Agricultural Land"
192+
},
193+
{
194+
"id": "rural_settlements",
195+
"name": "Rural Settlements"
196+
}
197+
]
198+
}
199+
}
200+
]
201+
},
202+
{
203+
"id": "kcsSelection",
204+
"title": "KCS & Hazards",
205+
"drawerTitle": "Select KCS and hazard",
206+
"requiredSteps": [
207+
"archetypeSelection"
208+
],
209+
"explanation": "Select KCS and hazard type, then confirm to run the UOM KCS calculation.",
210+
"requiresConfirmation": true,
211+
"confirmationSource": "component",
212+
"wps": {
213+
"identifier": "lare_kcs",
214+
"trigger": "stepComplete",
215+
"inputs": [
216+
{
217+
"id": "archetype",
218+
"type": "LiteralData",
219+
"source": "store:app.selections.archetypeSelection"
220+
},
221+
{
222+
"id": "kcs",
223+
"type": "LiteralData",
224+
"source": "store:app.selections.kcsSelection"
225+
},
226+
{
227+
"id": "sessionid",
228+
"type": "LiteralData",
229+
"source": "store:app.wpsResults.initialSetup.sessionid"
230+
},
231+
{
232+
"id": "hazard",
233+
"type": "LiteralData",
234+
"source": "store:app.selections.hazardSelection"
235+
}
236+
],
237+
"storeResultAs": "uomKcs",
238+
"outputActions": [
239+
{
240+
"action": "addLayer",
241+
"path": "layers"
242+
}
243+
]
244+
},
245+
"components": [
246+
{
247+
"component": "SelectionList",
248+
"componentProps": {
249+
"label": "KCS",
250+
"selectionKey": "kcsSelection",
251+
"options": [
252+
{
253+
"id": "transport",
254+
"name": "Transport"
255+
}
256+
]
257+
}
258+
},
259+
{
260+
"component": "SelectionList",
261+
"componentProps": {
262+
"label": "Hazards",
263+
"selectionKey": "hazardSelection",
264+
"options": [
265+
{
266+
"id": "pluvial_RP200",
267+
"name": "Pluvial floods"
268+
}
269+
]
270+
}
271+
}
272+
]
148273
}
149274
]
150275
}

src/lib/wps/handle-output.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,40 @@
33
* from workflow.json.
44
*
55
* Supported actions:
6-
* - storeValue: saves a value (or sub-path of response) into appStore.wpsResults
7-
* - addLayer: adds a dynamic layer to the map via mapStore
6+
* - storeValue: saves a value (or sub-path of response) into appStore.wpsResults
7+
* - addLayer: adds a dynamic layer to the map via mapStore
8+
* - removeLayer: removes a dynamic layer that was previously added
89
*
910
* @param {Array<OutputAction>} actions - Output action definitions from config
1011
* @param {Object} response - The parsed WPS response
1112
* @param {Object} stores - { app: appStore, map: mapStore }
1213
*
1314
* @typedef {Object} OutputAction
14-
* @property {'storeValue'|'addLayer'} action
15-
* @property {string} [path] - Dot-notated path into the response (omit or "response" for full response)
16-
* @property {string} [storeAs] - For storeValue: key under wpsResults to store into
17-
* @property {Object} [layerConfig] - For addLayer: layer configuration
15+
* @property {'storeValue'|'addLayer'|'removeLayer'} action
16+
* @property {string} [path] - For storeValue/addLayer: dot-notated path into the current WPS response (omit or "response" for full response)
17+
* @property {string} [storeAs] - For storeValue: key under wpsResults to store into
18+
* @property {Object} [layerConfig] - For addLayer: layer configuration
19+
* @property {string} [fromResultKey] - For removeLayer: key in appStore.wpsResults to resolve layers from
1820
*/
1921
export function handleOutputActions (actions, response, stores) {
2022
if (!actions?.length || !response) return
2123

2224
for (const action of actions) {
23-
const value = resolvePath(response, action.path)
24-
2525
switch (action.action) {
26-
case 'storeValue':
26+
case 'storeValue': {
27+
const value = resolvePath(response, action.path)
2728
if (action.storeAs && stores.app) {
2829
setNestedValue(stores.app.wpsResults, action.storeAs, value)
2930
}
3031
break
32+
}
3133

3234
case 'addLayer': {
35+
const value = resolvePath(response, action.path)
3336
if (!value || !stores.map?.addDynamicLayer) break
34-
const folders = Array.isArray(value) ? value : [value]
37+
const folders = Array.isArray(value) ? value : [ value ]
3538
for (const folder of folders) {
36-
const entries = folder?.contents ?? [folder]
39+
const entries = folder?.contents ?? [ folder ]
3740
for (const entry of entries) {
3841
if (entry?.layer && entry?.url) {
3942
stores.map.addDynamicLayer({
@@ -49,6 +52,39 @@ export function handleOutputActions (actions, response, stores) {
4952
break
5053
}
5154

55+
case 'removeLayer': {
56+
const { fromResultKey } = action
57+
const previousResult = fromResultKey && stores.app?.wpsResults
58+
? stores.app.wpsResults[fromResultKey]
59+
: null
60+
61+
// If we cannot resolve a previous result or the path,
62+
// fall back to clearing all dynamic layers.
63+
if (!stores.map?.removeDynamicLayer) break
64+
65+
if (previousResult) {
66+
const value = resolvePath(previousResult, action.path)
67+
if (value) {
68+
const folders = Array.isArray(value) ? value : [ value ]
69+
for (const folder of folders) {
70+
const entries = folder?.contents ?? [ folder ]
71+
for (const entry of entries) {
72+
if (entry?.layer) {
73+
stores.map.removeDynamicLayer(entry.layer)
74+
}
75+
}
76+
}
77+
break
78+
}
79+
}
80+
81+
// Fallback: remove all dynamic layers (those not in the static config)
82+
if (stores.map?.clearDynamicLayers) {
83+
stores.map.clearDynamicLayers()
84+
}
85+
break
86+
}
87+
5288
default:
5389
console.warn(`[handleOutputActions] Unknown action: "${ action.action }"`)
5490
}

src/stores/map.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import buildMapboxLayer from '@/lib/build-mapbox-layer'
66
export const useMapStore = defineStore('map', {
77
state: () => ({
88
layersConfig,
9+
staticLayerIds: layersConfig.map(cfg => cfg.id),
910
mapboxLayers: [],
1011
layerVisibility: {},
1112
layerClickable: {},
@@ -167,12 +168,47 @@ export const useMapStore = defineStore('map', {
167168
if (built) {
168169
this.mapboxLayers.push(built)
169170
this.layerVisibility[layerConfig.id] = true
171+
172+
// Ensure dynamic layers also show up in the legend by
173+
// creating a corresponding layersConfig entry when needed.
174+
const hasConfig = this.layersConfig.some(cfg => cfg.id === layerConfig.id)
175+
if (!hasConfig) {
176+
this.layersConfig.push({
177+
id: layerConfig.id,
178+
url: layerConfig.url,
179+
layer: layerConfig.layer,
180+
name: layerConfig.name || layerConfig.id,
181+
dynamic: true,
182+
})
183+
}
170184
}
171185
},
172186

173187
removeDynamicLayer (layerId) {
174188
this.mapboxLayers = this.mapboxLayers.filter(l => l.id !== layerId)
175189
delete this.layerVisibility[layerId]
190+
191+
// Remove any dynamic-only config entry so legends stay in sync
192+
this.layersConfig = this.layersConfig.filter(cfg => !(cfg.id === layerId && cfg.dynamic))
193+
},
194+
195+
/**
196+
* Removes all dynamically added layers (those not present in the static layersConfig).
197+
*/
198+
clearDynamicLayers () {
199+
const staticIds = new Set(this.staticLayerIds)
200+
201+
// Remove all non-static layers from the map and visibility state
202+
this.mapboxLayers = this.mapboxLayers.filter(layer => {
203+
const isStatic = staticIds.has(layer.id)
204+
if (!isStatic) {
205+
delete this.layerVisibility[layer.id]
206+
}
207+
return isStatic
208+
})
209+
210+
// Strip any dynamic layer configs so legends update accordingly
211+
this.layersConfig = this.layersConfig.filter(cfg => staticIds.has(cfg.id))
176212
},
177213
},
178214
})

0 commit comments

Comments
 (0)