@eodash/eodash 5.6.0 → 5.7.0
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/components/DashboardLayout.vue +1 -1
- package/core/client/composables/index.js +6 -2
- package/core/client/eodashSTAC/EodashCollection.js +5 -21
- package/core/client/eodashSTAC/createLayers.js +23 -9
- package/core/client/eodashSTAC/helpers.js +37 -4
- package/core/client/types.ts +8 -1
- package/core/client/utils/index.js +4 -0
- package/dist/client/{DashboardLayout-DkKDhfgH.js → DashboardLayout-kRsmsNW5.js} +5 -5
- package/dist/client/{DynamicWebComponent-BrpkA-e3.js → DynamicWebComponent-CnKhYo0K.js} +1 -1
- package/dist/client/{EodashChart-p7_1FRlF.js → EodashChart-CLDqE93C.js} +4 -4
- package/dist/client/{EodashDatePicker-HK0Jl7wQ.js → EodashDatePicker-CMPDT-ce.js} +3 -3
- package/dist/client/{EodashItemFilter-BYQDA78B.js → EodashItemFilter-BpdA8QQa.js} +1 -1
- package/dist/client/{EodashLayerControl-NcrNncGn.js → EodashLayerControl-BHpDA4_0.js} +25 -7
- package/dist/client/{EodashLayoutSwitcher-CC2Znd0I.js → EodashLayoutSwitcher-B1fRUt7G.js} +3 -3
- package/dist/client/EodashMapBtns-Cpned0tc.js +478 -0
- package/dist/client/{EodashStacInfo-BA-0UjvT.js → EodashStacInfo-BQspAHKH.js} +31 -31
- package/dist/client/{EodashTools-CWmoH0g_.js → EodashTools-1zHz26gv.js} +4 -4
- package/dist/client/{ExportState-CfAfX4RN.js → ExportState-DbOm8hBI.js} +4 -4
- package/dist/client/{Footer-DLGxQTZt.js → Footer-BKg-VVkj.js} +1 -1
- package/dist/client/{Header-eyEyyzD6.js → Header-DCrwCvmY.js} +2 -2
- package/dist/client/MobileLayout-vWSHpluo.js +118 -0
- package/dist/client/{PopUp-WNs6Io2N.js → PopUp-9uimfK49.js} +2 -2
- package/dist/client/{ProcessList-RzVqbd5E.js → ProcessList-CTTIZa06.js} +4 -4
- package/dist/client/{VImg-GKUTFoV-.js → VImg-CrmB76ZY.js} +2 -2
- package/dist/client/{VMain-D7_72mHr.js → VMain-OKZNpcEG.js} +1 -1
- package/dist/client/{VTooltip-BbmvtvYw.js → VTooltip-CJw2FyWk.js} +2 -2
- package/dist/client/{WidgetsContainer-Dw5d7SOW.js → WidgetsContainer-BaWOnE5R.js} +1 -1
- package/dist/client/{asWebComponent-DDt7V3Bl.js → asWebComponent-BHWpMuE3.js} +47 -35
- package/dist/client/{async-CT3KEV52.js → async-DSKj6g1f.js} +29 -28
- package/dist/client/create-layers-config-CBoYedqB.js +182 -0
- package/dist/client/eo-dash.js +1 -1
- package/dist/client/{forwardRefs-CVzxyoSq.js → forwardRefs-DH51zPUr.js} +2 -2
- package/dist/client/{handling-C285F_Mq.js → handling-BrcDDlgS.js} +452 -114
- package/dist/client/{helpers-CZFH0EVN.js → helpers-DjZVl_WF.js} +2277 -1504
- package/dist/client/{index-B-m7V3Kb.js → index-1JKE68n3.js} +49 -194
- package/dist/client/{index-CF3ZMzTz.js → index-5Jhsa8Xe.js} +3 -3
- package/dist/client/index-CSG_JoOA.js +436 -0
- package/dist/client/{index-DgOXD7wd.js → index-CUjl-ZsI.js} +2 -2
- package/dist/client/{index-B_6H-N14.js → index-Cu3-13Xg.js} +84 -90
- package/dist/client/material-symbols-outlined.woff2 +0 -0
- package/dist/client/material-symbols-rounded.woff2 +0 -0
- package/dist/client/material-symbols-sharp.woff2 +0 -0
- package/dist/client/material-symbols-subset.woff2 +0 -0
- package/dist/client/templates.js +1 -1
- package/dist/client/{transition-C2gnrWg0.js → transition-OHcz5Qq1.js} +1 -1
- package/dist/node/cli.js +1 -1
- package/dist/types/core/client/composables/index.d.ts +1 -3
- package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +6 -7
- package/dist/types/core/client/eodashSTAC/createLayers.d.ts +3 -3
- package/dist/types/core/client/eodashSTAC/helpers.d.ts +1 -0
- package/dist/types/core/client/types.d.ts +8 -1
- package/dist/types/templates/baseConfig.d.ts +12 -0
- package/dist/types/widgets/EodashLayerControl.vue.d.ts +1 -1
- package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +9 -0
- package/dist/types/widgets/EodashMap/index.vue.d.ts +7 -0
- package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -8
- package/dist/types/widgets/EodashProcess/methods/composables.d.ts +2 -3
- package/dist/types/widgets/EodashProcess/methods/handling.d.ts +15 -10
- 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/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +34 -0
- package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +39 -0
- package/dist/types/widgets/EodashTimeSlider/types.d.ts +62 -0
- package/package.json +32 -32
- package/widgets/EodashLayerControl.vue +22 -3
- package/widgets/EodashMap/EodashMapBtns.vue +46 -1
- package/widgets/EodashMap/index.vue +17 -5
- package/widgets/EodashMap/methods/create-layers-config.js +2 -0
- package/widgets/EodashMap/methods/index.js +23 -5
- package/widgets/EodashProcess/index.vue +17 -13
- package/widgets/EodashProcess/methods/async.js +19 -8
- package/widgets/EodashProcess/methods/composables.js +63 -74
- package/widgets/EodashProcess/methods/custom-endpoints/chart/sentinelhub-endpoint.js +9 -5
- package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +7 -4
- package/widgets/EodashProcess/methods/handling.js +125 -76
- 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
- package/widgets/EodashTimeSlider/index.vue +152 -0
- package/widgets/EodashTimeSlider/methods/index.js +279 -0
- package/widgets/EodashTimeSlider/types.ts +64 -0
- package/dist/client/EodashMapBtns-BJSh7qT-.js +0 -435
- package/dist/client/EodashTimeSlider-GDv1hbw7.js +0 -53
- package/dist/client/MobileLayout-RzLOyXwL.js +0 -118
- package/dist/types/widgets/EodashTimeSlider.vue.d.ts +0 -15
- package/widgets/EodashTimeSlider.vue +0 -40
|
@@ -1,15 +1,261 @@
|
|
|
1
1
|
import log from 'loglevel';
|
|
2
|
-
import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as
|
|
3
|
-
import { d as axios, b as extractLayerConfig,
|
|
2
|
+
import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as extractGeometries, i as applyProcessLayersToMap, g as getDrawToolsProperties } from './async-DSKj6g1f.js';
|
|
3
|
+
import { d as axios, b as extractLayerConfig, H as compareIndicator, E as indicator, x as extractCollectionUrls, G as currentCompareUrl, F as currentUrl, y as generateLinksFromItems, o as getDatetimeProperty, I as comparePoi, A as datetime, Z as compareChartData, _ as chartData, N as compareChartSpec, M as chartSpec, B as poi, Y as getCompareLayers, v as getLayers } from './helpers-DjZVl_WF.js';
|
|
4
4
|
import mustache from 'mustache';
|
|
5
|
-
import {
|
|
5
|
+
import { S as isFirstLoad, H as useSTAcStore, W as readParquetItems, X as useGetSubCodeId } from './asWebComponent-BHWpMuE3.js';
|
|
6
6
|
import { toAbsolute } from 'stac-js/src/http.js';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Returns form value keys that match the multiQuery configuration.
|
|
10
|
+
* @param {Record<string, any> | undefined} jsonformValue
|
|
11
|
+
* @param {Record<string, any>} jsonformSchema
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
function getMultiQueryMatches(jsonformValue, jsonformSchema) {
|
|
15
|
+
const multiQuery = jsonformSchema?.options?.multiQuery;
|
|
16
|
+
return Object.keys(jsonformValue ?? {}).filter((key) => {
|
|
17
|
+
return Array.isArray(multiQuery)
|
|
18
|
+
? multiQuery.includes(key)
|
|
19
|
+
: multiQuery === key;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extracts iterable scalar values for a multiQuery field.
|
|
25
|
+
* Wraps single values in an array; returns empty array for undefined.
|
|
26
|
+
* @param {string} match - The multiQuery field name
|
|
27
|
+
* @param {Record<string, any>} jsonformValue
|
|
28
|
+
* @returns {any[]}
|
|
29
|
+
*/
|
|
30
|
+
function getScalarMultiQueryValues(match, jsonformValue) {
|
|
31
|
+
const value = jsonformValue?.[match];
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
return value === undefined ? [] : [value];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extracts iterable values for a POST multiQuery field.
|
|
40
|
+
* For GeoJSON fields, uses rawJsonformValue to get original Feature objects
|
|
41
|
+
* (before extractGeometries stringifies them). For scalar fields, delegates
|
|
42
|
+
* to getScalarMultiQueryValues.
|
|
43
|
+
* @param {string} match - The multiQuery field name
|
|
44
|
+
* @param {Record<string, any>} jsonformValue - Form values after geometry extraction
|
|
45
|
+
* @param {Record<string, any> | undefined} rawJsonformValue - Form values before geometry extraction
|
|
46
|
+
* @param {Record<string, any>} jsonformSchema
|
|
47
|
+
* @returns {any[]}
|
|
48
|
+
*/
|
|
49
|
+
function getPostMultiQueryValues(
|
|
50
|
+
match,
|
|
51
|
+
jsonformValue,
|
|
52
|
+
rawJsonformValue,
|
|
53
|
+
jsonformSchema,
|
|
54
|
+
) {
|
|
55
|
+
if (jsonformSchema?.properties?.[match]?.type === "geojson") {
|
|
56
|
+
return getGeoJsonMultiQueryValues(rawJsonformValue?.[match]);
|
|
57
|
+
}
|
|
58
|
+
return getScalarMultiQueryValues(match, jsonformValue);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extracts individual GeoJSON Feature objects from a value that may be
|
|
63
|
+
* a FeatureCollection, a single Feature, or an array of Features.
|
|
64
|
+
* Filters for valid Features with geometry and deduplicates.
|
|
65
|
+
* @param {any} value
|
|
66
|
+
* @returns {Record<string, any>[]}
|
|
67
|
+
*/
|
|
68
|
+
function getGeoJsonMultiQueryValues(value) {
|
|
69
|
+
if (value?.type === "FeatureCollection") {
|
|
70
|
+
return dedupeMultiQueryValues(
|
|
71
|
+
(value.features ?? []).filter(
|
|
72
|
+
(/** @type {any} */ feature) =>
|
|
73
|
+
feature?.type === "Feature" && feature.geometry,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (value?.type === "Feature") {
|
|
78
|
+
return [value];
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
return dedupeMultiQueryValues(
|
|
82
|
+
value.filter(
|
|
83
|
+
(/** @type {any} */ feature) =>
|
|
84
|
+
feature?.type === "Feature" && feature.geometry,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Removes duplicate values using JSON serialization as the identity key.
|
|
93
|
+
* @param {any[]} values
|
|
94
|
+
* @returns {any[]}
|
|
95
|
+
*/
|
|
96
|
+
function dedupeMultiQueryValues(values) {
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
const deduped = [];
|
|
99
|
+
for (const value of values ?? []) {
|
|
100
|
+
const key =
|
|
101
|
+
value && typeof value === "object"
|
|
102
|
+
? JSON.stringify(value)
|
|
103
|
+
: String(value);
|
|
104
|
+
if (seen.has(key)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
seen.add(key);
|
|
108
|
+
deduped.push(value);
|
|
109
|
+
}
|
|
110
|
+
return deduped;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Builds the Mustache rendering context for one multiQuery iteration.
|
|
115
|
+
* Spreads the full form value, overrides the matched field with the current
|
|
116
|
+
* iteration value, and adds a 1-based `multiQueryIndex`.
|
|
117
|
+
* @param {Record<string, any>} jsonformValue
|
|
118
|
+
* @param {string} match - The multiQuery field name being iterated
|
|
119
|
+
* @param {any} value - The current iteration value
|
|
120
|
+
* @param {number} index - Zero-based iteration index
|
|
121
|
+
* @returns {Record<string, any>}
|
|
122
|
+
*/
|
|
123
|
+
function createMultiQueryContext(jsonformValue, match, value, index) {
|
|
124
|
+
return {
|
|
125
|
+
...(jsonformValue ?? {}),
|
|
126
|
+
[match]: value,
|
|
127
|
+
multiQueryIndex: index + 1,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Flattens an array of POST responses into a single values array.
|
|
133
|
+
* Each response that is itself an array is spread; scalars are wrapped.
|
|
134
|
+
* @param {any[]} values
|
|
135
|
+
* @returns {any[]}
|
|
136
|
+
*/
|
|
137
|
+
function aggregateInlineResponses(values) {
|
|
138
|
+
return values.flatMap((value) => (Array.isArray(value) ? value : [value]));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Renders a JSON body template string with the given context.
|
|
143
|
+
* Primary path: parses the template as JSON, then recursively renders each
|
|
144
|
+
* node (preserving object-valued placeholders as raw JSON).
|
|
145
|
+
* Fallback: treats the template as a Mustache string, renders, then parses.
|
|
146
|
+
* @param {string} bodyTemplate - JSON template string (may contain Mustache placeholders)
|
|
147
|
+
* @param {Record<string, any>} context - Values to substitute into the template
|
|
148
|
+
* @returns {any} Parsed JSON body ready for POST
|
|
149
|
+
*/
|
|
150
|
+
function renderJsonBodyTemplate(bodyTemplate, context) {
|
|
151
|
+
try {
|
|
152
|
+
const parsedTemplate = JSON.parse(bodyTemplate);
|
|
153
|
+
return renderTemplateNode(parsedTemplate, context);
|
|
154
|
+
} catch {
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(
|
|
157
|
+
mustache.render(bodyTemplate, {
|
|
158
|
+
...(context ?? {}),
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`[eodash] Failed to render POST body template: ${e instanceof Error ? e.message : String(e)}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Recursively renders a parsed JSON template node with context values.
|
|
171
|
+
* - Arrays and objects are traversed recursively.
|
|
172
|
+
* - Exact placeholders (where the entire string value is `"{{path}}"` or
|
|
173
|
+
* `"{{{path}}}"`) are resolved to their original typed value (object, array,
|
|
174
|
+
* number, etc.) instead of being stringified. This is critical for embedding
|
|
175
|
+
* GeoJSON geometry objects in POST bodies.
|
|
176
|
+
* - Partial placeholders and mixed strings use standard Mustache interpolation.
|
|
177
|
+
* @param {any} templateNode
|
|
178
|
+
* @param {Record<string, any>} context
|
|
179
|
+
* @returns {any}
|
|
180
|
+
*/
|
|
181
|
+
function renderTemplateNode(templateNode, context) {
|
|
182
|
+
if (Array.isArray(templateNode)) {
|
|
183
|
+
return templateNode.map((value) => renderTemplateNode(value, context));
|
|
184
|
+
}
|
|
185
|
+
if (templateNode && typeof templateNode === "object") {
|
|
186
|
+
return Object.fromEntries(
|
|
187
|
+
Object.entries(templateNode).map(([key, value]) => [
|
|
188
|
+
mustache.render(key, context),
|
|
189
|
+
renderTemplateNode(value, context),
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (typeof templateNode !== "string") {
|
|
194
|
+
return templateNode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Detect exact placeholder: entire value is "{{path}}" or "{{{path}}}".
|
|
198
|
+
// Matches both double-brace {{...}} and triple-brace {{{...}}} syntax.
|
|
199
|
+
// When matched, the resolved value is returned as its original type (e.g. an
|
|
200
|
+
// object), rather than being coerced to a string by Mustache.
|
|
201
|
+
const exactPlaceholder = templateNode.match(
|
|
202
|
+
/^\{\{\{?\s*([^{}]+?)\s*\}?\}\}$/,
|
|
203
|
+
);
|
|
204
|
+
if (exactPlaceholder) {
|
|
205
|
+
const resolvedValue = resolveTemplatePath(context, exactPlaceholder[1]);
|
|
206
|
+
if (resolvedValue !== undefined) {
|
|
207
|
+
return cloneTemplateValue(resolvedValue);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return mustache.render(templateNode, context);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Resolves a dot/bracket-notation path against a context object.
|
|
216
|
+
* Supports paths like `"feature.geometry"` or `"list[0].name"`.
|
|
217
|
+
* @param {Record<string, any>} context
|
|
218
|
+
* @param {string} path
|
|
219
|
+
* @returns {any}
|
|
220
|
+
*/
|
|
221
|
+
function resolveTemplatePath(context, path) {
|
|
222
|
+
const segments = path
|
|
223
|
+
.replace(/\[(\d+)\]/g, ".$1")
|
|
224
|
+
.split(".")
|
|
225
|
+
.map((segment) => segment.trim())
|
|
226
|
+
.filter(Boolean);
|
|
227
|
+
|
|
228
|
+
let currentValue = context;
|
|
229
|
+
for (const segment of segments) {
|
|
230
|
+
if (currentValue === null || currentValue === undefined) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
currentValue = currentValue[segment];
|
|
234
|
+
}
|
|
235
|
+
return currentValue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Deep-clones objects via JSON round-trip to prevent template rendering
|
|
240
|
+
* from mutating the original context values. This also strips non-JSON-
|
|
241
|
+
* serializable properties (e.g. OL Feature methods from drawtools),
|
|
242
|
+
* producing clean plain objects. Primitives pass through.
|
|
243
|
+
* @param {any} value
|
|
244
|
+
* @returns {any}
|
|
245
|
+
*/
|
|
246
|
+
function cloneTemplateValue(value) {
|
|
247
|
+
if (value && typeof value === "object") {
|
|
248
|
+
return JSON.parse(JSON.stringify(value));
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
|
|
8
253
|
////// --- CHARTS --- //////
|
|
9
254
|
/**
|
|
10
255
|
* @param {object} options
|
|
11
256
|
* @param {import("stac-ts").StacLink[] | undefined} options.links
|
|
12
257
|
* @param {Record<string,any> | undefined} options.jsonformValue
|
|
258
|
+
* @param {Record<string,any> | undefined} [options.rawJsonformValue]
|
|
13
259
|
* @param {string} options.specUrl
|
|
14
260
|
* @param {(input:import("^/EodashProcess/types").CustomEnpointInput)=>Promise<Record<string,any>[] | undefined | null>} [options.customEndpointsHandler]
|
|
15
261
|
* @param {import("vue").Ref<boolean>} options.isPolling
|
|
@@ -22,6 +268,7 @@ import { toAbsolute } from 'stac-js/src/http.js';
|
|
|
22
268
|
async function processCharts({
|
|
23
269
|
links,
|
|
24
270
|
jsonformValue,
|
|
271
|
+
rawJsonformValue,
|
|
25
272
|
specUrl,
|
|
26
273
|
customEndpointsHandler,
|
|
27
274
|
jsonformSchema,
|
|
@@ -51,6 +298,7 @@ async function processCharts({
|
|
|
51
298
|
(await customEndpointsHandler({
|
|
52
299
|
jsonformSchema,
|
|
53
300
|
jsonformValue,
|
|
301
|
+
rawJsonformValue,
|
|
54
302
|
links: endpointLinks,
|
|
55
303
|
selectedStac,
|
|
56
304
|
isPolling,
|
|
@@ -112,6 +360,7 @@ async function processCharts({
|
|
|
112
360
|
await injectVegaInlineData(spec, {
|
|
113
361
|
url: link.href,
|
|
114
362
|
jsonformValue: jsonformValue,
|
|
363
|
+
rawJsonformValue,
|
|
115
364
|
link: link,
|
|
116
365
|
flatstyleUrl: /** @type string */ (link["eox:flatstyle"]),
|
|
117
366
|
jsonformSchema,
|
|
@@ -138,46 +387,39 @@ async function processCharts({
|
|
|
138
387
|
* @param {object} injectables
|
|
139
388
|
* @param {string} injectables.url
|
|
140
389
|
* @param {Record<string,any>} [injectables.jsonformValue]
|
|
390
|
+
* @param {Record<string,any>} [injectables.rawJsonformValue]
|
|
141
391
|
* @param {import("stac-ts").StacLink} injectables.link
|
|
142
392
|
* @param {url} [injectables.flatstyleUrl]
|
|
143
393
|
* @param {import("json-schema").JSONSchema7} [injectables.jsonformSchema]
|
|
144
394
|
*/
|
|
145
395
|
async function injectVegaInlineData(
|
|
146
396
|
spec,
|
|
147
|
-
{ url, jsonformValue, link, flatstyleUrl, jsonformSchema },
|
|
397
|
+
{ url, jsonformValue, rawJsonformValue, link, flatstyleUrl, jsonformSchema },
|
|
148
398
|
) {
|
|
149
399
|
if (!spec.data) {
|
|
150
400
|
return;
|
|
151
401
|
}
|
|
402
|
+
// @ts-expect-error JSONSchema7 is structurally compatible with Record<string, any>
|
|
403
|
+
const multiQueryMatches = getMultiQueryMatches(jsonformValue, jsonformSchema);
|
|
152
404
|
if (link.method == "GET") {
|
|
153
|
-
|
|
154
|
-
// and if so, we can do multiple requests and merge all data together.
|
|
155
|
-
//@ts-expect-error type jsonform Schema
|
|
156
|
-
const multiQuery = jsonformSchema?.options?.multiQuery;
|
|
157
|
-
const matches = Object.keys(jsonformValue ?? {}).filter((key) => {
|
|
158
|
-
return Array.isArray(multiQuery)
|
|
159
|
-
? multiQuery.includes(key)
|
|
160
|
-
: multiQuery === key;
|
|
161
|
-
});
|
|
162
|
-
if (matches.length > 0 && jsonformValue) {
|
|
405
|
+
if (multiQueryMatches.length > 0 && jsonformValue) {
|
|
163
406
|
const dataValues = [];
|
|
164
|
-
for (const match of
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const dataUrl = await renderDataUrl(url, jsonformValue, flatstyleUrl);
|
|
407
|
+
for (const match of multiQueryMatches) {
|
|
408
|
+
const queryValues = getScalarMultiQueryValues(match, jsonformValue);
|
|
409
|
+
if (!queryValues.length) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
for (const value of queryValues) {
|
|
413
|
+
const dataUrl = await renderDataUrl(
|
|
414
|
+
url,
|
|
415
|
+
{ ...jsonformValue, [match]: value },
|
|
416
|
+
flatstyleUrl,
|
|
417
|
+
);
|
|
176
418
|
dataValues.push(await axios.get(dataUrl).then((resp) => resp.data));
|
|
177
419
|
}
|
|
178
420
|
}
|
|
179
421
|
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
180
|
-
(spec.data).values = dataValues
|
|
422
|
+
(spec.data).values = aggregateInlineResponses(dataValues);
|
|
181
423
|
return spec;
|
|
182
424
|
}
|
|
183
425
|
// if no array matches, we can just do a single request
|
|
@@ -201,11 +443,49 @@ async function injectVegaInlineData(
|
|
|
201
443
|
.then((resp) => {
|
|
202
444
|
return resp.data;
|
|
203
445
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
446
|
+
if (
|
|
447
|
+
// @ts-expect-error jsonform options extension not in JSONSchema7
|
|
448
|
+
jsonformSchema?.options?.multiQuery &&
|
|
449
|
+
multiQueryMatches.length === 0
|
|
450
|
+
) {
|
|
451
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
452
|
+
(spec.data).values = [];
|
|
453
|
+
return spec;
|
|
454
|
+
}
|
|
455
|
+
if (multiQueryMatches.length > 0 && jsonformValue) {
|
|
456
|
+
const responses = [];
|
|
457
|
+
for (const match of multiQueryMatches) {
|
|
458
|
+
const queryValues = getPostMultiQueryValues(
|
|
459
|
+
match,
|
|
460
|
+
jsonformValue,
|
|
461
|
+
rawJsonformValue,
|
|
462
|
+
/** @type {Record<string, any>} */ (jsonformSchema),
|
|
463
|
+
);
|
|
464
|
+
if (!queryValues.length) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
// Sequential POSTs (not parallel) to avoid rate-limiting on endpoints
|
|
468
|
+
// like TiTiler-PgSTAC. For small selection counts this is fine; if
|
|
469
|
+
// parallelism is needed later, switch to Promise.all.
|
|
470
|
+
for (const [index, value] of queryValues.entries()) {
|
|
471
|
+
const requestContext = createMultiQueryContext(
|
|
472
|
+
jsonformValue,
|
|
473
|
+
match,
|
|
474
|
+
value,
|
|
475
|
+
index,
|
|
476
|
+
);
|
|
477
|
+
const body = renderJsonBodyTemplate(bodyTemplate, requestContext);
|
|
478
|
+
responses.push(await axios.post(url, body).then((resp) => resp.data));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
482
|
+
(spec.data).values = aggregateInlineResponses(responses);
|
|
483
|
+
return spec;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const body = renderJsonBodyTemplate(bodyTemplate, {
|
|
487
|
+
...(jsonformValue ?? {}),
|
|
488
|
+
});
|
|
209
489
|
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
210
490
|
(spec.data).values = await axios.post(url, body).then((resp) => {
|
|
211
491
|
return resp.data;
|
|
@@ -214,6 +494,8 @@ async function injectVegaInlineData(
|
|
|
214
494
|
return spec;
|
|
215
495
|
}
|
|
216
496
|
|
|
497
|
+
// MultiQuery helpers and template rendering are in ./template-helpers.js
|
|
498
|
+
|
|
217
499
|
/**
|
|
218
500
|
* @param {import("vega-lite").TopLevelSpec} spec
|
|
219
501
|
* @param {object} injectables
|
|
@@ -651,7 +933,7 @@ async function handleSentinelHubProcess({
|
|
|
651
933
|
[jsonformValue["start_date"], jsonformValue["end_date"]],
|
|
652
934
|
jsonformValue["distribution"]
|
|
653
935
|
);
|
|
654
|
-
|
|
936
|
+
const results = await Promise.all(
|
|
655
937
|
timePairs.map(([to, from]) => {
|
|
656
938
|
return fetchSentinelHubData({
|
|
657
939
|
url: endpoint,
|
|
@@ -660,14 +942,16 @@ async function handleSentinelHubProcess({
|
|
|
660
942
|
from,
|
|
661
943
|
to,
|
|
662
944
|
exampleLink: evalScriptLink
|
|
663
|
-
}).catch(
|
|
664
|
-
|
|
945
|
+
}).catch((err) => {
|
|
946
|
+
console.error(
|
|
665
947
|
"[eodash] Error while fetching data from sentinel hub endpoint:",
|
|
666
948
|
err
|
|
667
|
-
)
|
|
668
|
-
|
|
949
|
+
);
|
|
950
|
+
return [];
|
|
951
|
+
});
|
|
669
952
|
})
|
|
670
|
-
).then((data) => data.flat().map((data2) => data2.outputs.data));
|
|
953
|
+
).then((data) => data.filter((result) => result.length > 0)).then((data) => data.flat().map((data2) => data2.outputs.data));
|
|
954
|
+
return results;
|
|
671
955
|
}
|
|
672
956
|
async function sentinelHubAuth(clientId, clientSecret) {
|
|
673
957
|
if (!clientId || !clientSecret) {
|
|
@@ -853,7 +1137,7 @@ async function handleVedaEndpoint({
|
|
|
853
1137
|
vedaLink,
|
|
854
1138
|
);
|
|
855
1139
|
// TODO: convert jsonform bbox type to geojson in the schema to avoid the conversion here
|
|
856
|
-
|
|
1140
|
+
const results = await Promise.all(
|
|
857
1141
|
configs.map(({ endpoint, datetime }) => {
|
|
858
1142
|
const url = new URL(vedaEndpoint);
|
|
859
1143
|
const key = vedaLink.endpoint === "veda_stac" ? "ids" : "url";
|
|
@@ -872,14 +1156,17 @@ async function handleVedaEndpoint({
|
|
|
872
1156
|
fetchedSats.date = datetime;
|
|
873
1157
|
return fetchedSats;
|
|
874
1158
|
})
|
|
875
|
-
.catch((resp) =>
|
|
1159
|
+
.catch((resp) => {
|
|
876
1160
|
console.error(
|
|
877
1161
|
"[eodash] Error while fetching data from veda endpoint:",
|
|
878
1162
|
resp,
|
|
879
|
-
)
|
|
880
|
-
|
|
1163
|
+
);
|
|
1164
|
+
return null;
|
|
1165
|
+
});
|
|
881
1166
|
}),
|
|
882
1167
|
);
|
|
1168
|
+
// Filter out the nulls (failed requests) before returning
|
|
1169
|
+
return results.filter((result) => result !== null);
|
|
883
1170
|
}
|
|
884
1171
|
|
|
885
1172
|
/**
|
|
@@ -1005,37 +1292,58 @@ function createCustomChartEndpointsHandler(callbacks) {
|
|
|
1005
1292
|
* @async
|
|
1006
1293
|
* @param {Object} params
|
|
1007
1294
|
* @param {import("vue").Ref<import("stac-ts").StacCollection | null>} params.selectedStac
|
|
1008
|
-
* @param {import("vue").Ref<import("@eox/jsonform").EOxJSONForm | null>} params.jsonformEl
|
|
1009
1295
|
* @param {import("vue").Ref<Record<string,any> | null>} params.jsonformSchema
|
|
1010
1296
|
* @param {import("vue").Ref<any[]>} params.processResults
|
|
1011
1297
|
* @param {import("vue").Ref<boolean>} params.isProcessed
|
|
1012
1298
|
* @param {import("vue").Ref<boolean>} params.loading
|
|
1013
1299
|
* @param {import("vue").Ref<boolean>} params.isPolling
|
|
1300
|
+
* @param {import("@eox/map").EOxMap | null} params.mapElement
|
|
1014
1301
|
* @param {boolean} params.enableCompare
|
|
1015
1302
|
*/
|
|
1016
1303
|
async function initProcess({
|
|
1017
1304
|
selectedStac,
|
|
1018
|
-
jsonformEl,
|
|
1019
1305
|
jsonformSchema,
|
|
1020
1306
|
isProcessed,
|
|
1021
1307
|
processResults,
|
|
1022
1308
|
loading,
|
|
1023
1309
|
isPolling,
|
|
1024
1310
|
enableCompare,
|
|
1311
|
+
mapElement,
|
|
1025
1312
|
}) {
|
|
1026
1313
|
const isPoiAlive = enableCompare ? !!comparePoi.value : !!poi.value;
|
|
1027
1314
|
let updatedJsonform = null;
|
|
1028
1315
|
if (selectedStac.value?.["eodash:jsonform"]) {
|
|
1029
|
-
updatedJsonform = await
|
|
1316
|
+
updatedJsonform = await fetch(
|
|
1030
1317
|
//@ts-expect-error eodash extention
|
|
1031
|
-
|
|
1032
|
-
|
|
1318
|
+
selectedStac.value["eodash:jsonform"],
|
|
1319
|
+
).then((resp) => resp.json());
|
|
1033
1320
|
}
|
|
1034
1321
|
|
|
1035
1322
|
if (!updatedJsonform && isPoiAlive) {
|
|
1036
1323
|
jsonformSchema.value = null;
|
|
1037
1324
|
return;
|
|
1038
1325
|
}
|
|
1326
|
+
|
|
1327
|
+
if (!updatedJsonform) {
|
|
1328
|
+
resetProcess({
|
|
1329
|
+
loading,
|
|
1330
|
+
isProcessed,
|
|
1331
|
+
jsonformSchema,
|
|
1332
|
+
isPolling,
|
|
1333
|
+
processResults,
|
|
1334
|
+
enableCompare,
|
|
1335
|
+
});
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const newJsonForm = await updateJsonformIdentifier({
|
|
1340
|
+
jsonformSchema: updatedJsonform,
|
|
1341
|
+
newLayers: enableCompare ? getCompareLayers() : getLayers(),
|
|
1342
|
+
enableCompare,
|
|
1343
|
+
mapElement,
|
|
1344
|
+
});
|
|
1345
|
+
if (!newJsonForm) return;
|
|
1346
|
+
|
|
1039
1347
|
resetProcess({
|
|
1040
1348
|
loading,
|
|
1041
1349
|
isProcessed,
|
|
@@ -1044,89 +1352,109 @@ async function initProcess({
|
|
|
1044
1352
|
processResults,
|
|
1045
1353
|
enableCompare,
|
|
1046
1354
|
});
|
|
1355
|
+
jsonformSchema.value = newJsonForm;
|
|
1356
|
+
}
|
|
1047
1357
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1358
|
+
/**
|
|
1359
|
+
* Recursively search grouped layer tree for a layer whose id starts with the given prefix
|
|
1360
|
+
*
|
|
1361
|
+
* @param {Record<string, any>[]} layers
|
|
1362
|
+
* @param {string} prefix
|
|
1363
|
+
* @returns {string | null}
|
|
1364
|
+
*/
|
|
1365
|
+
function findLayerIdByPrefix(layers, prefix) {
|
|
1366
|
+
for (const layer of layers) {
|
|
1367
|
+
if (layer.type === "Group" && Array.isArray(layer.layers)) {
|
|
1368
|
+
const found = findLayerIdByPrefix(layer.layers, prefix);
|
|
1369
|
+
if (found) return found;
|
|
1370
|
+
} else if (layer.properties?.id?.startsWith(prefix)) {
|
|
1371
|
+
return layer.properties.id;
|
|
1058
1372
|
}
|
|
1059
|
-
// trigger jsonform update in next tick
|
|
1060
|
-
jsonformSchema.value = null;
|
|
1061
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1062
|
-
jsonformSchema.value = newJsonForm;
|
|
1063
1373
|
}
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Wait until a layer's features are loaded for the drawtools to attach correctly
|
|
1379
|
+
* Resolves immediately if already loaded.
|
|
1380
|
+
*
|
|
1381
|
+
* @param {import("@eox/map").AnyLayerWithSource | null | undefined} layer
|
|
1382
|
+
* @returns {Promise<void>}
|
|
1383
|
+
*/
|
|
1384
|
+
function waitForLayerRender(layer) {
|
|
1385
|
+
return new Promise((resolve, reject) => {
|
|
1386
|
+
if (!layer || layer.get("type") !== "Vector" || !("getSource" in layer))
|
|
1387
|
+
return resolve();
|
|
1388
|
+
const source = /** @type {import("ol/source/Vector").default} */ (
|
|
1389
|
+
layer.getSource()
|
|
1390
|
+
);
|
|
1391
|
+
if (!source || source.getFeatures().length > 0) return resolve();
|
|
1392
|
+
source.once("featuresloadend", () => resolve());
|
|
1393
|
+
source.once("featuresloaderror", () =>
|
|
1394
|
+
reject(new Error(`Layer ${layer.get("id")} failed to load features`)),
|
|
1395
|
+
);
|
|
1396
|
+
});
|
|
1064
1397
|
}
|
|
1065
1398
|
|
|
1066
1399
|
/**
|
|
1067
|
-
* Update the jsonform schema to have the correct layer id from the map
|
|
1400
|
+
* Update the jsonform schema to have the correct layer id from the map.
|
|
1401
|
+
* Returns null if any required layerId cannot be resolved (layer not yet in map).
|
|
1402
|
+
* Callers should treat null as "retry when layers:updated fires".
|
|
1068
1403
|
*
|
|
1069
1404
|
* @export
|
|
1070
|
-
* @async
|
|
1071
1405
|
* @param {Object} params
|
|
1072
|
-
* @param {Record<string,any> | null} params.jsonformSchema
|
|
1073
|
-
* @param {Record<string, any>[] | undefined} params.newLayers
|
|
1074
|
-
* @
|
|
1406
|
+
* @param {Record<string,any> | null} params.jsonformSchema
|
|
1407
|
+
* @param {Record<string, any>[] | undefined} params.newLayers
|
|
1408
|
+
* @param {boolean} params.enableCompare
|
|
1409
|
+
* @param {import("@eox/map").EOxMap | null | undefined} params.mapElement
|
|
1410
|
+
* @returns {Promise<Record<string,any> | null>} updated schema, or null if a layerId could not be resolved
|
|
1075
1411
|
*/
|
|
1076
|
-
async function updateJsonformIdentifier({
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1412
|
+
async function updateJsonformIdentifier({
|
|
1413
|
+
jsonformSchema,
|
|
1414
|
+
newLayers,
|
|
1415
|
+
enableCompare,
|
|
1416
|
+
mapElement,
|
|
1417
|
+
}) {
|
|
1418
|
+
if (!jsonformSchema || !newLayers) return null;
|
|
1419
|
+
|
|
1420
|
+
const form = JSON.parse(JSON.stringify(jsonformSchema));
|
|
1421
|
+
const drawToolsProperties = getDrawToolsProperties(form);
|
|
1422
|
+
/** @type {Promise<void>[]} */
|
|
1423
|
+
const renderPromises = [];
|
|
1424
|
+
|
|
1425
|
+
for (const drawToolsProperty of drawToolsProperties) {
|
|
1426
|
+
if (!drawToolsProperty) continue;
|
|
1427
|
+
|
|
1428
|
+
const drawtoolsOptions =
|
|
1429
|
+
form?.properties[drawToolsProperty]?.options?.drawtools;
|
|
1430
|
+
if (!drawtoolsOptions) continue;
|
|
1431
|
+
|
|
1432
|
+
if (drawtoolsOptions.for && enableCompare) {
|
|
1433
|
+
drawtoolsOptions.for = "eox-map#compare";
|
|
1094
1434
|
}
|
|
1095
1435
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
for (const layer of layersArray) {
|
|
1109
|
-
if (layer.type === "Group" && Array.isArray(layer.layers)) {
|
|
1110
|
-
traverseLayers(layer.layers);
|
|
1111
|
-
} else {
|
|
1112
|
-
if (layer.properties?.id?.startsWith(layerId)) {
|
|
1113
|
-
matchedLayerId = layer.properties.id;
|
|
1114
|
-
break;
|
|
1115
|
-
}
|
|
1436
|
+
if (drawtoolsOptions.layerId) {
|
|
1437
|
+
let layer = mapElement?.getLayerById(drawtoolsOptions.layerId);
|
|
1438
|
+
if (!layer) {
|
|
1439
|
+
const prefix = drawtoolsOptions.layerId.split(";:;")[0];
|
|
1440
|
+
const resolvedId = findLayerIdByPrefix(newLayers, prefix);
|
|
1441
|
+
if (!resolvedId) {
|
|
1442
|
+
console.warn(
|
|
1443
|
+
`Could not find matching layer for processing form with id: ${prefix}`,
|
|
1444
|
+
);
|
|
1445
|
+
return null;
|
|
1116
1446
|
}
|
|
1447
|
+
drawtoolsOptions.layerId = resolvedId;
|
|
1448
|
+
layer = mapElement?.getLayerById(resolvedId);
|
|
1117
1449
|
}
|
|
1118
|
-
|
|
1119
|
-
traverseLayers(layers);
|
|
1120
|
-
if (matchedLayerId) {
|
|
1121
|
-
form.properties.feature.options.drawtools.layerId = matchedLayerId;
|
|
1122
|
-
return form;
|
|
1123
|
-
} else {
|
|
1124
|
-
console.warn(
|
|
1125
|
-
`Could not find matching layer for processing form with id: ${layerId}`,
|
|
1126
|
-
);
|
|
1127
|
-
return null;
|
|
1450
|
+
renderPromises.push(waitForLayerRender(layer));
|
|
1128
1451
|
}
|
|
1129
1452
|
}
|
|
1453
|
+
|
|
1454
|
+
await Promise.all(renderPromises).catch((err) => {
|
|
1455
|
+
console.warn("[eodash] layer load failed before jsonform attach:", err);
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1130
1458
|
return form;
|
|
1131
1459
|
}
|
|
1132
1460
|
|
|
@@ -1165,6 +1493,15 @@ async function handleProcesses({
|
|
|
1165
1493
|
);
|
|
1166
1494
|
|
|
1167
1495
|
const bboxProperty = getBboxProperty(jsonformSchema.value);
|
|
1496
|
+
// Preserve raw form value before extractGeometries mutates it.
|
|
1497
|
+
// Needed so POST multiQuery can iterate original GeoJSON Feature objects.
|
|
1498
|
+
// Uses JSON round-trip instead of structuredClone to handle OL Feature
|
|
1499
|
+
// objects from drawtools (which contain non-cloneable methods).
|
|
1500
|
+
const rawJsonformValue = JSON.parse(
|
|
1501
|
+
JSON.stringify(
|
|
1502
|
+
/** @type {Record<string, any>} */ (jsonformEl.value?.value ?? {}),
|
|
1503
|
+
),
|
|
1504
|
+
);
|
|
1168
1505
|
const jsonformValue = /** @type {Record<string,any>} */ (
|
|
1169
1506
|
jsonformEl.value?.value
|
|
1170
1507
|
);
|
|
@@ -1183,6 +1520,7 @@ async function handleProcesses({
|
|
|
1183
1520
|
[tempChartSpec, usedChartData.value] = await processCharts({
|
|
1184
1521
|
links: serviceLinks,
|
|
1185
1522
|
jsonformValue: { ...(jsonformValue ?? {}) },
|
|
1523
|
+
rawJsonformValue,
|
|
1186
1524
|
jsonformSchema: jsonformSchema.value,
|
|
1187
1525
|
enableCompare,
|
|
1188
1526
|
selectedStac: selectedStac.value,
|