@eodash/eodash 5.6.0 → 5.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/client/eodashSTAC/EodashCollection.js +5 -1
- package/core/client/eodashSTAC/helpers.js +4 -4
- package/dist/client/{DashboardLayout-DkKDhfgH.js → DashboardLayout-CqMb7OXc.js} +2 -2
- package/dist/client/{DynamicWebComponent-BrpkA-e3.js → DynamicWebComponent-DqKqTf7l.js} +1 -1
- package/dist/client/{EodashChart-p7_1FRlF.js → EodashChart-PkUQfH0k.js} +4 -4
- package/dist/client/{EodashDatePicker-HK0Jl7wQ.js → EodashDatePicker-DLJGdTtX.js} +3 -3
- package/dist/client/{EodashItemFilter-BYQDA78B.js → EodashItemFilter-Dk8KjFcX.js} +1 -1
- package/dist/client/{EodashLayerControl-NcrNncGn.js → EodashLayerControl-CC1Cogrn.js} +2 -2
- package/dist/client/{EodashLayoutSwitcher-CC2Znd0I.js → EodashLayoutSwitcher-5WZYd6rr.js} +3 -3
- package/dist/client/{EodashMapBtns-BJSh7qT-.js → EodashMapBtns-BHrSDNoC.js} +6 -6
- package/dist/client/{EodashStacInfo-BA-0UjvT.js → EodashStacInfo-B90wobPc.js} +31 -31
- package/dist/client/{EodashTimeSlider-GDv1hbw7.js → EodashTimeSlider-5vm7F3ju.js} +2 -2
- package/dist/client/{EodashTools-CWmoH0g_.js → EodashTools-CGZtTh45.js} +4 -4
- package/dist/client/{ExportState-CfAfX4RN.js → ExportState-Ce0ZnrX_.js} +4 -4
- package/dist/client/{Footer-DLGxQTZt.js → Footer-CnZqEiop.js} +1 -1
- package/dist/client/{Header-eyEyyzD6.js → Header-Dr5PBIm8.js} +2 -2
- package/dist/client/{MobileLayout-RzLOyXwL.js → MobileLayout-DyMjaeif.js} +2 -2
- package/dist/client/{PopUp-WNs6Io2N.js → PopUp-DjvE_71e.js} +2 -2
- package/dist/client/{ProcessList-RzVqbd5E.js → ProcessList-kf3olI7W.js} +4 -4
- package/dist/client/{VImg-GKUTFoV-.js → VImg-Bj6uWt6j.js} +2 -2
- package/dist/client/{VMain-D7_72mHr.js → VMain-Cm0UmKGi.js} +1 -1
- package/dist/client/{VTooltip-BbmvtvYw.js → VTooltip-DbBu-ky3.js} +2 -2
- package/dist/client/{WidgetsContainer-Dw5d7SOW.js → WidgetsContainer-CfLnd44U.js} +1 -1
- package/dist/client/{asWebComponent-DDt7V3Bl.js → asWebComponent-DfULAbpq.js} +13 -9
- package/dist/client/{async-CT3KEV52.js → async-CIbKXEEe.js} +10 -20
- package/dist/client/eo-dash.js +1 -1
- package/dist/client/{forwardRefs-CVzxyoSq.js → forwardRefs-yj3VWOK5.js} +2 -2
- package/dist/client/{handling-C285F_Mq.js → handling-s8xt2JwF.js} +390 -90
- package/dist/client/{helpers-CZFH0EVN.js → helpers-DAVGYl_f.js} +4 -4
- package/dist/client/{index-B-m7V3Kb.js → index-4TrjfJ32.js} +3 -3
- package/dist/client/{index-B_6H-N14.js → index-Bu8BMA9q.js} +39 -31
- package/dist/client/{index-DgOXD7wd.js → index-CYz6dT7K.js} +2 -2
- package/dist/client/{index-CF3ZMzTz.js → index-CugS__t5.js} +3 -3
- package/dist/client/templates.js +1 -1
- package/dist/client/{transition-C2gnrWg0.js → transition-DaOsiD4u.js} +1 -1
- package/dist/types/widgets/EodashProcess/methods/composables.d.ts +1 -2
- package/dist/types/widgets/EodashProcess/methods/handling.d.ts +6 -6
- package/dist/types/widgets/EodashProcess/methods/outputs.d.ts +3 -1
- package/dist/types/widgets/EodashProcess/methods/template-helpers.d.ts +99 -0
- package/dist/types/widgets/EodashProcess/methods/utils.d.ts +1 -6
- package/dist/types/widgets/EodashProcess/types.d.ts +1 -0
- package/dist/types/widgets/EodashStacInfo.vue.d.ts +10 -10
- package/package.json +1 -1
- package/widgets/EodashProcess/index.vue +16 -12
- package/widgets/EodashProcess/methods/composables.js +18 -15
- package/widgets/EodashProcess/methods/handling.js +78 -62
- package/widgets/EodashProcess/methods/outputs.js +74 -29
- package/widgets/EodashProcess/methods/template-helpers.js +246 -0
- package/widgets/EodashProcess/methods/utils.js +7 -17
- package/widgets/EodashProcess/types.ts +1 -0
- package/widgets/EodashStacInfo.vue +30 -30
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
applyProcessLayersToMap,
|
|
4
4
|
extractGeometries,
|
|
5
5
|
getBboxProperty,
|
|
6
|
-
|
|
7
|
-
updateJsonformSchemaTarget,
|
|
6
|
+
getDrawToolsProperties,
|
|
8
7
|
} from "./utils";
|
|
9
8
|
import {
|
|
10
9
|
compareIndicator,
|
|
@@ -17,13 +16,12 @@ import {
|
|
|
17
16
|
compareChartData,
|
|
18
17
|
compareChartSpec,
|
|
19
18
|
} from "@/store/states";
|
|
20
|
-
import axios from "@/plugins/axios";
|
|
21
19
|
import { processCharts, processLayers, processSTAC } from "./outputs";
|
|
22
20
|
import { handleLayersCustomEndpoints } from "./custom-endpoints/layers";
|
|
23
21
|
import { handleChartCustomEndpoints } from "./custom-endpoints/chart";
|
|
24
22
|
import { useSTAcStore } from "@/store/stac";
|
|
25
23
|
import { useGetSubCodeId } from "@/composables";
|
|
26
|
-
import { getLayers } from "@/store/actions";
|
|
24
|
+
import { getLayers, getCompareLayers } from "@/store/actions";
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
27
|
* Fetch and set the jsonform schema to initialize the process
|
|
@@ -32,7 +30,6 @@ import { getLayers } from "@/store/actions";
|
|
|
32
30
|
* @async
|
|
33
31
|
* @param {Object} params
|
|
34
32
|
* @param {import("vue").Ref<import("stac-ts").StacCollection | null>} params.selectedStac
|
|
35
|
-
* @param {import("vue").Ref<import("@eox/jsonform").EOxJSONForm | null>} params.jsonformEl
|
|
36
33
|
* @param {import("vue").Ref<Record<string,any> | null>} params.jsonformSchema
|
|
37
34
|
* @param {import("vue").Ref<any[]>} params.processResults
|
|
38
35
|
* @param {import("vue").Ref<boolean>} params.isProcessed
|
|
@@ -42,7 +39,6 @@ import { getLayers } from "@/store/actions";
|
|
|
42
39
|
*/
|
|
43
40
|
export async function initProcess({
|
|
44
41
|
selectedStac,
|
|
45
|
-
jsonformEl,
|
|
46
42
|
jsonformSchema,
|
|
47
43
|
isProcessed,
|
|
48
44
|
processResults,
|
|
@@ -53,10 +49,10 @@ export async function initProcess({
|
|
|
53
49
|
const isPoiAlive = enableCompare ? !!comparePoi.value : !!poi.value;
|
|
54
50
|
let updatedJsonform = null;
|
|
55
51
|
if (selectedStac.value?.["eodash:jsonform"]) {
|
|
56
|
-
updatedJsonform = await
|
|
52
|
+
updatedJsonform = await fetch(
|
|
57
53
|
//@ts-expect-error eodash extention
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
selectedStac.value["eodash:jsonform"],
|
|
55
|
+
).then((resp) => resp.json());
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
if (!updatedJsonform && isPoiAlive) {
|
|
@@ -72,17 +68,13 @@ export async function initProcess({
|
|
|
72
68
|
enableCompare,
|
|
73
69
|
});
|
|
74
70
|
|
|
75
|
-
await jsonformEl.value?.editor.destroy();
|
|
76
71
|
if (updatedJsonform) {
|
|
77
72
|
// make sure correct target layer id is used in jsonform
|
|
78
|
-
|
|
79
|
-
newJsonForm = await updateJsonformIdentifier({
|
|
73
|
+
const newJsonForm = await updateJsonformIdentifier({
|
|
80
74
|
jsonformSchema: updatedJsonform,
|
|
81
|
-
newLayers: getLayers(),
|
|
75
|
+
newLayers: enableCompare ? getCompareLayers() : getLayers(),
|
|
76
|
+
enableCompare,
|
|
82
77
|
});
|
|
83
|
-
if (enableCompare) {
|
|
84
|
-
newJsonForm = updateJsonformSchemaTarget(newJsonForm);
|
|
85
|
-
}
|
|
86
78
|
// trigger jsonform update in next tick
|
|
87
79
|
jsonformSchema.value = null;
|
|
88
80
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -98,62 +90,76 @@ export async function initProcess({
|
|
|
98
90
|
* @param {Object} params
|
|
99
91
|
* @param {Record<string,any> | null} params.jsonformSchema params.jsonformSchema
|
|
100
92
|
* @param {Record<string, any>[] | undefined} params.newLayers params.newLayers
|
|
101
|
-
* @
|
|
93
|
+
* @param { boolean } params.enableCompare params.enableCompare
|
|
94
|
+
* @returns {Promise<Record<string,any> | null>} updated jsonform schema
|
|
102
95
|
*/
|
|
103
|
-
export async function updateJsonformIdentifier({
|
|
96
|
+
export async function updateJsonformIdentifier({
|
|
97
|
+
jsonformSchema,
|
|
98
|
+
newLayers,
|
|
99
|
+
enableCompare,
|
|
100
|
+
}) {
|
|
104
101
|
const form = jsonformSchema;
|
|
105
102
|
if (!form) {
|
|
106
|
-
return;
|
|
103
|
+
return null;
|
|
107
104
|
}
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
|
|
118
|
-
if (newLayers.layers && Array.isArray(newLayers.layers)) {
|
|
119
|
-
// @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
|
|
120
|
-
layers = newLayers.layers;
|
|
105
|
+
const drawToolsProperties = getDrawToolsProperties(form);
|
|
106
|
+
drawToolsProperties.forEach((drawToolsProperty) => {
|
|
107
|
+
if (
|
|
108
|
+
drawToolsProperty &&
|
|
109
|
+
form?.properties[drawToolsProperty]?.options?.drawtools?.for &&
|
|
110
|
+
enableCompare
|
|
111
|
+
) {
|
|
112
|
+
form.properties[drawToolsProperty].options.drawtools.for =
|
|
113
|
+
"eox-map#compare";
|
|
121
114
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
115
|
+
if (
|
|
116
|
+
drawToolsProperty &&
|
|
117
|
+
newLayers &&
|
|
118
|
+
form?.properties[drawToolsProperty]?.options?.drawtools?.layerId
|
|
119
|
+
) {
|
|
120
|
+
// get partial or full id and try to match with correct eoxmap layer
|
|
121
|
+
// check if newLayers is an array or an object with layers property
|
|
122
|
+
let layers = newLayers;
|
|
123
|
+
// @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
|
|
124
|
+
if (newLayers.layers && Array.isArray(newLayers.layers)) {
|
|
125
|
+
// @ts-expect-error TODO payload coming from time update sometimes is not an object with layers property
|
|
126
|
+
layers = newLayers.layers;
|
|
134
127
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
128
|
+
|
|
129
|
+
const layerId =
|
|
130
|
+
form.properties[drawToolsProperty].options.drawtools.layerId.split(
|
|
131
|
+
";:;",
|
|
132
|
+
)[0];
|
|
133
|
+
let matchedLayerId = null;
|
|
134
|
+
// layers are not flat can be grouped, we need to recursively search
|
|
135
|
+
const traverseLayers = (
|
|
136
|
+
/** @type {Record<string, any>[] | undefined} */ layersArray,
|
|
137
|
+
) => {
|
|
138
|
+
if (!layersArray) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const layer of layersArray) {
|
|
142
|
+
if (layer.type === "Group" && Array.isArray(layer.layers)) {
|
|
143
|
+
traverseLayers(layer.layers);
|
|
144
|
+
} else {
|
|
145
|
+
if (layer.properties?.id?.startsWith(layerId)) {
|
|
146
|
+
matchedLayerId = layer.properties.id;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
142
149
|
}
|
|
143
150
|
}
|
|
151
|
+
};
|
|
152
|
+
traverseLayers(layers);
|
|
153
|
+
if (matchedLayerId) {
|
|
154
|
+
form.properties[drawToolsProperty].options.drawtools.layerId =
|
|
155
|
+
matchedLayerId;
|
|
156
|
+
} else {
|
|
157
|
+
console.warn(
|
|
158
|
+
`Could not find matching layer for processing form with id: ${layerId}`,
|
|
159
|
+
);
|
|
144
160
|
}
|
|
145
|
-
};
|
|
146
|
-
traverseLayers(layers);
|
|
147
|
-
if (matchedLayerId) {
|
|
148
|
-
form.properties.feature.options.drawtools.layerId = matchedLayerId;
|
|
149
|
-
return form;
|
|
150
|
-
} else {
|
|
151
|
-
console.warn(
|
|
152
|
-
`Could not find matching layer for processing form with id: ${layerId}`,
|
|
153
|
-
);
|
|
154
|
-
return null;
|
|
155
161
|
}
|
|
156
|
-
}
|
|
162
|
+
});
|
|
157
163
|
return form;
|
|
158
164
|
}
|
|
159
165
|
|
|
@@ -192,6 +198,15 @@ export async function handleProcesses({
|
|
|
192
198
|
);
|
|
193
199
|
|
|
194
200
|
const bboxProperty = getBboxProperty(jsonformSchema.value);
|
|
201
|
+
// Preserve raw form value before extractGeometries mutates it.
|
|
202
|
+
// Needed so POST multiQuery can iterate original GeoJSON Feature objects.
|
|
203
|
+
// Uses JSON round-trip instead of structuredClone to handle OL Feature
|
|
204
|
+
// objects from drawtools (which contain non-cloneable methods).
|
|
205
|
+
const rawJsonformValue = JSON.parse(
|
|
206
|
+
JSON.stringify(
|
|
207
|
+
/** @type {Record<string, any>} */ (jsonformEl.value?.value ?? {}),
|
|
208
|
+
),
|
|
209
|
+
);
|
|
195
210
|
const jsonformValue = /** @type {Record<string,any>} */ (
|
|
196
211
|
jsonformEl.value?.value
|
|
197
212
|
);
|
|
@@ -210,6 +225,7 @@ export async function handleProcesses({
|
|
|
210
225
|
[tempChartSpec, usedChartData.value] = await processCharts({
|
|
211
226
|
links: serviceLinks,
|
|
212
227
|
jsonformValue: { ...(jsonformValue ?? {}) },
|
|
228
|
+
rawJsonformValue,
|
|
213
229
|
jsonformSchema: jsonformSchema.value,
|
|
214
230
|
enableCompare,
|
|
215
231
|
selectedStac: selectedStac.value,
|
|
@@ -4,12 +4,21 @@ import axios from "@/plugins/axios";
|
|
|
4
4
|
import { createTiffLayerDefinition, separateEndpointLinks } from "./utils";
|
|
5
5
|
import { useSTAcStore } from "@/store/stac";
|
|
6
6
|
import { isFirstLoad } from "@/utils/states";
|
|
7
|
+
import {
|
|
8
|
+
getMultiQueryMatches,
|
|
9
|
+
getPostMultiQueryValues,
|
|
10
|
+
getScalarMultiQueryValues,
|
|
11
|
+
createMultiQueryContext,
|
|
12
|
+
aggregateInlineResponses,
|
|
13
|
+
renderJsonBodyTemplate,
|
|
14
|
+
} from "./template-helpers";
|
|
7
15
|
|
|
8
16
|
////// --- CHARTS --- //////
|
|
9
17
|
/**
|
|
10
18
|
* @param {object} options
|
|
11
19
|
* @param {import("stac-ts").StacLink[] | undefined} options.links
|
|
12
20
|
* @param {Record<string,any> | undefined} options.jsonformValue
|
|
21
|
+
* @param {Record<string,any> | undefined} [options.rawJsonformValue]
|
|
13
22
|
* @param {string} options.specUrl
|
|
14
23
|
* @param {(input:import("^/EodashProcess/types").CustomEnpointInput)=>Promise<Record<string,any>[] | undefined | null>} [options.customEndpointsHandler]
|
|
15
24
|
* @param {import("vue").Ref<boolean>} options.isPolling
|
|
@@ -22,6 +31,7 @@ import { isFirstLoad } from "@/utils/states";
|
|
|
22
31
|
export async function processCharts({
|
|
23
32
|
links,
|
|
24
33
|
jsonformValue,
|
|
34
|
+
rawJsonformValue,
|
|
25
35
|
specUrl,
|
|
26
36
|
customEndpointsHandler,
|
|
27
37
|
jsonformSchema,
|
|
@@ -51,6 +61,7 @@ export async function processCharts({
|
|
|
51
61
|
(await customEndpointsHandler({
|
|
52
62
|
jsonformSchema,
|
|
53
63
|
jsonformValue,
|
|
64
|
+
rawJsonformValue,
|
|
54
65
|
links: endpointLinks,
|
|
55
66
|
selectedStac,
|
|
56
67
|
isPolling,
|
|
@@ -114,6 +125,7 @@ export async function processCharts({
|
|
|
114
125
|
await injectVegaInlineData(spec, {
|
|
115
126
|
url: link.href,
|
|
116
127
|
jsonformValue: jsonformValue,
|
|
128
|
+
rawJsonformValue,
|
|
117
129
|
link: link,
|
|
118
130
|
flatstyleUrl: /** @type string */ (link["eox:flatstyle"]),
|
|
119
131
|
jsonformSchema,
|
|
@@ -153,46 +165,39 @@ export async function processCharts({
|
|
|
153
165
|
* @param {object} injectables
|
|
154
166
|
* @param {string} injectables.url
|
|
155
167
|
* @param {Record<string,any>} [injectables.jsonformValue]
|
|
168
|
+
* @param {Record<string,any>} [injectables.rawJsonformValue]
|
|
156
169
|
* @param {import("stac-ts").StacLink} injectables.link
|
|
157
170
|
* @param {url} [injectables.flatstyleUrl]
|
|
158
171
|
* @param {import("json-schema").JSONSchema7} [injectables.jsonformSchema]
|
|
159
172
|
*/
|
|
160
173
|
async function injectVegaInlineData(
|
|
161
174
|
spec,
|
|
162
|
-
{ url, jsonformValue, link, flatstyleUrl, jsonformSchema },
|
|
175
|
+
{ url, jsonformValue, rawJsonformValue, link, flatstyleUrl, jsonformSchema },
|
|
163
176
|
) {
|
|
164
177
|
if (!spec.data) {
|
|
165
178
|
return;
|
|
166
179
|
}
|
|
180
|
+
// @ts-expect-error JSONSchema7 is structurally compatible with Record<string, any>
|
|
181
|
+
const multiQueryMatches = getMultiQueryMatches(jsonformValue, jsonformSchema);
|
|
167
182
|
if (link.method == "GET") {
|
|
168
|
-
|
|
169
|
-
// and if so, we can do multiple requests and merge all data together.
|
|
170
|
-
//@ts-expect-error type jsonform Schema
|
|
171
|
-
const multiQuery = jsonformSchema?.options?.multiQuery;
|
|
172
|
-
const matches = Object.keys(jsonformValue ?? {}).filter((key) => {
|
|
173
|
-
return Array.isArray(multiQuery)
|
|
174
|
-
? multiQuery.includes(key)
|
|
175
|
-
: multiQuery === key;
|
|
176
|
-
});
|
|
177
|
-
if (matches.length > 0 && jsonformValue) {
|
|
183
|
+
if (multiQueryMatches.length > 0 && jsonformValue) {
|
|
178
184
|
const dataValues = [];
|
|
179
|
-
for (const match of
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const dataUrl = await renderDataUrl(url, jsonformValue, flatstyleUrl);
|
|
185
|
+
for (const match of multiQueryMatches) {
|
|
186
|
+
const queryValues = getScalarMultiQueryValues(match, jsonformValue);
|
|
187
|
+
if (!queryValues.length) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
for (const value of queryValues) {
|
|
191
|
+
const dataUrl = await renderDataUrl(
|
|
192
|
+
url,
|
|
193
|
+
{ ...jsonformValue, [match]: value },
|
|
194
|
+
flatstyleUrl,
|
|
195
|
+
);
|
|
191
196
|
dataValues.push(await axios.get(dataUrl).then((resp) => resp.data));
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
199
|
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
195
|
-
(spec.data).values = dataValues
|
|
200
|
+
(spec.data).values = aggregateInlineResponses(dataValues);
|
|
196
201
|
return spec;
|
|
197
202
|
}
|
|
198
203
|
// if no array matches, we can just do a single request
|
|
@@ -216,11 +221,49 @@ async function injectVegaInlineData(
|
|
|
216
221
|
.then((resp) => {
|
|
217
222
|
return resp.data;
|
|
218
223
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
224
|
+
if (
|
|
225
|
+
// @ts-expect-error jsonform options extension not in JSONSchema7
|
|
226
|
+
jsonformSchema?.options?.multiQuery &&
|
|
227
|
+
multiQueryMatches.length === 0
|
|
228
|
+
) {
|
|
229
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
230
|
+
(spec.data).values = [];
|
|
231
|
+
return spec;
|
|
232
|
+
}
|
|
233
|
+
if (multiQueryMatches.length > 0 && jsonformValue) {
|
|
234
|
+
const responses = [];
|
|
235
|
+
for (const match of multiQueryMatches) {
|
|
236
|
+
const queryValues = getPostMultiQueryValues(
|
|
237
|
+
match,
|
|
238
|
+
jsonformValue,
|
|
239
|
+
rawJsonformValue,
|
|
240
|
+
/** @type {Record<string, any>} */ (jsonformSchema),
|
|
241
|
+
);
|
|
242
|
+
if (!queryValues.length) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
// Sequential POSTs (not parallel) to avoid rate-limiting on endpoints
|
|
246
|
+
// like TiTiler-PgSTAC. For small selection counts this is fine; if
|
|
247
|
+
// parallelism is needed later, switch to Promise.all.
|
|
248
|
+
for (const [index, value] of queryValues.entries()) {
|
|
249
|
+
const requestContext = createMultiQueryContext(
|
|
250
|
+
jsonformValue,
|
|
251
|
+
match,
|
|
252
|
+
value,
|
|
253
|
+
index,
|
|
254
|
+
);
|
|
255
|
+
const body = renderJsonBodyTemplate(bodyTemplate, requestContext);
|
|
256
|
+
responses.push(await axios.post(url, body).then((resp) => resp.data));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
260
|
+
(spec.data).values = aggregateInlineResponses(responses);
|
|
261
|
+
return spec;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const body = renderJsonBodyTemplate(bodyTemplate, {
|
|
265
|
+
...(jsonformValue ?? {}),
|
|
266
|
+
});
|
|
224
267
|
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
225
268
|
(spec.data).values = await axios.post(url, body).then((resp) => {
|
|
226
269
|
return resp.data;
|
|
@@ -229,6 +272,8 @@ async function injectVegaInlineData(
|
|
|
229
272
|
return spec;
|
|
230
273
|
}
|
|
231
274
|
|
|
275
|
+
// MultiQuery helpers and template rendering are in ./template-helpers.js
|
|
276
|
+
|
|
232
277
|
/**
|
|
233
278
|
* @param {import("vega-lite").TopLevelSpec} spec
|
|
234
279
|
* @param {object} injectables
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import mustache from "mustache";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns form value keys that match the multiQuery configuration.
|
|
5
|
+
* @param {Record<string, any> | undefined} jsonformValue
|
|
6
|
+
* @param {Record<string, any>} jsonformSchema
|
|
7
|
+
* @returns {string[]}
|
|
8
|
+
*/
|
|
9
|
+
export function getMultiQueryMatches(jsonformValue, jsonformSchema) {
|
|
10
|
+
const multiQuery = jsonformSchema?.options?.multiQuery;
|
|
11
|
+
return Object.keys(jsonformValue ?? {}).filter((key) => {
|
|
12
|
+
return Array.isArray(multiQuery)
|
|
13
|
+
? multiQuery.includes(key)
|
|
14
|
+
: multiQuery === key;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extracts iterable scalar values for a multiQuery field.
|
|
20
|
+
* Wraps single values in an array; returns empty array for undefined.
|
|
21
|
+
* @param {string} match - The multiQuery field name
|
|
22
|
+
* @param {Record<string, any>} jsonformValue
|
|
23
|
+
* @returns {any[]}
|
|
24
|
+
*/
|
|
25
|
+
export function getScalarMultiQueryValues(match, jsonformValue) {
|
|
26
|
+
const value = jsonformValue?.[match];
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return value === undefined ? [] : [value];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extracts iterable values for a POST multiQuery field.
|
|
35
|
+
* For GeoJSON fields, uses rawJsonformValue to get original Feature objects
|
|
36
|
+
* (before extractGeometries stringifies them). For scalar fields, delegates
|
|
37
|
+
* to getScalarMultiQueryValues.
|
|
38
|
+
* @param {string} match - The multiQuery field name
|
|
39
|
+
* @param {Record<string, any>} jsonformValue - Form values after geometry extraction
|
|
40
|
+
* @param {Record<string, any> | undefined} rawJsonformValue - Form values before geometry extraction
|
|
41
|
+
* @param {Record<string, any>} jsonformSchema
|
|
42
|
+
* @returns {any[]}
|
|
43
|
+
*/
|
|
44
|
+
export function getPostMultiQueryValues(
|
|
45
|
+
match,
|
|
46
|
+
jsonformValue,
|
|
47
|
+
rawJsonformValue,
|
|
48
|
+
jsonformSchema,
|
|
49
|
+
) {
|
|
50
|
+
if (jsonformSchema?.properties?.[match]?.type === "geojson") {
|
|
51
|
+
return getGeoJsonMultiQueryValues(rawJsonformValue?.[match]);
|
|
52
|
+
}
|
|
53
|
+
return getScalarMultiQueryValues(match, jsonformValue);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extracts individual GeoJSON Feature objects from a value that may be
|
|
58
|
+
* a FeatureCollection, a single Feature, or an array of Features.
|
|
59
|
+
* Filters for valid Features with geometry and deduplicates.
|
|
60
|
+
* @param {any} value
|
|
61
|
+
* @returns {Record<string, any>[]}
|
|
62
|
+
*/
|
|
63
|
+
export function getGeoJsonMultiQueryValues(value) {
|
|
64
|
+
if (value?.type === "FeatureCollection") {
|
|
65
|
+
return dedupeMultiQueryValues(
|
|
66
|
+
(value.features ?? []).filter(
|
|
67
|
+
(/** @type {any} */ feature) =>
|
|
68
|
+
feature?.type === "Feature" && feature.geometry,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (value?.type === "Feature") {
|
|
73
|
+
return [value];
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(value)) {
|
|
76
|
+
return dedupeMultiQueryValues(
|
|
77
|
+
value.filter(
|
|
78
|
+
(/** @type {any} */ feature) =>
|
|
79
|
+
feature?.type === "Feature" && feature.geometry,
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Removes duplicate values using JSON serialization as the identity key.
|
|
88
|
+
* @param {any[]} values
|
|
89
|
+
* @returns {any[]}
|
|
90
|
+
*/
|
|
91
|
+
export function dedupeMultiQueryValues(values) {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const deduped = [];
|
|
94
|
+
for (const value of values ?? []) {
|
|
95
|
+
const key =
|
|
96
|
+
value && typeof value === "object"
|
|
97
|
+
? JSON.stringify(value)
|
|
98
|
+
: String(value);
|
|
99
|
+
if (seen.has(key)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
seen.add(key);
|
|
103
|
+
deduped.push(value);
|
|
104
|
+
}
|
|
105
|
+
return deduped;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Builds the Mustache rendering context for one multiQuery iteration.
|
|
110
|
+
* Spreads the full form value, overrides the matched field with the current
|
|
111
|
+
* iteration value, and adds a 1-based `multiQueryIndex`.
|
|
112
|
+
* @param {Record<string, any>} jsonformValue
|
|
113
|
+
* @param {string} match - The multiQuery field name being iterated
|
|
114
|
+
* @param {any} value - The current iteration value
|
|
115
|
+
* @param {number} index - Zero-based iteration index
|
|
116
|
+
* @returns {Record<string, any>}
|
|
117
|
+
*/
|
|
118
|
+
export function createMultiQueryContext(jsonformValue, match, value, index) {
|
|
119
|
+
return {
|
|
120
|
+
...(jsonformValue ?? {}),
|
|
121
|
+
[match]: value,
|
|
122
|
+
multiQueryIndex: index + 1,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Flattens an array of POST responses into a single values array.
|
|
128
|
+
* Each response that is itself an array is spread; scalars are wrapped.
|
|
129
|
+
* @param {any[]} values
|
|
130
|
+
* @returns {any[]}
|
|
131
|
+
*/
|
|
132
|
+
export function aggregateInlineResponses(values) {
|
|
133
|
+
return values.flatMap((value) => (Array.isArray(value) ? value : [value]));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Renders a JSON body template string with the given context.
|
|
138
|
+
* Primary path: parses the template as JSON, then recursively renders each
|
|
139
|
+
* node (preserving object-valued placeholders as raw JSON).
|
|
140
|
+
* Fallback: treats the template as a Mustache string, renders, then parses.
|
|
141
|
+
* @param {string} bodyTemplate - JSON template string (may contain Mustache placeholders)
|
|
142
|
+
* @param {Record<string, any>} context - Values to substitute into the template
|
|
143
|
+
* @returns {any} Parsed JSON body ready for POST
|
|
144
|
+
*/
|
|
145
|
+
export function renderJsonBodyTemplate(bodyTemplate, context) {
|
|
146
|
+
try {
|
|
147
|
+
const parsedTemplate = JSON.parse(bodyTemplate);
|
|
148
|
+
return renderTemplateNode(parsedTemplate, context);
|
|
149
|
+
} catch {
|
|
150
|
+
try {
|
|
151
|
+
return JSON.parse(
|
|
152
|
+
mustache.render(bodyTemplate, {
|
|
153
|
+
...(context ?? {}),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[eodash] Failed to render POST body template: ${e instanceof Error ? e.message : String(e)}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Recursively renders a parsed JSON template node with context values.
|
|
166
|
+
* - Arrays and objects are traversed recursively.
|
|
167
|
+
* - Exact placeholders (where the entire string value is `"{{path}}"` or
|
|
168
|
+
* `"{{{path}}}"`) are resolved to their original typed value (object, array,
|
|
169
|
+
* number, etc.) instead of being stringified. This is critical for embedding
|
|
170
|
+
* GeoJSON geometry objects in POST bodies.
|
|
171
|
+
* - Partial placeholders and mixed strings use standard Mustache interpolation.
|
|
172
|
+
* @param {any} templateNode
|
|
173
|
+
* @param {Record<string, any>} context
|
|
174
|
+
* @returns {any}
|
|
175
|
+
*/
|
|
176
|
+
export function renderTemplateNode(templateNode, context) {
|
|
177
|
+
if (Array.isArray(templateNode)) {
|
|
178
|
+
return templateNode.map((value) => renderTemplateNode(value, context));
|
|
179
|
+
}
|
|
180
|
+
if (templateNode && typeof templateNode === "object") {
|
|
181
|
+
return Object.fromEntries(
|
|
182
|
+
Object.entries(templateNode).map(([key, value]) => [
|
|
183
|
+
mustache.render(key, context),
|
|
184
|
+
renderTemplateNode(value, context),
|
|
185
|
+
]),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (typeof templateNode !== "string") {
|
|
189
|
+
return templateNode;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Detect exact placeholder: entire value is "{{path}}" or "{{{path}}}".
|
|
193
|
+
// Matches both double-brace {{...}} and triple-brace {{{...}}} syntax.
|
|
194
|
+
// When matched, the resolved value is returned as its original type (e.g. an
|
|
195
|
+
// object), rather than being coerced to a string by Mustache.
|
|
196
|
+
const exactPlaceholder = templateNode.match(
|
|
197
|
+
/^\{\{\{?\s*([^{}]+?)\s*\}?\}\}$/,
|
|
198
|
+
);
|
|
199
|
+
if (exactPlaceholder) {
|
|
200
|
+
const resolvedValue = resolveTemplatePath(context, exactPlaceholder[1]);
|
|
201
|
+
if (resolvedValue !== undefined) {
|
|
202
|
+
return cloneTemplateValue(resolvedValue);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return mustache.render(templateNode, context);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resolves a dot/bracket-notation path against a context object.
|
|
211
|
+
* Supports paths like `"feature.geometry"` or `"list[0].name"`.
|
|
212
|
+
* @param {Record<string, any>} context
|
|
213
|
+
* @param {string} path
|
|
214
|
+
* @returns {any}
|
|
215
|
+
*/
|
|
216
|
+
export function resolveTemplatePath(context, path) {
|
|
217
|
+
const segments = path
|
|
218
|
+
.replace(/\[(\d+)\]/g, ".$1")
|
|
219
|
+
.split(".")
|
|
220
|
+
.map((segment) => segment.trim())
|
|
221
|
+
.filter(Boolean);
|
|
222
|
+
|
|
223
|
+
let currentValue = context;
|
|
224
|
+
for (const segment of segments) {
|
|
225
|
+
if (currentValue === null || currentValue === undefined) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
currentValue = currentValue[segment];
|
|
229
|
+
}
|
|
230
|
+
return currentValue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Deep-clones objects via JSON round-trip to prevent template rendering
|
|
235
|
+
* from mutating the original context values. This also strips non-JSON-
|
|
236
|
+
* serializable properties (e.g. OL Feature methods from drawtools),
|
|
237
|
+
* producing clean plain objects. Primitives pass through.
|
|
238
|
+
* @param {any} value
|
|
239
|
+
* @returns {any}
|
|
240
|
+
*/
|
|
241
|
+
export function cloneTemplateValue(value) {
|
|
242
|
+
if (value && typeof value === "object") {
|
|
243
|
+
return JSON.parse(JSON.stringify(value));
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
@@ -500,13 +500,17 @@ export function extractAsyncResults(resultItem) {
|
|
|
500
500
|
return extracted;
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
/** @param {*} jsonformSchema
|
|
504
|
-
|
|
503
|
+
/** @param {*} jsonformSchema
|
|
504
|
+
* @returns { string[] }
|
|
505
|
+
*/
|
|
506
|
+
export const getDrawToolsProperties = (jsonformSchema) => {
|
|
507
|
+
const properties = [];
|
|
505
508
|
for (const property in jsonformSchema.properties) {
|
|
506
509
|
if (jsonformSchema.properties[property]?.options?.drawtools) {
|
|
507
|
-
|
|
510
|
+
properties.push(property);
|
|
508
511
|
}
|
|
509
512
|
}
|
|
513
|
+
return properties;
|
|
510
514
|
};
|
|
511
515
|
|
|
512
516
|
/**
|
|
@@ -553,17 +557,3 @@ export const applyProcessLayersToMap = (mapElement, processLayers) => {
|
|
|
553
557
|
mapElement.layers = layers;
|
|
554
558
|
}
|
|
555
559
|
};
|
|
556
|
-
/**
|
|
557
|
-
* Updates the jsonform schema to target the compare map
|
|
558
|
-
* @param {Record<string,any> | null | undefined} jsonformSchema
|
|
559
|
-
*/
|
|
560
|
-
export function updateJsonformSchemaTarget(jsonformSchema) {
|
|
561
|
-
if (!jsonformSchema) {
|
|
562
|
-
return jsonformSchema;
|
|
563
|
-
}
|
|
564
|
-
const stringified = JSON.stringify(jsonformSchema).replaceAll(
|
|
565
|
-
"eox-map#main",
|
|
566
|
-
"eox-map#compare",
|
|
567
|
-
);
|
|
568
|
-
return JSON.parse(stringified);
|
|
569
|
-
}
|