@eodash/eodash 5.0.0 → 5.2.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/README.md +1 -0
- package/core/client/App.vue +8 -2
- package/core/client/asWebComponent.js +5 -5
- package/core/client/components/DashboardLayout.vue +43 -26
- package/core/client/components/EodashOverlay.vue +5 -6
- package/core/client/components/ErrorAlert.vue +2 -2
- package/core/client/components/Footer.vue +4 -4
- package/core/client/components/Header.vue +3 -3
- package/core/client/components/MobileLayout.vue +47 -27
- package/core/client/composables/DefineEodash.js +38 -43
- package/core/client/composables/DefineTemplate.js +4 -2
- package/core/client/composables/DefineWidgets.js +14 -8
- package/core/client/composables/index.js +273 -23
- package/core/client/eodashSTAC/EodashCollection.js +84 -62
- package/core/client/eodashSTAC/createLayers.js +30 -0
- package/core/client/eodashSTAC/helpers.js +159 -28
- package/core/client/eodashSTAC/parquet.js +145 -0
- package/core/client/eodashSTAC/triggers.js +6 -3
- package/core/client/plugins/index.js +4 -3
- package/core/client/plugins/vuetify.js +3 -0
- package/core/client/store/actions.js +21 -4
- package/core/client/store/stac.js +93 -56
- package/core/client/store/states.js +15 -5
- package/core/client/types.ts +59 -43
- package/core/client/utils/index.js +79 -0
- package/core/client/utils/keys.js +2 -2
- package/core/client/utils/states.js +30 -5
- package/core/client/views/Dashboard.vue +36 -32
- package/core/client/vite-env.d.ts +7 -0
- package/dist/client/{DashboardLayout-CkWvOMOW.js → DashboardLayout-Dq9Kfe6O.js} +24 -13
- package/dist/client/{DynamicWebComponent-DYBbpvUK.js → DynamicWebComponent-DCBMXskE.js} +1 -1
- package/dist/client/{EodashDatePicker-CALmW3SI.js → EodashDatePicker-DtngxU6s.js} +59 -32
- package/dist/client/{EodashItemFilter-DlQiE713.js → EodashItemFilter-ClQebJQt.js} +20 -10
- package/dist/client/{EodashLayerControl-DEzEbft7.js → EodashLayerControl-BLBds28C.js} +29 -16
- package/dist/client/EodashLayoutSwitcher-DQ8SfVDd.js +61 -0
- package/dist/client/EodashMapBtns-B89_YBDw.js +326 -0
- package/dist/client/{EodashStacInfo-DPPxDkF6.js → EodashStacInfo-Dt1nF06x.js} +3 -18
- package/dist/client/{EodashTools-CUaL9s4H.js → EodashTools-DV5ykmWc.js} +13 -13
- package/dist/client/{ExportState-DjyIZVhl.js → ExportState-B6zZQUmE.js} +57 -52
- package/dist/client/{Footer-DyL0JoWt.js → Footer-DNhXs8k6.js} +15 -13
- package/dist/client/{Header-B5Dgty9l.js → Header-BjhN5JY4.js} +32 -28
- package/dist/client/MobileLayout-JelB6w1G.js +118 -0
- package/dist/client/{PopUp-BfB8s_ki.js → PopUp-CgpvNr3o.js} +18 -10
- package/dist/client/ProcessList-vecpxThi.js +198 -0
- package/dist/client/{VImg-FD1WVphJ.js → VImg-CETuikH2.js} +221 -26
- package/dist/client/{VMain-DJKG4SvM.js → VMain-Ci9DyaGU.js} +7 -7
- package/dist/client/{VTooltip-CfeefrXI.js → VTooltip-J4ac48X7.js} +12 -10
- package/dist/client/{WidgetsContainer-C2TaTdb6.js → WidgetsContainer-CCML4TyV.js} +1 -1
- package/dist/client/asWebComponent-ZyEzWOOf.js +19092 -0
- package/dist/client/async-B7jIrM53.js +804 -0
- package/dist/client/eo-dash.js +1 -1
- package/dist/client/{VOverlay-BzOdRu9h.js → forwardRefs-BQclvjMq.js} +332 -28
- package/dist/client/handling-BS24aG1q.js +1227 -0
- package/dist/client/helpers-wXK7Ywio.js +4556 -0
- package/dist/client/index-4UCzZi8B.js +376 -0
- package/dist/client/{index-4CT7Tz83.js → index-9KR-G20t.js} +2 -2
- package/dist/client/{index-CIHH_3dW.js → index-B2XpdgR6.js} +227 -86
- 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/{ssrBoot-BP7SYRyC.js → ssrBoot-Zgc_Ttvi.js} +2 -2
- package/dist/client/templates.js +840 -0
- package/dist/client/transition-yBii4fu6.js +40 -0
- package/dist/node/cli.js +16 -6
- package/dist/node/types.d.ts +1 -1
- package/dist/types/core/client/App.vue.d.ts +2 -2
- package/dist/types/core/client/asWebComponent.d.ts +1 -1
- package/dist/types/core/client/components/DynamicWebComponent.vue.d.ts +1 -3
- package/dist/types/core/client/components/Footer.vue.d.ts +1 -105
- package/dist/types/core/client/components/IframeWrapper.vue.d.ts +1 -1
- package/dist/types/core/client/components/MobileLayout.vue.d.ts +1 -324
- package/dist/types/core/client/composables/DefineEodash.d.ts +2 -2
- package/dist/types/core/client/composables/DefineTemplate.d.ts +1 -1
- package/dist/types/core/client/composables/DefineWidgets.d.ts +4 -4
- package/dist/types/core/client/composables/index.d.ts +24 -2
- package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +9 -6
- package/dist/types/core/client/eodashSTAC/helpers.d.ts +25 -5
- package/dist/types/core/client/eodashSTAC/parquet.d.ts +2 -0
- package/dist/types/core/client/plugins/vuetify.d.ts +7 -4
- package/dist/types/core/client/store/actions.d.ts +3 -2
- package/dist/types/core/client/store/stac.d.ts +16 -13
- package/dist/types/core/client/store/states.d.ts +14 -4
- package/dist/types/core/client/types.d.ts +46 -31
- package/dist/types/core/client/utils/index.d.ts +2 -0
- package/dist/types/core/client/utils/keys.d.ts +4 -4
- package/dist/types/core/client/utils/states.d.ts +59 -47
- package/dist/types/core/client/views/Dashboard.vue.d.ts +2 -2
- package/dist/types/templates/baseConfig.d.ts +4 -0
- package/dist/types/templates/compare.d.ts +185 -0
- package/dist/types/templates/expert.d.ts +147 -0
- package/dist/types/templates/index.d.ts +6 -0
- package/dist/types/templates/light.d.ts +154 -0
- package/dist/types/widgets/EodashDatePicker.vue.d.ts +1 -458
- package/dist/types/widgets/EodashItemFilter.vue.d.ts +3 -3
- package/dist/types/widgets/EodashLayerControl.vue.d.ts +14 -7
- package/dist/types/widgets/EodashLayoutSwitcher.vue.d.ts +1 -3
- package/dist/types/widgets/{EodashMapBtns.vue.d.ts → EodashMap/EodashMapBtns.vue.d.ts} +12 -8
- package/dist/types/widgets/EodashMap/index.vue.d.ts +9 -4
- package/dist/types/widgets/EodashProcess/ProcessList.vue.d.ts +8 -1
- package/dist/types/widgets/EodashProcess/index.vue.d.ts +8 -4
- package/dist/types/widgets/EodashProcess/methods/async.d.ts +19 -18
- package/dist/types/widgets/EodashProcess/methods/composables.d.ts +3 -2
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/chart/index.d.ts +1 -0
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/chart/sentinelhub-endpoint.d.ts +6 -0
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.d.ts +4 -0
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +5 -0
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/index.d.ts +1 -0
- package/dist/types/widgets/EodashProcess/methods/handling.d.ts +12 -5
- package/dist/types/widgets/EodashProcess/methods/outputs.d.ts +72 -41
- package/dist/types/widgets/EodashProcess/methods/utils.d.ts +41 -21
- package/dist/types/widgets/EodashProcess/states.d.ts +11 -0
- package/dist/types/widgets/EodashProcess/types.d.ts +41 -0
- package/dist/types/widgets/EodashStacInfo.vue.d.ts +14 -14
- package/dist/types/widgets/EodashTools.vue.d.ts +3 -3
- package/dist/types/widgets/ExportState.vue.d.ts +1 -1
- package/dist/types/widgets/PopUp.vue.d.ts +11 -16
- package/dist/types/widgets/WidgetsContainer.vue.d.ts +3 -6
- package/package.json +55 -45
- package/templates/baseConfig.js +68 -0
- package/templates/compare.js +142 -0
- package/templates/expert.js +124 -0
- package/templates/index.js +8 -0
- package/templates/light.js +139 -0
- package/widgets/EodashDatePicker.vue +80 -31
- package/widgets/EodashItemFilter.vue +26 -11
- package/widgets/EodashLayerControl.vue +20 -11
- package/widgets/EodashLayoutSwitcher.vue +6 -3
- package/widgets/EodashMap/EodashMapBtns.vue +269 -0
- package/widgets/EodashMap/index.vue +255 -45
- package/widgets/EodashMap/methods/create-layers-config.js +4 -3
- package/widgets/EodashMap/methods/index.js +33 -23
- package/widgets/EodashProcess/ProcessList.vue +47 -11
- package/widgets/EodashProcess/index.vue +55 -20
- package/widgets/EodashProcess/methods/async.js +99 -60
- package/widgets/EodashProcess/methods/composables.js +21 -14
- package/widgets/EodashProcess/methods/custom-endpoints/chart/index.js +35 -0
- package/widgets/EodashProcess/methods/custom-endpoints/chart/sentinelhub-endpoint.js +275 -0
- package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +132 -0
- package/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.js +94 -0
- package/widgets/EodashProcess/methods/custom-endpoints/layers/index.js +33 -0
- package/widgets/EodashProcess/methods/handling.js +127 -80
- package/widgets/EodashProcess/methods/outputs.js +376 -125
- package/widgets/EodashProcess/methods/utils.js +442 -10
- package/widgets/EodashProcess/states.js +13 -0
- package/widgets/EodashProcess/types.ts +46 -0
- package/widgets/EodashStacInfo.vue +2 -17
- package/widgets/EodashTools.vue +13 -13
- package/widgets/WidgetsContainer.vue +1 -1
- package/core/client/eodash.js +0 -454
- package/dist/client/EodashLayoutSwitcher-CDeCV8F-.js +0 -52
- package/dist/client/EodashMapBtns-CktQCfa-.js +0 -131
- package/dist/client/MobileLayout-CRsg_5Q4.js +0 -1217
- package/dist/client/ProcessList-DTefwQZx.js +0 -484
- package/dist/client/asWebComponent-CLhcT715.js +0 -12479
- package/dist/client/eo-dash.css +0 -5
- package/dist/client/forwardRefs-Bon_Kku1.js +0 -245
- package/dist/client/index-Bm9cbtx5.js +0 -201
- package/dist/client/index-DiGDvTQU.js +0 -780
- package/dist/client/transition-C5I57hn6.js +0 -37
- package/dist/types/core/client/eodash.d.ts +0 -8
- package/widgets/EodashMapBtns.vue +0 -113
|
@@ -0,0 +1,1227 @@
|
|
|
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, g as getBboxProperty, b as generateTimePairs, f as updateJsonformSchemaTarget, h as extractGeometries, i as applyProcessLayersToMap } from './async-B7jIrM53.js';
|
|
3
|
+
import { f as axios, h as extractLayerConfig, D as compareIndicator, C as indicator, t as extractCollectionUrls, w as currentCompareUrl, x as currentUrl, u as generateLinksFromItems, k as getDatetimeProperty, y as datetime, E as comparePoi, z as poi } from './helpers-wXK7Ywio.js';
|
|
4
|
+
import mustache from 'mustache';
|
|
5
|
+
import { O as isFirstLoad, E as useSTAcStore, R as readParquetItems, S as useGetSubCodeId } from './asWebComponent-ZyEzWOOf.js';
|
|
6
|
+
import { toAbsolute } from 'stac-js/src/http.js';
|
|
7
|
+
|
|
8
|
+
////// --- CHARTS --- //////
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} options
|
|
11
|
+
* @param {import("stac-ts").StacLink[] | undefined} options.links
|
|
12
|
+
* @param {Record<string,any> | undefined} options.jsonformValue
|
|
13
|
+
* @param {string} options.specUrl
|
|
14
|
+
* @param {(input:import("^/EodashProcess/types").CustomEnpointInput)=>Promise<Record<string,any>[] | undefined | null>} [options.customEndpointsHandler]
|
|
15
|
+
* @param {import("vue").Ref<boolean>} options.isPolling
|
|
16
|
+
* @param {import("stac-ts").StacCollection} options.selectedStac
|
|
17
|
+
* @param {Record<string,any>} options.jsonformSchema
|
|
18
|
+
* @param {import("vue").Ref<import("../types").AsyncJob[]>} options.jobs
|
|
19
|
+
* @param {boolean} [options.enableCompare=false] - Whether to enable compare mode
|
|
20
|
+
* @returns {Promise<[import("@eox/chart").EOxChart["spec"] | null,Record<string,any>|null]>}
|
|
21
|
+
**/
|
|
22
|
+
async function processCharts({
|
|
23
|
+
links,
|
|
24
|
+
jsonformValue,
|
|
25
|
+
specUrl,
|
|
26
|
+
customEndpointsHandler,
|
|
27
|
+
jsonformSchema,
|
|
28
|
+
selectedStac,
|
|
29
|
+
isPolling,
|
|
30
|
+
jobs,
|
|
31
|
+
enableCompare = false,
|
|
32
|
+
}) {
|
|
33
|
+
if (!specUrl || !links) return [null, null];
|
|
34
|
+
/** @type {import("vega-lite").TopLevelSpec} **/
|
|
35
|
+
const spec = await axios.get(specUrl).then((resp) => {
|
|
36
|
+
return resp.data;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** @type {Record<string,any>} */
|
|
40
|
+
const dataValues = {};
|
|
41
|
+
|
|
42
|
+
const [standardLinks, endpointLinks] = separateEndpointLinks(
|
|
43
|
+
links,
|
|
44
|
+
"service",
|
|
45
|
+
undefined,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const data =
|
|
49
|
+
customEndpointsHandler &&
|
|
50
|
+
jsonformValue &&
|
|
51
|
+
(await customEndpointsHandler({
|
|
52
|
+
jsonformSchema,
|
|
53
|
+
jsonformValue,
|
|
54
|
+
links: endpointLinks,
|
|
55
|
+
selectedStac,
|
|
56
|
+
isPolling,
|
|
57
|
+
jobs,
|
|
58
|
+
enableCompare,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
if (data && data.length) {
|
|
62
|
+
//@ts-expect-error we assume data to exist in spec
|
|
63
|
+
spec.data.values = data;
|
|
64
|
+
return [spec, dataValues];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const dataLinks = standardLinks.filter((link) => link.rel === "service");
|
|
68
|
+
try {
|
|
69
|
+
checkForData: for (const link of dataLinks ?? []) {
|
|
70
|
+
switch (link.type) {
|
|
71
|
+
case undefined:
|
|
72
|
+
continue;
|
|
73
|
+
case "application/json":
|
|
74
|
+
await injectVegaInlineData(spec, {
|
|
75
|
+
url: link.href,
|
|
76
|
+
jsonformValue: jsonformValue,
|
|
77
|
+
link: link,
|
|
78
|
+
flatstyleUrl: /** @type string */ (link["eox:flatstyle"]),
|
|
79
|
+
jsonformSchema,
|
|
80
|
+
});
|
|
81
|
+
break checkForData;
|
|
82
|
+
case "text/csv":
|
|
83
|
+
await injectVegaUrlData(spec, {
|
|
84
|
+
url: link.href,
|
|
85
|
+
jsonformValue: jsonformValue,
|
|
86
|
+
flatstyleUrl: /** @type string */ (link["eox:flatstyle"]),
|
|
87
|
+
});
|
|
88
|
+
break checkForData;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error("[eodash] Error while injecting Vega data", e);
|
|
93
|
+
}
|
|
94
|
+
return [spec, dataValues];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param {import("vega-lite").TopLevelSpec} spec
|
|
100
|
+
* @param {object} injectables
|
|
101
|
+
* @param {string} injectables.url
|
|
102
|
+
* @param {Record<string,any>} [injectables.jsonformValue]
|
|
103
|
+
* @param {import("stac-ts").StacLink} injectables.link
|
|
104
|
+
* @param {url} [injectables.flatstyleUrl]
|
|
105
|
+
* @param {import("json-schema").JSONSchema7} [injectables.jsonformSchema]
|
|
106
|
+
*/
|
|
107
|
+
async function injectVegaInlineData(
|
|
108
|
+
spec,
|
|
109
|
+
{ url, jsonformValue, link, flatstyleUrl, jsonformSchema },
|
|
110
|
+
) {
|
|
111
|
+
if (!spec.data) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (link.method == "GET") {
|
|
115
|
+
// we see if any of the multiQuery values match an array in the jsonformValue
|
|
116
|
+
// and if so, we can do multiple requests and merge all data together.
|
|
117
|
+
//@ts-expect-error type jsonform Schema
|
|
118
|
+
const multiQuery = jsonformSchema?.options?.multiQuery;
|
|
119
|
+
const matches = Object.keys(jsonformValue ?? {}).filter((key) => {
|
|
120
|
+
return Array.isArray(multiQuery)
|
|
121
|
+
? multiQuery.includes(key)
|
|
122
|
+
: multiQuery === key;
|
|
123
|
+
});
|
|
124
|
+
if (matches.length > 0 && jsonformValue) {
|
|
125
|
+
const dataValues = [];
|
|
126
|
+
for (const match of matches) {
|
|
127
|
+
if (Array.isArray(jsonformValue[match])) {
|
|
128
|
+
for (const value of jsonformValue[match]) {
|
|
129
|
+
const dataUrl = await renderDataUrl(
|
|
130
|
+
url,
|
|
131
|
+
{ ...jsonformValue, [match]: value },
|
|
132
|
+
flatstyleUrl,
|
|
133
|
+
);
|
|
134
|
+
dataValues.push(await axios.get(dataUrl).then((resp) => resp.data));
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
const dataUrl = await renderDataUrl(url, jsonformValue, flatstyleUrl);
|
|
138
|
+
dataValues.push(await axios.get(dataUrl).then((resp) => resp.data));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
142
|
+
(spec.data).values = dataValues.flat();
|
|
143
|
+
return spec;
|
|
144
|
+
}
|
|
145
|
+
// if no array matches, we can just do a single request
|
|
146
|
+
const dataUrl = await renderDataUrl(url, jsonformValue, flatstyleUrl);
|
|
147
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
148
|
+
(spec.data).values = await axios.get(dataUrl).then((resp) => {
|
|
149
|
+
return resp.data;
|
|
150
|
+
});
|
|
151
|
+
} else if (link.method == "POST") {
|
|
152
|
+
// get body template to be used in POST request, check first if available
|
|
153
|
+
if (!link.body) {
|
|
154
|
+
console.error(
|
|
155
|
+
"[eodash] Inline data POST request requires a body template",
|
|
156
|
+
);
|
|
157
|
+
return spec;
|
|
158
|
+
}
|
|
159
|
+
/** @type {string} */
|
|
160
|
+
const bodyTemplate = await axios
|
|
161
|
+
// @ts-expect-error we assume link.body to be a string, not defined in stac-ts
|
|
162
|
+
.get(link.body, { responseType: "text" })
|
|
163
|
+
.then((resp) => {
|
|
164
|
+
return resp.data;
|
|
165
|
+
});
|
|
166
|
+
const body = JSON.parse(
|
|
167
|
+
mustache.render(bodyTemplate, {
|
|
168
|
+
...(jsonformValue ?? {}),
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
/** @type {import("vega-lite/build/src/data").InlineData} */
|
|
172
|
+
(spec.data).values = await axios.post(url, body).then((resp) => {
|
|
173
|
+
return resp.data;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return spec;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {import("vega-lite").TopLevelSpec} spec
|
|
181
|
+
* @param {object} injectables
|
|
182
|
+
* @param {string} injectables.url
|
|
183
|
+
* @param {Record<string,any>} [injectables.jsonformValue]
|
|
184
|
+
* @param {url} [injectables.flatstyleUrl]
|
|
185
|
+
*/
|
|
186
|
+
async function injectVegaUrlData(spec, { url, jsonformValue, flatstyleUrl }) {
|
|
187
|
+
if (!spec.data) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const dataUrl = await renderDataUrl(url, jsonformValue, flatstyleUrl);
|
|
191
|
+
/** @type {import("vega").UrlData} */
|
|
192
|
+
(spec.data).url = dataUrl;
|
|
193
|
+
return spec;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
*
|
|
197
|
+
* @param {string} url
|
|
198
|
+
* @param {Record<string,any>} [jsonformValue]
|
|
199
|
+
* @param {string} [flatstyleUrl]
|
|
200
|
+
*/
|
|
201
|
+
async function renderDataUrl(url, jsonformValue, flatstyleUrl) {
|
|
202
|
+
let flatStyles = {};
|
|
203
|
+
if (flatstyleUrl) {
|
|
204
|
+
flatStyles = await axios.get(flatstyleUrl).then((resp) => resp.data);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return mustache.render(url, {
|
|
208
|
+
...(jsonformValue ?? {}),
|
|
209
|
+
...flatStyles,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/////// MAP LAYERS ///////
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {object} options
|
|
217
|
+
* @param {import("stac-ts").StacLink[] | undefined} options.links
|
|
218
|
+
* @param {Record<string,any> | undefined} options.jsonformValue
|
|
219
|
+
* @param {string} options.layerId
|
|
220
|
+
* @param {string} [options.projection]
|
|
221
|
+
*/
|
|
222
|
+
async function processGeoTiff({
|
|
223
|
+
links,
|
|
224
|
+
jsonformValue,
|
|
225
|
+
layerId,
|
|
226
|
+
projection,
|
|
227
|
+
}) {
|
|
228
|
+
if (!links) return;
|
|
229
|
+
|
|
230
|
+
const [geotiffLinks, _] = separateEndpointLinks(
|
|
231
|
+
links,
|
|
232
|
+
"service",
|
|
233
|
+
"image/tiff",
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (!geotiffLinks.length) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let urls = [];
|
|
240
|
+
let processId = "";
|
|
241
|
+
for (const link of geotiffLinks ?? []) {
|
|
242
|
+
urls.push(mustache.render(link.href, { ...(jsonformValue ?? {}) }));
|
|
243
|
+
}
|
|
244
|
+
const definitions = await Promise.all(
|
|
245
|
+
geotiffLinks.map((geotiffLink) =>
|
|
246
|
+
createTiffLayerDefinition(
|
|
247
|
+
geotiffLink,
|
|
248
|
+
layerId,
|
|
249
|
+
urls,
|
|
250
|
+
projection,
|
|
251
|
+
processId,
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
).then((defs) => defs.filter((defs) => !!defs));
|
|
255
|
+
return definitions;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @param {import("stac-ts").StacLink[] | undefined} links
|
|
260
|
+
* @param {Record<string,any>|undefined} jsonformValue
|
|
261
|
+
* @param {number[]} origBbox
|
|
262
|
+
*/
|
|
263
|
+
function processImage(links, jsonformValue, origBbox) {
|
|
264
|
+
if (!links) return;
|
|
265
|
+
const imageLinks = links.filter(
|
|
266
|
+
(link) => link.rel === "service" && link.type === "image/png",
|
|
267
|
+
);
|
|
268
|
+
/** @type {import("@eox/map/src/layers").EOxLayerType<"Image","ImageStatic">[]} */
|
|
269
|
+
const layers = [];
|
|
270
|
+
for (const link of imageLinks) {
|
|
271
|
+
layers.push({
|
|
272
|
+
type: "Image",
|
|
273
|
+
properties: {
|
|
274
|
+
id: link.id + "_process",
|
|
275
|
+
title: "Results " + link.id,
|
|
276
|
+
},
|
|
277
|
+
source: {
|
|
278
|
+
type: "ImageStatic",
|
|
279
|
+
imageExtent: origBbox,
|
|
280
|
+
url: mustache.render(link.href, {
|
|
281
|
+
...(jsonformValue ?? {}),
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return layers;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {import("stac-ts").StacLink[] | undefined} links
|
|
291
|
+
* @param {Record<string,any> | undefined} jsonformValue
|
|
292
|
+
* @param {string} layerId
|
|
293
|
+
*/
|
|
294
|
+
async function processVector(links, jsonformValue, layerId) {
|
|
295
|
+
if (!links) return;
|
|
296
|
+
/** @type {import("@eox/map/src/layers").EOxLayerType<"Vector",any>[]} */
|
|
297
|
+
const layers = [];
|
|
298
|
+
const vectorLinks = links.filter(
|
|
299
|
+
(link) => link.rel === "service" && link.type === "application/geo+json",
|
|
300
|
+
);
|
|
301
|
+
if (!vectorLinks.length) return layers;
|
|
302
|
+
|
|
303
|
+
let flatStyleJSON = null;
|
|
304
|
+
|
|
305
|
+
for (const link of vectorLinks) {
|
|
306
|
+
if ("eox:flatstyle" in (link ?? {})) {
|
|
307
|
+
flatStyleJSON = await axios
|
|
308
|
+
.get(/** @type {string} */ (link["eox:flatstyle"]))
|
|
309
|
+
.then((resp) => resp.data);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** @type {Record<string,any>|undefined} */
|
|
313
|
+
let layerConfig;
|
|
314
|
+
/** @type {Record<string,any>|undefined} */
|
|
315
|
+
let style;
|
|
316
|
+
if (flatStyleJSON) {
|
|
317
|
+
const extracted = extractLayerConfig(layerId ?? "", flatStyleJSON);
|
|
318
|
+
layerConfig = extracted.layerConfig;
|
|
319
|
+
style = extracted.style;
|
|
320
|
+
}
|
|
321
|
+
/** @type {import("@eox/map/src/layers").EOxLayerType<"Vector","Vector"|"FlatGeoBuf">} */
|
|
322
|
+
const layer = {
|
|
323
|
+
type: "Vector",
|
|
324
|
+
source: {
|
|
325
|
+
type: "Vector",
|
|
326
|
+
url: mustache.render(link.href, {
|
|
327
|
+
...(jsonformValue ?? {}),
|
|
328
|
+
}),
|
|
329
|
+
format: "GeoJSON",
|
|
330
|
+
},
|
|
331
|
+
properties: {
|
|
332
|
+
id: link.id + "_process",
|
|
333
|
+
title: "Results " + layerId,
|
|
334
|
+
...(layerConfig && { ...layerConfig }),
|
|
335
|
+
},
|
|
336
|
+
...(style && { style }),
|
|
337
|
+
};
|
|
338
|
+
layers.push(layer);
|
|
339
|
+
}
|
|
340
|
+
return layers;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Unified wrapper for processing map layer types (Vector, Image, GeoTiff)
|
|
345
|
+
* @param {object} options
|
|
346
|
+
* @param {import("stac-ts").StacLink[] | undefined} options.links
|
|
347
|
+
* @param {Record<string,any> | undefined} options.jsonformValue
|
|
348
|
+
* @param {string} options.layerId
|
|
349
|
+
* @param {string} [options.projection] - Required for GeoTiff layers
|
|
350
|
+
* @param {number[]} options.origBbox - Required for Image layers
|
|
351
|
+
* @param {import("vue").Ref<boolean>} options.isPolling
|
|
352
|
+
* @param {import("stac-ts").StacCollection} options.selectedStac
|
|
353
|
+
* @param {import("json-schema").JSONSchema7} options.jsonformSchema
|
|
354
|
+
* @param {import("vue").Ref<import("../types").AsyncJob[]>} options.jobs
|
|
355
|
+
* @param {(input:import("../types").CustomEnpointInput)=>Promise<import("@eox/map").EoxLayer[]>} options.customLayersHandler
|
|
356
|
+
* @param {boolean} [options.enableCompare=false] - Whether to enable compare mode
|
|
357
|
+
*/
|
|
358
|
+
async function processLayers({
|
|
359
|
+
links,
|
|
360
|
+
jsonformValue,
|
|
361
|
+
layerId,
|
|
362
|
+
projection,
|
|
363
|
+
origBbox,
|
|
364
|
+
isPolling,
|
|
365
|
+
selectedStac,
|
|
366
|
+
jsonformSchema,
|
|
367
|
+
jobs,
|
|
368
|
+
customLayersHandler,
|
|
369
|
+
enableCompare = false,
|
|
370
|
+
}) {
|
|
371
|
+
if (!links) return [];
|
|
372
|
+
/** @type {import("@eox/map").EoxLayer[]} */
|
|
373
|
+
const layers = [];
|
|
374
|
+
|
|
375
|
+
const [standardLinks, endpointLinks] = separateEndpointLinks(
|
|
376
|
+
links,
|
|
377
|
+
"service",
|
|
378
|
+
undefined,
|
|
379
|
+
);
|
|
380
|
+
// Handle custom endpoints first if handler is provided
|
|
381
|
+
if (customLayersHandler && jsonformValue && selectedStac && jsonformSchema) {
|
|
382
|
+
if (endpointLinks.length > 0) {
|
|
383
|
+
const customLayers = await customLayersHandler({
|
|
384
|
+
jsonformValue,
|
|
385
|
+
links: endpointLinks,
|
|
386
|
+
selectedStac,
|
|
387
|
+
isPolling,
|
|
388
|
+
jsonformSchema,
|
|
389
|
+
jobs,
|
|
390
|
+
enableCompare,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (customLayers.length) {
|
|
394
|
+
layers.push(...customLayers);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const vectorlayers = await processVector(
|
|
400
|
+
standardLinks,
|
|
401
|
+
jsonformValue,
|
|
402
|
+
layerId,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const imagelayers = processImage(standardLinks, jsonformValue, origBbox);
|
|
406
|
+
|
|
407
|
+
const geotiffLayers = await processGeoTiff({
|
|
408
|
+
links: standardLinks,
|
|
409
|
+
jsonformValue,
|
|
410
|
+
layerId,
|
|
411
|
+
projection,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
layers.push(
|
|
415
|
+
...[
|
|
416
|
+
...(vectorlayers ?? []),
|
|
417
|
+
...(imagelayers ?? []),
|
|
418
|
+
...(geotiffLayers ?? []),
|
|
419
|
+
],
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
return layers;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
////// STAC PROCESSING /////
|
|
426
|
+
/**
|
|
427
|
+
* This function loads a STAC collection as a processing output.
|
|
428
|
+
* Currently, it only supports POI STAC collections
|
|
429
|
+
*
|
|
430
|
+
* @param {import("stac-ts").StacLink[]} links
|
|
431
|
+
* @param {Record<string,any>} jsonformValue
|
|
432
|
+
*/
|
|
433
|
+
async function processSTAC(links, jsonformValue, enableCompare = false) {
|
|
434
|
+
const stacLink = links.find(
|
|
435
|
+
(link) =>
|
|
436
|
+
link.rel === "service" &&
|
|
437
|
+
link.type == "application/json; profile=collection" &&
|
|
438
|
+
link.endpoint === "STAC",
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
if (!stacLink) return;
|
|
442
|
+
let poiUrl = mustache.render(stacLink.href, {
|
|
443
|
+
...(jsonformValue ?? {}),
|
|
444
|
+
});
|
|
445
|
+
if (isFirstLoad.value) {
|
|
446
|
+
// prevent the map from jumping to the initial position
|
|
447
|
+
isFirstLoad.value = false;
|
|
448
|
+
}
|
|
449
|
+
const store = useSTAcStore();
|
|
450
|
+
const loadPOI = enableCompare
|
|
451
|
+
? store.loadSelectedCompareSTAC
|
|
452
|
+
: store.loadSelectedSTAC;
|
|
453
|
+
await loadPOI(poiUrl, true);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
*
|
|
458
|
+
* @param {import("^/EodashProcess/types").CustomEnpointInput} param0
|
|
459
|
+
*/
|
|
460
|
+
|
|
461
|
+
async function handleEOxHubEndpoint({
|
|
462
|
+
links,
|
|
463
|
+
jsonformValue,
|
|
464
|
+
isPolling,
|
|
465
|
+
selectedStac,
|
|
466
|
+
jobs,
|
|
467
|
+
enableCompare = false,
|
|
468
|
+
}) {
|
|
469
|
+
if (!isPolling) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const eoxhubLinks = links.filter(
|
|
473
|
+
(link) => link.rel === "service" && link.endpoint === "eoxhub_workspaces",
|
|
474
|
+
);
|
|
475
|
+
const layers = [];
|
|
476
|
+
for (const link of eoxhubLinks) {
|
|
477
|
+
// TODO: prove of concept, needs to be reworked for sure
|
|
478
|
+
// Special handling for eoxhub workspace process endpoints
|
|
479
|
+
const postBody = await axios
|
|
480
|
+
.get(/** @type {string} */ (link["body"]), { responseType: "text" })
|
|
481
|
+
.then((resp) => resp.data);
|
|
482
|
+
const jsonData = JSON.parse(
|
|
483
|
+
mustache.render(postBody, { ...(jsonformValue ?? {}) }),
|
|
484
|
+
);
|
|
485
|
+
const currentIndicator = enableCompare ? compareIndicator : indicator;
|
|
486
|
+
try {
|
|
487
|
+
const responseProcess = await axios.post(link.href, jsonData, {
|
|
488
|
+
headers: {
|
|
489
|
+
"Content-Type": "application/json",
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// We save the process status url into localstorage assigning it to the indicator id
|
|
494
|
+
const currentJobs = JSON.parse(
|
|
495
|
+
localStorage.getItem(currentIndicator.value) || "[]",
|
|
496
|
+
);
|
|
497
|
+
currentJobs.push(responseProcess.headers.location);
|
|
498
|
+
localStorage.setItem(currentIndicator.value, JSON.stringify(currentJobs));
|
|
499
|
+
|
|
500
|
+
const processResults = await pollProcessStatus({
|
|
501
|
+
jobs,
|
|
502
|
+
processUrl: responseProcess.headers.location,
|
|
503
|
+
isPolling,
|
|
504
|
+
enableCompare,
|
|
505
|
+
})
|
|
506
|
+
.then((resultItem) => {
|
|
507
|
+
return extractAsyncResults(resultItem);
|
|
508
|
+
})
|
|
509
|
+
.catch((error) => {
|
|
510
|
+
if (error instanceof Error) {
|
|
511
|
+
console.error("Polling failed:", error.message);
|
|
512
|
+
} else {
|
|
513
|
+
console.error("Unknown error occurred during polling:", error);
|
|
514
|
+
}
|
|
515
|
+
return [];
|
|
516
|
+
});
|
|
517
|
+
await updateJobsStatus(jobs, currentIndicator.value);
|
|
518
|
+
|
|
519
|
+
layers.push(
|
|
520
|
+
...(await creatAsyncProcessLayerDefinitions(
|
|
521
|
+
processResults,
|
|
522
|
+
link,
|
|
523
|
+
selectedStac,
|
|
524
|
+
)),
|
|
525
|
+
);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
await updateJobsStatus(jobs, currentIndicator.value);
|
|
528
|
+
if (error instanceof Error) {
|
|
529
|
+
console.error("Error sending POST request:", error.message);
|
|
530
|
+
} else {
|
|
531
|
+
console.error("Unknown error occurred:", error);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return layers;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const handleLayersCustomEndpoints = createCustomLayersEndpointsHandler([
|
|
540
|
+
handleEOxHubEndpoint,
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* @param {((input:import("^/EodashProcess/types").CustomEnpointInput)=> Promise<Record<string,any>[] | undefined | null>)[]} callbacks
|
|
545
|
+
* @returns {(input: import("^/EodashProcess/types").CustomEnpointInput) => Promise<import("@eox/map/.").EoxLayer[]>}
|
|
546
|
+
*/
|
|
547
|
+
function createCustomLayersEndpointsHandler(callbacks) {
|
|
548
|
+
return async (inputs) => {
|
|
549
|
+
// this allows multiple endpoints links to exist
|
|
550
|
+
// and return multiple layers
|
|
551
|
+
return await Promise.all(
|
|
552
|
+
callbacks.map((callback) => callback(inputs)),
|
|
553
|
+
).then((asyncProcessesLayers) => {
|
|
554
|
+
const layers = [];
|
|
555
|
+
for (const processLayers of asyncProcessesLayers) {
|
|
556
|
+
layers.push(...(processLayers?.filter(isValidEoxLayer) ?? []));
|
|
557
|
+
}
|
|
558
|
+
return layers;
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* @param {any} layer
|
|
565
|
+
* @returns {layer is import("@eox/map/.").EoxLayer}
|
|
566
|
+
*/
|
|
567
|
+
function isValidEoxLayer(layer) {
|
|
568
|
+
return !!layer && !!layer.type && (!!layer.source || !!layer.layers);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function handleSentinelHubProcess({
|
|
572
|
+
links,
|
|
573
|
+
jsonformValue,
|
|
574
|
+
jsonformSchema,
|
|
575
|
+
selectedStac,
|
|
576
|
+
enableCompare = false
|
|
577
|
+
}) {
|
|
578
|
+
const sentinelHubLink = links.find(
|
|
579
|
+
(link) => link.rel === "service" && link.endpoint === "sentinelhub"
|
|
580
|
+
);
|
|
581
|
+
if (!sentinelHubLink) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const evalScriptLink = await getEvalScriptLink(
|
|
585
|
+
selectedStac,
|
|
586
|
+
enableCompare ? currentCompareUrl.value : currentUrl.value
|
|
587
|
+
);
|
|
588
|
+
if (!evalScriptLink) {
|
|
589
|
+
console.error(
|
|
590
|
+
"[eodash] evalscript link for sentinel hub not found in indicator",
|
|
591
|
+
evalScriptLink
|
|
592
|
+
);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (!sentinelHubLink) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const endpoint = sentinelHubLink.href;
|
|
599
|
+
const bboxProperty = getBboxProperty(jsonformSchema);
|
|
600
|
+
const bbox = jsonformValue[bboxProperty];
|
|
601
|
+
const clientId = import.meta.env.EODASH_SENTINELHUB_CLIENT_ID;
|
|
602
|
+
const clientSecret = import.meta.env.EODASH_SENTINELHUB_CLIENT_SECRET;
|
|
603
|
+
const bearer = await sentinelHubAuth(clientId, clientSecret);
|
|
604
|
+
if (!bearer) {
|
|
605
|
+
console.error(
|
|
606
|
+
"[eodash] Error while fetching bearer token from sentinel hub"
|
|
607
|
+
);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const timePairs = generateTimePairs(
|
|
611
|
+
selectedStac.extent.temporal.interval[0],
|
|
612
|
+
[jsonformValue["start_date"], jsonformValue["end_date"]],
|
|
613
|
+
jsonformValue["distribution"]
|
|
614
|
+
);
|
|
615
|
+
return await Promise.all(
|
|
616
|
+
timePairs.map(([to, from]) => {
|
|
617
|
+
return fetchSentinelHubData({
|
|
618
|
+
url: endpoint,
|
|
619
|
+
bearer,
|
|
620
|
+
bbox,
|
|
621
|
+
from,
|
|
622
|
+
to,
|
|
623
|
+
exampleLink: evalScriptLink
|
|
624
|
+
}).catch(
|
|
625
|
+
(err) => console.error(
|
|
626
|
+
"[eodash] Error while fetching data from sentinel hub endpoint:",
|
|
627
|
+
err
|
|
628
|
+
)
|
|
629
|
+
);
|
|
630
|
+
})
|
|
631
|
+
).then((data) => data.flat().map((data2) => data2.outputs.data));
|
|
632
|
+
}
|
|
633
|
+
async function sentinelHubAuth(clientId, clientSecret) {
|
|
634
|
+
if (!clientId || !clientSecret) {
|
|
635
|
+
console.error(
|
|
636
|
+
"[eodash] Error (sentinelhub): client id or secret not found"
|
|
637
|
+
);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const sessionToken = sessionStorage.getItem("sentinelhub_token");
|
|
641
|
+
const sessionTokenTime = (
|
|
642
|
+
/** @type {string} */
|
|
643
|
+
sessionStorage.getItem("sentinelhub_token_time")
|
|
644
|
+
);
|
|
645
|
+
const isValid = Date.now() - Number(sessionTokenTime) < 3600 * 1e3;
|
|
646
|
+
if (sessionToken && isValid) {
|
|
647
|
+
sessionStorage.setItem("sentinelhub_token_time", Date.now().toString());
|
|
648
|
+
return sessionToken;
|
|
649
|
+
}
|
|
650
|
+
const token = await retrieveSentinelHubToken(clientId, clientSecret);
|
|
651
|
+
if (!token) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
sessionStorage.setItem("sentinelhub_token_time", Date.now().toString());
|
|
655
|
+
sessionStorage.setItem("sentinelhub_token", token);
|
|
656
|
+
return token;
|
|
657
|
+
}
|
|
658
|
+
async function retrieveSentinelHubToken(clientId, clientSecret) {
|
|
659
|
+
const url = "https://services.sentinel-hub.com/oauth/token";
|
|
660
|
+
const headers = {
|
|
661
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
662
|
+
};
|
|
663
|
+
const body = new URLSearchParams({
|
|
664
|
+
client_id: clientId,
|
|
665
|
+
client_secret: clientSecret,
|
|
666
|
+
response_type: "token",
|
|
667
|
+
grant_type: "client_credentials"
|
|
668
|
+
});
|
|
669
|
+
return await axios.post(url, body, { headers }).then((resp) => resp.data.access_token);
|
|
670
|
+
}
|
|
671
|
+
async function fetchSentinelHubData({
|
|
672
|
+
url,
|
|
673
|
+
bearer,
|
|
674
|
+
bbox,
|
|
675
|
+
from,
|
|
676
|
+
to,
|
|
677
|
+
timeout = 2e4,
|
|
678
|
+
exampleLink
|
|
679
|
+
}) {
|
|
680
|
+
if (!exampleLink) {
|
|
681
|
+
console.error(
|
|
682
|
+
"[eodash] Error (sentinelhub): example link not found in indicator"
|
|
683
|
+
);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (!bbox) {
|
|
687
|
+
console.error("[eodash] Error (sentinelhub): bbox not found");
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (!from || !to || new Date(from) > new Date(to)) {
|
|
691
|
+
console.error(
|
|
692
|
+
"[eodash] Error (sentinelhub): time range is faulty or not found",
|
|
693
|
+
from,
|
|
694
|
+
to
|
|
695
|
+
);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const evalscript = await axios.get(exampleLink.href, { responseType: "text" }).then((resp) => resp.data);
|
|
699
|
+
if (!evalscript || evalscript.error) {
|
|
700
|
+
console.error(
|
|
701
|
+
"[eodash] Error (sentinelhub): evalscript not found",
|
|
702
|
+
evalscript
|
|
703
|
+
);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const body = {
|
|
707
|
+
input: {
|
|
708
|
+
bounds: {
|
|
709
|
+
bbox
|
|
710
|
+
},
|
|
711
|
+
data: [
|
|
712
|
+
{
|
|
713
|
+
dataFilter: {},
|
|
714
|
+
type: exampleLink.dataId
|
|
715
|
+
}
|
|
716
|
+
]
|
|
717
|
+
},
|
|
718
|
+
aggregation: {
|
|
719
|
+
evalscript,
|
|
720
|
+
timeRange: {
|
|
721
|
+
from,
|
|
722
|
+
to
|
|
723
|
+
},
|
|
724
|
+
aggregationInterval: {
|
|
725
|
+
of: "P1D"
|
|
726
|
+
},
|
|
727
|
+
width: 100,
|
|
728
|
+
height: 100
|
|
729
|
+
},
|
|
730
|
+
calculations: {
|
|
731
|
+
default: {}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
const config = {
|
|
735
|
+
headers: {
|
|
736
|
+
Authorization: `Bearer ${bearer}`,
|
|
737
|
+
"Content-Type": "application/json"
|
|
738
|
+
},
|
|
739
|
+
params: {
|
|
740
|
+
credentials: "same-origin"
|
|
741
|
+
},
|
|
742
|
+
timeout
|
|
743
|
+
};
|
|
744
|
+
return await axios.post(url, body, config).then((resp) => {
|
|
745
|
+
const fetched = resp.data.data;
|
|
746
|
+
fetched.forEach((dataItem) => {
|
|
747
|
+
dataItem.outputs.data.date = from;
|
|
748
|
+
});
|
|
749
|
+
return fetched;
|
|
750
|
+
}).catch((err) => {
|
|
751
|
+
if (err.response?.status === 401) {
|
|
752
|
+
console.error(
|
|
753
|
+
"[eodash] Error (sentinelhub): bearer token expired, please try again"
|
|
754
|
+
);
|
|
755
|
+
sessionStorage.removeItem("sentinelhub_token");
|
|
756
|
+
sessionStorage.removeItem("sentinelhub_token_time");
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
console.error(
|
|
760
|
+
"[eodash] Error (sentinelhub): error while fetching data from sentinel hub",
|
|
761
|
+
err.response?.data
|
|
762
|
+
);
|
|
763
|
+
return [];
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
async function getEvalScriptLink(selectedStac, absoluteUrl) {
|
|
767
|
+
const evalScriptLink = selectedStac.links.find(
|
|
768
|
+
(link) => link.rel === "example" && link.title === "evalscript"
|
|
769
|
+
);
|
|
770
|
+
if (evalScriptLink) {
|
|
771
|
+
return evalScriptLink;
|
|
772
|
+
}
|
|
773
|
+
for (const link of extractCollectionUrls(selectedStac, absoluteUrl)) {
|
|
774
|
+
const scriptLink = axios.get(link).then(
|
|
775
|
+
(resp) => (
|
|
776
|
+
/** @type {import("stac-ts").StacCollection} */
|
|
777
|
+
resp.data.links.find(
|
|
778
|
+
(link2) => link2.rel === "example" && link2.title === "evalscript"
|
|
779
|
+
)
|
|
780
|
+
)
|
|
781
|
+
);
|
|
782
|
+
if (scriptLink) {
|
|
783
|
+
return scriptLink;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @param {import("^/EodashProcess/types").CustomEnpointInput} inputs
|
|
790
|
+
*/
|
|
791
|
+
async function handleVedaEndpoint({
|
|
792
|
+
links,
|
|
793
|
+
jsonformSchema,
|
|
794
|
+
jsonformValue,
|
|
795
|
+
selectedStac,
|
|
796
|
+
enableCompare = false,
|
|
797
|
+
}) {
|
|
798
|
+
const vedaLink = links.find(
|
|
799
|
+
(link) => link.rel === "service" && link.endpoint === "veda",
|
|
800
|
+
);
|
|
801
|
+
if (!vedaLink) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const vedaEndpoint = vedaLink?.href;
|
|
805
|
+
const bboxProperty = getBboxProperty(jsonformSchema);
|
|
806
|
+
// this should be type geojson
|
|
807
|
+
const bboxGeoJSON = JSON.parse(jsonformValue[bboxProperty]);
|
|
808
|
+
|
|
809
|
+
const configs = await fetchVedaCOGsConfig(
|
|
810
|
+
selectedStac,
|
|
811
|
+
enableCompare ? currentCompareUrl.value : currentUrl.value,
|
|
812
|
+
);
|
|
813
|
+
// TODO: convert jsonform bbox type to geojson in the schema to avoid the conversion here
|
|
814
|
+
return await Promise.all(
|
|
815
|
+
configs.map(({ endpoint, datetime }) => {
|
|
816
|
+
return axios
|
|
817
|
+
.post(vedaEndpoint + `?url=${endpoint}`, {
|
|
818
|
+
...{
|
|
819
|
+
type: "Feature",
|
|
820
|
+
properties: {},
|
|
821
|
+
geometry: bboxGeoJSON,
|
|
822
|
+
},
|
|
823
|
+
})
|
|
824
|
+
.then((resp) => {
|
|
825
|
+
const fetchedSats = resp.data.properties.statistics;
|
|
826
|
+
fetchedSats.date = datetime;
|
|
827
|
+
return fetchedSats;
|
|
828
|
+
})
|
|
829
|
+
.catch((resp) =>
|
|
830
|
+
console.error(
|
|
831
|
+
"[eodash] Error while fetching data from veda endpoint:",
|
|
832
|
+
resp,
|
|
833
|
+
),
|
|
834
|
+
);
|
|
835
|
+
}),
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Fetches the COGs endpoints from the STAC collections
|
|
841
|
+
* @param {import("stac-ts").StacCollection} selectedStac
|
|
842
|
+
* @param {string} absoluteUrl
|
|
843
|
+
*/
|
|
844
|
+
async function fetchVedaCOGsConfig(selectedStac, absoluteUrl) {
|
|
845
|
+
// retrieve the collections from the indicator
|
|
846
|
+
const collectionLinks = selectedStac.links.filter(
|
|
847
|
+
(link) => link.rel == "child",
|
|
848
|
+
);
|
|
849
|
+
/** @type {import("stac-ts").StacCollection[]} */
|
|
850
|
+
const collections = [];
|
|
851
|
+
if (!collectionLinks.length) {
|
|
852
|
+
collections.push(selectedStac);
|
|
853
|
+
} else {
|
|
854
|
+
collections.push(
|
|
855
|
+
...(await Promise.all(
|
|
856
|
+
collectionLinks.map((link) =>
|
|
857
|
+
axios
|
|
858
|
+
.get(toAbsolute(link.href, absoluteUrl))
|
|
859
|
+
.then((resp) => resp.data)
|
|
860
|
+
.then(async (collection) => {
|
|
861
|
+
// items in geoparquet handling specially to get item links
|
|
862
|
+
const parquetAsset = Object.values(collection.assets ?? {}).find(
|
|
863
|
+
(asset) =>
|
|
864
|
+
asset.type === "application/vnd.apache.parquet" &&
|
|
865
|
+
asset.roles?.includes("collection-mirror"),
|
|
866
|
+
);
|
|
867
|
+
if (parquetAsset) {
|
|
868
|
+
const parquetAbsoluteUrl = toAbsolute(parquetAsset.href, toAbsolute(link.href, absoluteUrl));
|
|
869
|
+
await readParquetItems(parquetAbsoluteUrl).then((items) => {
|
|
870
|
+
collection.links.push(...generateLinksFromItems(items));
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return collection;
|
|
874
|
+
}),
|
|
875
|
+
),
|
|
876
|
+
)),
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
/** @type {{endpoint:string; datetime:string}[]} */
|
|
880
|
+
const configs = [];
|
|
881
|
+
for (const collection of collections) {
|
|
882
|
+
const datetimeProperty = /** @type string **/ (
|
|
883
|
+
getDatetimeProperty(collection.links)
|
|
884
|
+
);
|
|
885
|
+
const itemLinks = collection.links.filter((link) => link.rel == "item");
|
|
886
|
+
configs.push(
|
|
887
|
+
...itemLinks.map((link) => ({
|
|
888
|
+
endpoint: /** @type {string} */ (link["cog_href"]),
|
|
889
|
+
datetime: /** @type string **/ (link[datetimeProperty]),
|
|
890
|
+
})),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Sort by date ascending
|
|
895
|
+
configs.sort(
|
|
896
|
+
(a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime(),
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
const maxConfigs = 50;
|
|
900
|
+
if (configs.length <= maxConfigs) {
|
|
901
|
+
return configs;
|
|
902
|
+
}
|
|
903
|
+
// we need to sample if the number of configs are more than 50
|
|
904
|
+
const totalSize = configs.length;
|
|
905
|
+
const sampledConfigs = [];
|
|
906
|
+
for (let i = 0; i < maxConfigs; i++) {
|
|
907
|
+
// Calculate the index to pick, ensuring distribution and inclusion of first/last
|
|
908
|
+
const index = Math.floor((i * (totalSize - 1)) / (maxConfigs - 1));
|
|
909
|
+
sampledConfigs.push(configs[index]);
|
|
910
|
+
}
|
|
911
|
+
return sampledConfigs;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const handleChartCustomEndpoints = createCustomChartEndpointsHandler([
|
|
915
|
+
handleSentinelHubProcess,
|
|
916
|
+
handleVedaEndpoint,
|
|
917
|
+
]);
|
|
918
|
+
/**
|
|
919
|
+
* @param {((input:import("^/EodashProcess/types").CustomEnpointInput)=> Promise<any[] | undefined | null>)[]} callbacks
|
|
920
|
+
*/
|
|
921
|
+
function createCustomChartEndpointsHandler(callbacks) {
|
|
922
|
+
/**
|
|
923
|
+
* @param {import("^/EodashProcess/types").CustomEnpointInput} inputs
|
|
924
|
+
* */
|
|
925
|
+
return async (inputs) => {
|
|
926
|
+
for (const callback of callbacks) {
|
|
927
|
+
const data = await callback(inputs);
|
|
928
|
+
log.debug(
|
|
929
|
+
"Custom endpoint data:",
|
|
930
|
+
data,
|
|
931
|
+
"for callback:",
|
|
932
|
+
callback.name,
|
|
933
|
+
"inputs:",
|
|
934
|
+
inputs,
|
|
935
|
+
);
|
|
936
|
+
const isNotValid = !data || !data.length || data.some((item) => !item);
|
|
937
|
+
if (isNotValid) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
return data;
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Fetch and set the jsonform schema to initialize the process
|
|
948
|
+
*
|
|
949
|
+
* @export
|
|
950
|
+
* @async
|
|
951
|
+
* @param {Object} params
|
|
952
|
+
* @param {import("vue").Ref<import("stac-ts").StacCollection | null>} params.selectedStac
|
|
953
|
+
* @param {import("vue").Ref<import("@eox/jsonform").EOxJSONForm | null>} params.jsonformEl
|
|
954
|
+
* @param {import("vue").Ref<Record<string,any> | null>} params.jsonformSchema
|
|
955
|
+
* @param {import("vue").Ref<import("@eox/chart").EOxChart["spec"] | null>} params.chartSpec
|
|
956
|
+
* @param {import("vue").Ref<any[]>} params.processResults
|
|
957
|
+
* @param {import("vue").Ref<boolean>} params.isProcessed
|
|
958
|
+
* @param {import("vue").Ref<boolean>} params.loading
|
|
959
|
+
* @param {import("vue").Ref<boolean>} params.isPolling
|
|
960
|
+
* @param {boolean} params.enableCompare
|
|
961
|
+
*/
|
|
962
|
+
async function initProcess({
|
|
963
|
+
selectedStac,
|
|
964
|
+
jsonformEl,
|
|
965
|
+
jsonformSchema,
|
|
966
|
+
chartSpec,
|
|
967
|
+
isProcessed,
|
|
968
|
+
processResults,
|
|
969
|
+
loading,
|
|
970
|
+
isPolling,
|
|
971
|
+
enableCompare,
|
|
972
|
+
}) {
|
|
973
|
+
const isPoiAlive = enableCompare ? !!comparePoi.value : !!poi.value;
|
|
974
|
+
let updatedJsonform = null;
|
|
975
|
+
if (selectedStac.value?.["eodash:jsonform"]) {
|
|
976
|
+
updatedJsonform = await axios
|
|
977
|
+
//@ts-expect-error eodash extention
|
|
978
|
+
.get(selectedStac.value["eodash:jsonform"])
|
|
979
|
+
.then((resp) => resp.data);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (!updatedJsonform && isPoiAlive) {
|
|
983
|
+
jsonformSchema.value = null;
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
resetProcess({
|
|
987
|
+
loading,
|
|
988
|
+
isProcessed,
|
|
989
|
+
chartSpec,
|
|
990
|
+
jsonformSchema,
|
|
991
|
+
isPolling,
|
|
992
|
+
processResults,
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
await jsonformEl.value?.editor.destroy();
|
|
996
|
+
if (updatedJsonform) {
|
|
997
|
+
if (enableCompare) {
|
|
998
|
+
updatedJsonform = updateJsonformSchemaTarget(updatedJsonform);
|
|
999
|
+
}
|
|
1000
|
+
jsonformSchema.value = updatedJsonform;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
*
|
|
1006
|
+
* @param {object} params
|
|
1007
|
+
* @param {import("vue").Ref<boolean>} params.loading
|
|
1008
|
+
* @param {import("vue").Ref<import("stac-ts").StacCollection | null>} params.selectedStac
|
|
1009
|
+
* @param {import("vue").Ref<import("@eox/jsonform").EOxJSONForm | null>} params.jsonformEl
|
|
1010
|
+
* @param {import("vue").Ref<Record<string,any>|null>} params.jsonformSchema
|
|
1011
|
+
* @param {import("vue").Ref<import("@eox/chart").EOxChart["spec"] | null>} params.chartSpec
|
|
1012
|
+
* @param {import("vue").Ref<Record<string, any> | null>} params.chartData
|
|
1013
|
+
* @param {import("vue").Ref<boolean>} params.isPolling
|
|
1014
|
+
* @param {import("vue").Ref<any[]>} params.processResults
|
|
1015
|
+
* @param {import("@eox/map").EOxMap | null} params.mapElement
|
|
1016
|
+
* @param {import("vue").Ref<import("../types").AsyncJob[]>} params.jobs
|
|
1017
|
+
*/
|
|
1018
|
+
async function handleProcesses({
|
|
1019
|
+
loading,
|
|
1020
|
+
selectedStac,
|
|
1021
|
+
jsonformEl,
|
|
1022
|
+
jsonformSchema,
|
|
1023
|
+
chartSpec,
|
|
1024
|
+
chartData,
|
|
1025
|
+
isPolling,
|
|
1026
|
+
processResults,
|
|
1027
|
+
mapElement,
|
|
1028
|
+
jobs,
|
|
1029
|
+
}) {
|
|
1030
|
+
if (!jsonformEl.value || !jsonformSchema.value || !selectedStac.value) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
log.debug("Processing...");
|
|
1035
|
+
loading.value = true;
|
|
1036
|
+
try {
|
|
1037
|
+
const serviceLinks = selectedStac.value?.links?.filter(
|
|
1038
|
+
(l) => l.rel === "service",
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
const bboxProperty = getBboxProperty(jsonformSchema.value);
|
|
1042
|
+
const jsonformValue = /** @type {Record<string,any>} */ (
|
|
1043
|
+
jsonformEl.value?.value
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
extractGeometries(jsonformValue, jsonformSchema.value);
|
|
1047
|
+
|
|
1048
|
+
const origBbox = jsonformValue[bboxProperty];
|
|
1049
|
+
|
|
1050
|
+
const specUrl = /** @type {string} */ (
|
|
1051
|
+
selectedStac.value?.["eodash:vegadefinition"]
|
|
1052
|
+
);
|
|
1053
|
+
const layerId = selectedStac.value?.id ?? "";
|
|
1054
|
+
|
|
1055
|
+
[chartSpec.value, chartData.value] = await processCharts({
|
|
1056
|
+
links: serviceLinks,
|
|
1057
|
+
jsonformValue: { ...(jsonformValue ?? {}) },
|
|
1058
|
+
jsonformSchema: jsonformSchema.value,
|
|
1059
|
+
enableCompare: mapElement?.id === "compare",
|
|
1060
|
+
selectedStac: selectedStac.value,
|
|
1061
|
+
specUrl,
|
|
1062
|
+
isPolling,
|
|
1063
|
+
jobs,
|
|
1064
|
+
customEndpointsHandler: handleChartCustomEndpoints,
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
if (Object.keys(chartData.value ?? {}).length) {
|
|
1068
|
+
processResults.value.push(chartData.value);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
//@ts-expect-error we assume that the spec data is of type InlineData
|
|
1072
|
+
if (chartSpec.value?.data?.values?.length) {
|
|
1073
|
+
//@ts-expect-error we assume that the spec data is of type InlineData
|
|
1074
|
+
processResults.value.push(chartSpec.value?.data.values);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (chartSpec.value && !("background" in chartSpec.value)) {
|
|
1078
|
+
chartSpec.value["background"] = "transparent";
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
await processSTAC(
|
|
1082
|
+
serviceLinks,
|
|
1083
|
+
jsonformValue,
|
|
1084
|
+
mapElement?.id === "compare",
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
const newLayers = await processLayers({
|
|
1088
|
+
isPolling,
|
|
1089
|
+
links: serviceLinks,
|
|
1090
|
+
jsonformValue: { ...(jsonformValue ?? {}) },
|
|
1091
|
+
jsonformSchema: jsonformSchema.value,
|
|
1092
|
+
selectedStac: selectedStac.value,
|
|
1093
|
+
enableCompare: mapElement?.id === "compare",
|
|
1094
|
+
layerId,
|
|
1095
|
+
origBbox,
|
|
1096
|
+
jobs,
|
|
1097
|
+
customLayersHandler: handleLayersCustomEndpoints,
|
|
1098
|
+
projection: /** @type {{name?:string}} */ (
|
|
1099
|
+
selectedStac.value["eodash:mapProjection"]
|
|
1100
|
+
)?.["name"],
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
// save layers results
|
|
1104
|
+
if (newLayers.length) {
|
|
1105
|
+
for (const layer of newLayers) {
|
|
1106
|
+
if (layer.type === "WebGLTile" && layer.source?.type === "GeoTIFF") {
|
|
1107
|
+
processResults.value.push(...(layer.source.sources ?? []));
|
|
1108
|
+
} else if (layer.source && "url" in layer.source) {
|
|
1109
|
+
processResults.value.push(layer.source.url);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
applyProcessLayersToMap(mapElement, newLayers);
|
|
1115
|
+
loading.value = false;
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
console.error("[eodash] Error while running process:", error);
|
|
1118
|
+
loading.value = false;
|
|
1119
|
+
throw error;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Reset the process state
|
|
1125
|
+
* @param {Object} params
|
|
1126
|
+
* @param {import("vue").Ref<boolean>} params.loading
|
|
1127
|
+
* @param {import("vue").Ref<boolean>} params.isProcessed
|
|
1128
|
+
* @param {import("vue").Ref<import("@eox/chart").EOxChart["spec"] | null>} params.chartSpec
|
|
1129
|
+
* @param {import("vue").Ref<boolean>} params.isPolling
|
|
1130
|
+
* @param {import("vue").Ref<any[]>} params.processResults
|
|
1131
|
+
* @param {import("vue").Ref<Record<string,any>|null>} params.jsonformSchema
|
|
1132
|
+
*/
|
|
1133
|
+
function resetProcess({
|
|
1134
|
+
loading,
|
|
1135
|
+
isProcessed,
|
|
1136
|
+
chartSpec,
|
|
1137
|
+
jsonformSchema,
|
|
1138
|
+
processResults,
|
|
1139
|
+
isPolling,
|
|
1140
|
+
}) {
|
|
1141
|
+
loading.value = false;
|
|
1142
|
+
isProcessed.value = false;
|
|
1143
|
+
isPolling.value = false;
|
|
1144
|
+
chartSpec.value = null;
|
|
1145
|
+
processResults.value = [];
|
|
1146
|
+
jsonformSchema.value = null;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Handles the click event on a chart to extract temporal information and update the global datetime value.
|
|
1151
|
+
*
|
|
1152
|
+
* @param {object} evt - The click event object.
|
|
1153
|
+
* @param {object} evt.target - The target of the event, expected to have a Vega-Lite specification (`spec`).
|
|
1154
|
+
* @param {object} evt.target.spec - The Vega-Lite specification of the chart.
|
|
1155
|
+
* @param {Record<string,{type?:string;field?:string;}>} [evt.target.spec.encoding] - The encoding specification of the chart.
|
|
1156
|
+
* @param {object} evt.detail - The detail of the event, containing information about the clicked item.
|
|
1157
|
+
* @param {import("vega").Item} evt.detail.item - The Vega item that was clicked.
|
|
1158
|
+
*/
|
|
1159
|
+
const onChartClick = (evt) => {
|
|
1160
|
+
const chartSpec = evt.target?.spec;
|
|
1161
|
+
if (
|
|
1162
|
+
!chartSpec ||
|
|
1163
|
+
!evt.detail?.item?.datum ||
|
|
1164
|
+
!evt.detail?.item?.datum.datum
|
|
1165
|
+
) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const encodingKey = Object.keys(chartSpec.encoding ?? {}).find(
|
|
1169
|
+
(key) => chartSpec.encoding?.[key].type === "temporal",
|
|
1170
|
+
);
|
|
1171
|
+
if (!encodingKey) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const temporalKey = chartSpec.encoding?.[encodingKey].field;
|
|
1175
|
+
if (!temporalKey) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
try {
|
|
1180
|
+
const vegaItem = evt.detail.item;
|
|
1181
|
+
let datestring = "";
|
|
1182
|
+
// It seems sometimes we have datum inside datum and sometimes not
|
|
1183
|
+
if (vegaItem.datum && vegaItem.datum.datum) {
|
|
1184
|
+
// If datum is nested, we use the nested datum
|
|
1185
|
+
datestring = vegaItem.datum.datum[temporalKey];
|
|
1186
|
+
} else {
|
|
1187
|
+
// Otherwise, we use the top-level datum
|
|
1188
|
+
datestring = vegaItem.datum[temporalKey];
|
|
1189
|
+
}
|
|
1190
|
+
const temporalValue = new Date(datestring);
|
|
1191
|
+
datetime.value = temporalValue.toISOString();
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
console.warn(
|
|
1194
|
+
"[eodash] Error while setting datetime from eox-chart:",
|
|
1195
|
+
error,
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Loads the main indicator of a Point of Interest (POI)
|
|
1202
|
+
*/
|
|
1203
|
+
const loadPOiIndicator = () => {
|
|
1204
|
+
if (!indicator.value) {
|
|
1205
|
+
indicator.value =
|
|
1206
|
+
new URLSearchParams(window.location.search).get("indicator") ?? "";
|
|
1207
|
+
}
|
|
1208
|
+
const stacStore = useSTAcStore();
|
|
1209
|
+
const link = stacStore.stac?.find(
|
|
1210
|
+
(link) => useGetSubCodeId(link) === indicator.value,
|
|
1211
|
+
);
|
|
1212
|
+
stacStore.loadSelectedSTAC(link?.href);
|
|
1213
|
+
if (comparePoi.value) {
|
|
1214
|
+
if (compareIndicator.value) {
|
|
1215
|
+
const comparelink = stacStore.stac?.find(
|
|
1216
|
+
(link) => useGetSubCodeId(link) === compareIndicator.value,
|
|
1217
|
+
);
|
|
1218
|
+
stacStore.loadSelectedCompareSTAC(comparelink?.href).catch((err) => {
|
|
1219
|
+
console.error("[eodash] Error loading compare STAC:", err);
|
|
1220
|
+
});
|
|
1221
|
+
} else {
|
|
1222
|
+
stacStore.resetSelectedCompareSTAC();
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
export { handleProcesses as h, initProcess as i, loadPOiIndicator as l, onChartClick as o };
|