@eodash/eodash 5.6.1 → 5.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/client/components/DashboardLayout.vue +1 -1
- package/core/client/components/EodashOverlay.vue +27 -4
- package/core/client/composables/DefineEodash.js +0 -1
- package/core/client/composables/DefineWidgets.js +3 -1
- package/core/client/composables/index.js +6 -2
- package/core/client/eodashSTAC/EodashCollection.js +79 -45
- package/core/client/eodashSTAC/cql.js +64 -0
- package/core/client/eodashSTAC/createLayers.js +192 -125
- package/core/client/eodashSTAC/helpers.js +423 -97
- package/core/client/eodashSTAC/mosaic.js +416 -0
- package/core/client/plugins/axios.js +31 -1
- package/core/client/store/actions.js +1 -1
- package/core/client/store/stac.js +25 -4
- package/core/client/store/states.js +20 -2
- package/core/client/types.ts +155 -29
- package/core/client/utils/bands-editor/exampleSchema.json +50 -50
- package/core/client/utils/index.js +4 -0
- package/core/client/utils/states.js +10 -0
- package/dist/client/{DashboardLayout-CqMb7OXc.js → DashboardLayout-ePbdvUTE.js} +5 -5
- package/dist/client/{DynamicWebComponent-DqKqTf7l.js → DynamicWebComponent-g5SdRbjY.js} +1 -1
- package/dist/client/{EodashChart-PkUQfH0k.js → EodashChart-ChrkA5oS.js} +7 -6
- package/dist/client/{EodashDatePicker-DLJGdTtX.js → EodashDatePicker-CY1TrsDo.js} +3 -3
- package/dist/client/{EodashItemFilter-Dk8KjFcX.js → EodashItemFilter-DzshHDlS.js} +2 -1
- package/dist/client/{EodashLayerControl-CC1Cogrn.js → EodashLayerControl-DMCeMkSd.js} +70 -22
- package/dist/client/{EodashLayoutSwitcher-5WZYd6rr.js → EodashLayoutSwitcher-iJ8mX1rF.js} +3 -3
- package/dist/client/EodashMapBtns-C7Cr7q5Y.js +592 -0
- package/dist/client/EodashStacInfo-K7-iuXa7.js +169 -0
- package/dist/client/{EodashTools-CGZtTh45.js → EodashTools-BEK7e7JM.js} +4 -4
- package/dist/client/{ExportState-Ce0ZnrX_.js → ExportState-CqIzfMYQ.js} +6 -168
- package/dist/client/{Footer-CnZqEiop.js → Footer-1P0cmBOZ.js} +1 -1
- package/dist/client/{Header-Dr5PBIm8.js → Header-CkkBaBNh.js} +2 -2
- package/dist/client/MobileLayout-BPf49kDU.js +118 -0
- package/dist/client/{PopUp-DjvE_71e.js → PopUp-DPZq5SWI.js} +4 -168
- package/dist/client/{ProcessList-kf3olI7W.js → ProcessList-DuI6WpS1.js} +5 -5
- package/dist/client/VDivider-Bj9LG_JV.js +168 -0
- package/dist/client/{VImg-Bj6uWt6j.js → VImg-Ddv5OyMz.js} +2 -2
- package/dist/client/{VMain-Cm0UmKGi.js → VMain-7yqk_qC3.js} +1 -1
- package/dist/client/{VTooltip-DbBu-ky3.js → VTooltip-2hAJyEoa.js} +2 -2
- package/dist/client/{WidgetsContainer-CfLnd44U.js → WidgetsContainer-2DOrlwCB.js} +1 -1
- package/dist/client/{asWebComponent-DfULAbpq.js → asWebComponent-BlFN2lkB.js} +493 -335
- package/dist/client/{async-CIbKXEEe.js → async-B2H8ZwUK.js} +21 -10
- package/dist/client/create-layers-config-u_vn5hPk.js +182 -0
- package/dist/client/dialog-transition-BrMkMiNa.js +170 -0
- package/dist/client/eo-dash.js +1 -1
- package/dist/client/{forwardRefs-yj3VWOK5.js → forwardRefs-DAlror6d.js} +8 -87
- package/dist/client/{handling-s8xt2JwF.js → handling-B6I1V5eG.js} +123 -85
- package/dist/client/{helpers-DAVGYl_f.js → helpers-CBqIUSXJ.js} +3034 -1649
- package/dist/client/index-BWq1ZKaV.js +3168 -0
- package/dist/client/{index-CYz6dT7K.js → index-CJ9UI3ln.js} +2 -2
- package/dist/client/index-DXLz4sFR.js +442 -0
- package/dist/client/{index-4TrjfJ32.js → index-DhKU6KqM.js} +230 -231
- package/dist/client/{index-Bu8BMA9q.js → index-DjQlbWbJ.js} +70 -83
- 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/mosaic-BFYLEwyx.js +455 -0
- package/dist/client/templates.js +250 -119
- package/dist/client/{transition-DaOsiD4u.js → transition-D_W6Wn9v.js} +1 -1
- package/dist/node/cli.js +5 -5
- package/dist/node/types.d.ts +4 -0
- package/dist/types/core/client/composables/DefineEodash.d.ts +1 -1
- package/dist/types/core/client/composables/index.d.ts +1 -3
- package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +62 -31
- package/dist/types/core/client/eodashSTAC/cql.d.ts +15 -0
- package/dist/types/core/client/eodashSTAC/createLayers.d.ts +44 -25
- package/dist/types/core/client/eodashSTAC/helpers.d.ts +78 -18
- package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
- package/dist/types/core/client/store/stac.d.ts +31 -7
- package/dist/types/core/client/store/states.d.ts +24 -2
- package/dist/types/core/client/types.d.ts +131 -25
- package/dist/types/core/client/utils/states.d.ts +12 -0
- package/dist/types/templates/baseConfig.d.ts +1 -88
- package/dist/types/templates/compare.d.ts +32 -3
- package/dist/types/templates/expert.d.ts +18 -0
- package/dist/types/templates/explore.d.ts +9 -1
- package/dist/types/templates/lite.d.ts +11 -0
- package/dist/types/widgets/EodashChart.vue.d.ts +5 -3
- package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +129 -2
- package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +28 -6
- package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +17 -4
- package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +16 -4
- package/dist/types/widgets/EodashLayerControl.vue.d.ts +36 -17
- package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +23 -10
- package/dist/types/widgets/EodashMap/index.vue.d.ts +91 -35
- package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -8
- package/dist/types/widgets/EodashMap/methods/index.d.ts +5 -1
- package/dist/types/widgets/EodashMap/methods/use-map-loading.d.ts +1 -0
- package/dist/types/widgets/EodashProcess/index.vue.d.ts +5 -3
- package/dist/types/widgets/EodashProcess/methods/composables.d.ts +1 -1
- package/dist/types/widgets/EodashProcess/methods/handling.d.ts +13 -8
- package/dist/types/widgets/EodashProcess/states.d.ts +1 -1
- package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
- package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +54 -0
- package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +34 -0
- package/dist/types/widgets/EodashTimeSlider/types.d.ts +52 -0
- package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
- package/package.json +46 -46
- package/templates/baseConfig.js +36 -6
- package/templates/compare.js +62 -7
- package/templates/expert.js +23 -0
- package/templates/explore.js +11 -5
- package/templates/lite.js +14 -0
- package/widgets/EodashChart.vue +1 -0
- package/widgets/EodashItemCatalog/index.vue +363 -35
- package/widgets/EodashItemCatalog/methods/filters.js +134 -70
- package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
- package/widgets/EodashItemCatalog/methods/map.js +142 -28
- package/widgets/EodashLayerControl.vue +64 -14
- package/widgets/EodashMap/EodashMapBtns.vue +153 -47
- package/widgets/EodashMap/index.vue +101 -105
- package/widgets/EodashMap/methods/btns.js +37 -5
- package/widgets/EodashMap/methods/create-layers-config.js +18 -16
- package/widgets/EodashMap/methods/index.js +89 -8
- package/widgets/EodashMap/methods/use-map-loading.js +52 -0
- package/widgets/EodashProcess/index.vue +2 -1
- package/widgets/EodashProcess/methods/async.js +19 -8
- package/widgets/EodashProcess/methods/composables.js +63 -77
- 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 +105 -72
- package/widgets/EodashProcess/states.js +1 -1
- package/widgets/EodashStacInfo.vue +64 -2
- package/widgets/EodashTimeSlider/index.vue +208 -0
- package/widgets/EodashTimeSlider/methods/index.js +236 -0
- package/widgets/EodashTimeSlider/types.ts +54 -0
- package/dist/client/EodashMapBtns-BHrSDNoC.js +0 -435
- package/dist/client/EodashStacInfo-B90wobPc.js +0 -107
- package/dist/client/EodashTimeSlider-5vm7F3ju.js +0 -53
- package/dist/client/MobileLayout-DyMjaeif.js +0 -118
- package/dist/client/index-CugS__t5.js +0 -570
- package/dist/types/widgets/EodashTimeSlider.vue.d.ts +0 -15
- package/widgets/EodashTimeSlider.vue +0 -40
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { onMounted, watch, nextTick, ref, useTemplateRef, computed, openBlock, createElementBlock, createVNode, unref, createCommentVNode, createBlock, createElementVNode, withCtx, createTextVNode } from 'vue';
|
|
1
|
+
import { onMounted, watch, nextTick, onUnmounted, ref, useTemplateRef, computed, openBlock, createElementBlock, createVNode, unref, createCommentVNode, createBlock, createElementVNode, withCtx, createTextVNode } from 'vue';
|
|
2
2
|
import '@eox/chart';
|
|
3
3
|
import '@eox/drawtools';
|
|
4
4
|
import '@eox/jsonform';
|
|
5
|
-
import {
|
|
5
|
+
import { L as useOnLayersUpdate, _ as _export_sfc, H as useSTAcStore, J as VBtn } from './asWebComponent-BlFN2lkB.js';
|
|
6
6
|
import { storeToRefs } from 'pinia';
|
|
7
|
-
import { P as ProcessList, c as compareJobs, j as jobs } from './ProcessList-
|
|
8
|
-
import EodashChart from './EodashChart-
|
|
9
|
-
import { i as initProcess, u as updateJsonformIdentifier, h as handleProcesses } from './handling-
|
|
10
|
-
import {
|
|
11
|
-
import { u as updateJobsStatus, d as download, g as getDrawToolsProperties } from './async-
|
|
12
|
-
import { I as compareIndicator, F as indicator, S as mapCompareEl, i as mapEl, M as areChartsSeparateLayout, O as compareChartSpec, N as chartSpec } from './helpers-DAVGYl_f.js';
|
|
7
|
+
import { P as ProcessList, c as compareJobs, j as jobs } from './ProcessList-DuI6WpS1.js';
|
|
8
|
+
import EodashChart from './EodashChart-ChrkA5oS.js';
|
|
9
|
+
import { i as initProcess, u as updateJsonformIdentifier, h as handleProcesses } from './handling-B6I1V5eG.js';
|
|
10
|
+
import { ab as getCompareLayers, B as getLayers, Q as compareIndicator, M as indicator, a1 as mapCompareEl, n as mapEl, V as areChartsSeparateLayout, X as compareChartSpec, W as chartSpec } from './helpers-CBqIUSXJ.js';
|
|
11
|
+
import { u as updateJobsStatus, d as download, g as getDrawToolsProperties } from './async-B2H8ZwUK.js';
|
|
13
12
|
import { mdiCogPlayOutline, mdiDownloadCircleOutline } from '@mdi/js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -24,7 +23,7 @@ import { mdiCogPlayOutline, mdiDownloadCircleOutline } from '@mdi/js';
|
|
|
24
23
|
* @param {import("vue").Ref<boolean>} params.isProcessed
|
|
25
24
|
* @param {import("vue").Ref<boolean>} params.loading
|
|
26
25
|
* @param {import("vue").Ref<boolean>} params.isPolling
|
|
27
|
-
* @param {import("@eox/map").EOxMap | null} params.mapElement
|
|
26
|
+
* @param {import("vue").Ref<import("@eox/map").EOxMap | null>} params.mapElement
|
|
28
27
|
*/
|
|
29
28
|
const useInitProcess = ({
|
|
30
29
|
selectedStac,
|
|
@@ -35,60 +34,45 @@ const useInitProcess = ({
|
|
|
35
34
|
isPolling,
|
|
36
35
|
mapElement,
|
|
37
36
|
}) => {
|
|
38
|
-
const layersEvents = useEventBus(eoxLayersKey);
|
|
39
|
-
|
|
40
37
|
onMounted(async () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
} else {
|
|
53
|
-
layersEvents.once(async () => {
|
|
54
|
-
await initProcess({
|
|
55
|
-
enableCompare: mapElement?.id === "compare",
|
|
56
|
-
selectedStac,
|
|
57
|
-
jsonformSchema,
|
|
58
|
-
isProcessed,
|
|
59
|
-
loading,
|
|
60
|
-
processResults,
|
|
61
|
-
isPolling,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
}
|
|
38
|
+
await initProcess({
|
|
39
|
+
enableCompare: mapElement.value?.id === "compare",
|
|
40
|
+
selectedStac,
|
|
41
|
+
jsonformSchema,
|
|
42
|
+
isProcessed,
|
|
43
|
+
processResults,
|
|
44
|
+
loading,
|
|
45
|
+
isPolling,
|
|
46
|
+
mapElement: mapElement.value,
|
|
47
|
+
});
|
|
65
48
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
49
|
+
|
|
50
|
+
useOnLayersUpdate(async (evt) => {
|
|
51
|
+
const enableCompare = mapElement.value?.id === "compare";
|
|
52
|
+
const layerUpdatedKey = enableCompare
|
|
53
|
+
? "compareLayers:updated"
|
|
54
|
+
: "layers:updated";
|
|
55
|
+
const timeUpdatedKeys = enableCompare
|
|
56
|
+
? ["compareLayertime:updated", "compareTime:updated"]
|
|
57
|
+
: ["layertime:updated", "time:updated"];
|
|
58
|
+
|
|
59
|
+
if (timeUpdatedKeys.includes(evt)) {
|
|
60
|
+
// we need to update jsonform on time change in cases
|
|
61
|
+
// when the feature selection layer was time-based,
|
|
62
|
+
// so that it attaches to a correct new layer
|
|
63
|
+
const newJsonForm = await updateJsonformIdentifier({
|
|
64
|
+
jsonformSchema: jsonformSchema.value,
|
|
65
|
+
newLayers: enableCompare ? getCompareLayers() : getLayers(),
|
|
66
|
+
enableCompare,
|
|
67
|
+
mapElement: mapElement.value,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (newJsonForm) {
|
|
88
71
|
jsonformSchema.value = newJsonForm;
|
|
89
72
|
}
|
|
90
73
|
}
|
|
91
|
-
|
|
74
|
+
|
|
75
|
+
if (evt !== layerUpdatedKey) {
|
|
92
76
|
return;
|
|
93
77
|
}
|
|
94
78
|
await initProcess({
|
|
@@ -99,6 +83,7 @@ const useInitProcess = ({
|
|
|
99
83
|
processResults,
|
|
100
84
|
loading,
|
|
101
85
|
isPolling,
|
|
86
|
+
mapElement: mapElement.value,
|
|
102
87
|
});
|
|
103
88
|
});
|
|
104
89
|
};
|
|
@@ -118,39 +103,40 @@ function useAutoExec(
|
|
|
118
103
|
startProcess,
|
|
119
104
|
) {
|
|
120
105
|
/**
|
|
121
|
-
* @param {
|
|
106
|
+
* @param {any} _e
|
|
122
107
|
**/
|
|
123
108
|
const onJsonFormChange = async (_e) => {
|
|
124
109
|
await startProcess();
|
|
125
110
|
};
|
|
126
111
|
|
|
127
|
-
const addEventListener = async () => {
|
|
128
|
-
await nextTick(() => {
|
|
129
|
-
//@ts-expect-error TODO
|
|
130
|
-
jsonformEl.value?.addEventListener("change", onJsonFormChange);
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
const removeEventListener = () => {
|
|
134
|
-
//@ts-expect-error TODO
|
|
135
|
-
jsonformEl.value?.removeEventListener("change", onJsonFormChange);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
112
|
watch(jsonformSchema, (updatedSchema) => {
|
|
139
113
|
autoExec.value = updatedSchema?.options?.["execute"] || false;
|
|
140
114
|
});
|
|
141
115
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
116
|
+
// Re-attach listener whenever the element or autoExec changes
|
|
117
|
+
const stop = watch(
|
|
118
|
+
[autoExec, jsonformEl],
|
|
119
|
+
async ([exec, el], [_prevExec, prevEl]) => {
|
|
120
|
+
// Cleanup if previous element existed
|
|
121
|
+
if (prevEl) {
|
|
122
|
+
prevEl.removeEventListener("change", onJsonFormChange);
|
|
123
|
+
}
|
|
124
|
+
// Add if enabled and we have an element
|
|
125
|
+
if (exec && el) {
|
|
126
|
+
// remove listener before adding to avoid duplicates in case of element change
|
|
127
|
+
el.removeEventListener("change", onJsonFormChange);
|
|
128
|
+
await nextTick();
|
|
129
|
+
el.addEventListener("change", onJsonFormChange);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{ immediate: true },
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
onUnmounted(() => {
|
|
136
|
+
if (jsonformEl.value) {
|
|
137
|
+
jsonformEl.value.removeEventListener("change", onJsonFormChange);
|
|
138
|
+
}
|
|
139
|
+
stop();
|
|
154
140
|
});
|
|
155
141
|
}
|
|
156
142
|
|
|
@@ -172,6 +158,7 @@ const _sfc_main = {
|
|
|
172
158
|
default: false,
|
|
173
159
|
},
|
|
174
160
|
vegaEmbedOptions: {
|
|
161
|
+
/** @type {import("vue").PropType<import("vega-embed").EmbedOptions>} */
|
|
175
162
|
type: Object,
|
|
176
163
|
default() {
|
|
177
164
|
return { actions: true };
|
|
@@ -224,7 +211,7 @@ const jsonformKey = computed(
|
|
|
224
211
|
|
|
225
212
|
useInitProcess({
|
|
226
213
|
selectedStac: currentSelectedStac,
|
|
227
|
-
mapElement
|
|
214
|
+
mapElement,
|
|
228
215
|
jsonformSchema,
|
|
229
216
|
isProcessed,
|
|
230
217
|
processResults,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { K as mapPosition, a6 as isGlobe, n as mapEl, S as eodashCollections, q as extractLayerTimeValues, H as datetime, f as axios, ag as resolveRenders, l as encodeURLObject, M as indicator, B as getLayers } from './helpers-CBqIUSXJ.js';
|
|
2
|
+
import { shallowRef, ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
|
|
3
|
+
import { createSharedComposable, useEventBus, useDebounceFn } from '@vueuse/core';
|
|
4
|
+
import { H as useSTAcStore, L as useOnLayersUpdate, ab as useEodash } from './asWebComponent-BlFN2lkB.js';
|
|
5
|
+
import log from 'loglevel';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Keys that are catalog based and have dedicated parameters
|
|
9
|
+
* sent to a remote API (e.g. TiTiler, eoAPI STAC search).
|
|
10
|
+
* @type {string[]}
|
|
11
|
+
*/
|
|
12
|
+
const SKIP_KEYS = ["collection", "datetime", "bbox"];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Strips the "properties." prefix that eox-itemfilter catalog configs add to
|
|
16
|
+
* item property keys (e.g. "properties.eo:cloud_cover" -> "eo:cloud_cover").
|
|
17
|
+
* Keys without the prefix are returned unchanged.
|
|
18
|
+
* @param {string} key
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function normalizeFilterKey(key) {
|
|
22
|
+
return key.startsWith("properties.") ? key.slice("properties.".length) : key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a CQL2-text filter string from an eox-itemfilter filters record.
|
|
27
|
+
*
|
|
28
|
+
* Handles range, multiselect, and select filter types.
|
|
29
|
+
* - Strips "properties." prefix from keys (catalog eox-itemfilter convention).
|
|
30
|
+
* - Double-quotes property names that contain non-word characters (e.g. "eo:cloud_cover").
|
|
31
|
+
* - Skips catalog-only keys (e.g. "collection").
|
|
32
|
+
*
|
|
33
|
+
* Compatible with both TiTiler's `filter` param and the STAC API `filter` param
|
|
34
|
+
* (both accept CQL2-text).
|
|
35
|
+
*
|
|
36
|
+
* @param {import("@/types").ItemFilterFilters | null | undefined} filters
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function buildCqlFilter(filters) {
|
|
40
|
+
if (!filters) return "";
|
|
41
|
+
|
|
42
|
+
/** @type {string[]} */
|
|
43
|
+
const parts = [];
|
|
44
|
+
|
|
45
|
+
for (const filter of Object.values(filters)) {
|
|
46
|
+
if (!filter?.key || SKIP_KEYS.includes(filter.key)) continue;
|
|
47
|
+
|
|
48
|
+
const key = normalizeFilterKey(filter.key);
|
|
49
|
+
const prop = /\W/.test(key) ? `"${key}"` : key;
|
|
50
|
+
|
|
51
|
+
if (filter.type === "range" && filter.state) {
|
|
52
|
+
const { min, max } = filter.state;
|
|
53
|
+
if (min != null && min > (filter.min ?? -Infinity)) {
|
|
54
|
+
parts.push(`${prop} >= ${min}`);
|
|
55
|
+
}
|
|
56
|
+
if (max != null && max < (filter.max ?? Infinity)) {
|
|
57
|
+
parts.push(`${prop} <= ${max}`);
|
|
58
|
+
}
|
|
59
|
+
} else if (
|
|
60
|
+
filter.type === "multiselect" &&
|
|
61
|
+
filter.stringifiedState?.length
|
|
62
|
+
) {
|
|
63
|
+
parts.push(`${prop} IN (${filter.stringifiedState})`);
|
|
64
|
+
} else if (filter.type === "select" && filter.stringifiedState) {
|
|
65
|
+
parts.push(`${prop} = '${filter.stringifiedState}'`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return parts.join(" AND ");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const mosaicReturnToOverviewKey = Symbol("mosaic:return-to-overview");
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shared mosaic state across all components. Stores the latest mosaic layer,
|
|
76
|
+
* query parameters, visibility threshold, item-view flag, and an event bus
|
|
77
|
+
* for the "Back to overview" user action .
|
|
78
|
+
*/
|
|
79
|
+
const useMosaicState = createSharedComposable(() => {
|
|
80
|
+
/** @type {import("vue").ShallowRef<Record<string, any> | null>} */
|
|
81
|
+
const latestLayer = shallowRef(null);
|
|
82
|
+
/** @type {import("vue").Ref<Record<string, string> | null>} */
|
|
83
|
+
const query = ref(null);
|
|
84
|
+
/** @type {import("vue").Ref<number>} */
|
|
85
|
+
const visibilityThreshold = ref(8);
|
|
86
|
+
/** @type {import("vue").Ref<boolean>} */
|
|
87
|
+
const isItemView = ref(false);
|
|
88
|
+
/** @type {import("vue").Ref<boolean>} - filtered mosaic has assets in the current view */
|
|
89
|
+
const hasDataInView = ref(true);
|
|
90
|
+
const returnToOverview = useEventBus(mosaicReturnToOverviewKey);
|
|
91
|
+
|
|
92
|
+
const store = useSTAcStore();
|
|
93
|
+
/** @type {import("vue").ComputedRef<string | null>} */
|
|
94
|
+
const mosaicEndpoint = computed(() => {
|
|
95
|
+
if (!store.rasterEndpoint || !store.selectedStac?.id) return null;
|
|
96
|
+
return `${store.rasterEndpoint}/collections/${store.selectedStac.id}/WebMercatorQuad/tilejson.json`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
latestLayer,
|
|
101
|
+
query,
|
|
102
|
+
visibilityThreshold,
|
|
103
|
+
isItemView,
|
|
104
|
+
hasDataInView,
|
|
105
|
+
returnToOverview,
|
|
106
|
+
mosaicEndpoint,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Modifies the layers collection to display the mosaic layer.
|
|
112
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
113
|
+
* @param {Record<string, string> | null} [params] - Query params forwarded to the tilejson URL.
|
|
114
|
+
*/
|
|
115
|
+
async function renderMosaic(mosaicEndpoint, params) {
|
|
116
|
+
const mosaicLayers = await createMosaicLayers(mosaicEndpoint, params);
|
|
117
|
+
|
|
118
|
+
if (!mosaicLayers.length) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { selectedStac } = useSTAcStore();
|
|
123
|
+
if (selectedStac) {
|
|
124
|
+
const eodashCol = eodashCollections.find(
|
|
125
|
+
(ec) => ec.collectionStac?.id === selectedStac.id,
|
|
126
|
+
);
|
|
127
|
+
const dates = (await eodashCol?.getDates()) ?? [];
|
|
128
|
+
const { timeControlValues } = extractLayerTimeValues(dates, datetime.value);
|
|
129
|
+
if (timeControlValues) {
|
|
130
|
+
mosaicLayers[0].properties.timeControlValues = timeControlValues;
|
|
131
|
+
mosaicLayers[0].properties.timeControlProperty = "TIME";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
assignAnalysisLayer(mosaicLayers);
|
|
136
|
+
const { latestLayer } = useMosaicState();
|
|
137
|
+
latestLayer.value = mosaicLayers[0];
|
|
138
|
+
log.debug("[eodash] Mosaic layer rendered.", mosaicLayers[0].source.url);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Updates the mosaic layer based on the current filters.
|
|
143
|
+
* @param {string | undefined | null} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
144
|
+
* @param {{ timeRange?: [string, string]; filters?: import("@/types").ItemFilterFilters }} [queries]
|
|
145
|
+
*/
|
|
146
|
+
async function updateMosaicLayer(
|
|
147
|
+
mosaicEndpoint,
|
|
148
|
+
{ timeRange, filters } = {},
|
|
149
|
+
) {
|
|
150
|
+
if (!mosaicEndpoint) return;
|
|
151
|
+
|
|
152
|
+
/** @type {Record<string, string>} */
|
|
153
|
+
const params = {};
|
|
154
|
+
|
|
155
|
+
if (timeRange && Array.isArray(timeRange) && timeRange.length === 2) {
|
|
156
|
+
const start = timeToDate(timeRange[0]);
|
|
157
|
+
const end = timeToDate(timeRange[1]);
|
|
158
|
+
if (start && end) {
|
|
159
|
+
params.datetime = start === end ? start : `${start}/${end}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const cqlFilter = buildCqlFilter(filters);
|
|
164
|
+
if (cqlFilter) {
|
|
165
|
+
params.filter = cqlFilter;
|
|
166
|
+
params["filter-lang"] = "cql2-text";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const queryParams = Object.keys(params).length > 0 ? params : null;
|
|
170
|
+
const { query } = useMosaicState();
|
|
171
|
+
query.value = queryParams;
|
|
172
|
+
await renderMosaic(mosaicEndpoint, queryParams);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Renders the latest mosaic layer stored in the shared mosaic state.
|
|
177
|
+
*/
|
|
178
|
+
function renderLatestMosaic() {
|
|
179
|
+
const { latestLayer } = useMosaicState();
|
|
180
|
+
if (!latestLayer.value) return;
|
|
181
|
+
assignAnalysisLayer([latestLayer.value]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
186
|
+
* @param {[string, string] | undefined} timeRange
|
|
187
|
+
*/
|
|
188
|
+
async function initMosaic(mosaicEndpoint, timeRange) {
|
|
189
|
+
await updateMosaicLayer(mosaicEndpoint, { timeRange });
|
|
190
|
+
await nextTick();
|
|
191
|
+
const zoom =
|
|
192
|
+
mapPosition.value[2] ?? mapEl.value?.map?.getView()?.getZoom() ?? 0;
|
|
193
|
+
toggleMosaicVisibility(zoom);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Vue composable that initializes the mosaic layer and keeps it in sync with
|
|
198
|
+
* map zoom and layer updates.
|
|
199
|
+
* @param {string | null | undefined} mosaicEndpoint - Pass null/undefined to disable.
|
|
200
|
+
* @param {import("vue").Ref<[string, string]> | undefined} timeRange
|
|
201
|
+
* @param {string[]} [indicators] - If provided, mosaic only activates for these collection IDs.
|
|
202
|
+
*/
|
|
203
|
+
function useInitMosaic(mosaicEndpoint, timeRange, indicators) {
|
|
204
|
+
if (!mosaicEndpoint) return;
|
|
205
|
+
|
|
206
|
+
const store = useSTAcStore();
|
|
207
|
+
const { isItemView, latestLayer, query, hasDataInView } = useMosaicState();
|
|
208
|
+
|
|
209
|
+
function shouldInitiate() {
|
|
210
|
+
if (isItemView.value) return false;
|
|
211
|
+
if (!store.selectedStac?.id) return false;
|
|
212
|
+
if (indicators !== undefined && !indicators.includes(store.selectedStac.id))
|
|
213
|
+
return false;
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const stopWatcher = watch(mapPosition, (updatedPos, oldPos) => {
|
|
218
|
+
// Any pan or zoom changes the viewport bbox; re-check data presence.
|
|
219
|
+
scheduleMosaicDataCheck();
|
|
220
|
+
const [_oldX, _oldY, oldZ] = oldPos;
|
|
221
|
+
const [_x, _y, z] = updatedPos;
|
|
222
|
+
if (!z || z === oldZ) return;
|
|
223
|
+
toggleMosaicVisibility(z);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Re-check when filters/time change, and re-apply visibility once it lands.
|
|
227
|
+
const stopQueryWatch = watch(query, () => scheduleMosaicDataCheck());
|
|
228
|
+
|
|
229
|
+
onMounted(async () => {
|
|
230
|
+
if (!shouldInitiate()) return;
|
|
231
|
+
initMosaic(mosaicEndpoint, timeRange?.value);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
useOnLayersUpdate((evt) => {
|
|
235
|
+
if (evt !== "layers:updated") return;
|
|
236
|
+
if (!shouldInitiate()) return;
|
|
237
|
+
initMosaic(mosaicEndpoint, timeRange?.value);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
onUnmounted(() => {
|
|
241
|
+
latestLayer.value = null;
|
|
242
|
+
query.value = null;
|
|
243
|
+
hasDataInView.value = true;
|
|
244
|
+
stopWatcher();
|
|
245
|
+
stopQueryWatch();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Shared debounced scheduler for mosaic updates. All callers share a single
|
|
251
|
+
* timer so concurrent updates (e.g. datetime + filter changes from different
|
|
252
|
+
* widgets within the debounce window) coalesce into one final update.
|
|
253
|
+
*
|
|
254
|
+
* @returns {(mosaicEndpoint: string | undefined | null, timeRange: [string,string] | undefined, filters?: import("@/types").ItemFilterFilters) => void}
|
|
255
|
+
*/
|
|
256
|
+
const useScheduleMosaicUpdate = createSharedComposable(() =>
|
|
257
|
+
useDebounceFn(
|
|
258
|
+
/**
|
|
259
|
+
* @param {string | undefined | null} mosaicEndpoint
|
|
260
|
+
* @param {[string, string] | undefined} timeRange
|
|
261
|
+
* @param {import("@/types").ItemFilterFilters} [filters]
|
|
262
|
+
*/
|
|
263
|
+
(mosaicEndpoint, timeRange, filters) => {
|
|
264
|
+
updateMosaicLayer(mosaicEndpoint, { timeRange, filters });
|
|
265
|
+
},
|
|
266
|
+
300,
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Creates a layer configuration for the mosaic.
|
|
272
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
273
|
+
* @param {Record<string, string> | null} [params] - Query params (datetime, filter, etc.).
|
|
274
|
+
* @returns {Promise<Record<string, any>[]>}
|
|
275
|
+
*/
|
|
276
|
+
async function createMosaicLayers(mosaicEndpoint, params) {
|
|
277
|
+
const { selectedStac } = useSTAcStore();
|
|
278
|
+
const renders = resolveRenders(selectedStac, useEodash()?.options?.renders);
|
|
279
|
+
const preset = renders ? Object.values(renders)[0] : undefined;
|
|
280
|
+
if (!preset) {
|
|
281
|
+
console.warn(
|
|
282
|
+
"[eodash] No render preset (collection `renders` nor `options.renders`) for the mosaic layer.",
|
|
283
|
+
);
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const renderParamsStr = encodeURLObject({
|
|
288
|
+
assets: preset.assets,
|
|
289
|
+
expression: preset.expression,
|
|
290
|
+
nodata: preset.nodata,
|
|
291
|
+
resampling: preset.resampling,
|
|
292
|
+
color_formula: preset.color_formula,
|
|
293
|
+
colormap: preset.colormap,
|
|
294
|
+
colormap_name: preset.colormap_name,
|
|
295
|
+
rescale: preset.rescale,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const tileParams = new URLSearchParams({ tilesize: "512", ...params });
|
|
299
|
+
const tileJsonUrl = `${mosaicEndpoint}?${renderParamsStr}${tileParams.toString()}`;
|
|
300
|
+
|
|
301
|
+
const tileJSON = await axios
|
|
302
|
+
.get(tileJsonUrl)
|
|
303
|
+
.then((res) => res.data)
|
|
304
|
+
.catch((err) => {
|
|
305
|
+
console.error("Failed to fetch mosaic TileJSON", err);
|
|
306
|
+
return null;
|
|
307
|
+
});
|
|
308
|
+
if (!tileJSON?.tiles?.[0]) {
|
|
309
|
+
console.warn("[eodash] No tile URL found in mosaic TileJSON response.");
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
type: "Tile",
|
|
316
|
+
minZoom: useMosaicState().visibilityThreshold.value,
|
|
317
|
+
properties: {
|
|
318
|
+
id: `${indicator.value};:;mosaic`,
|
|
319
|
+
title: "Mosaic Layer",
|
|
320
|
+
},
|
|
321
|
+
source: {
|
|
322
|
+
type: "XYZ",
|
|
323
|
+
url: tileJSON.tiles[0],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {string | Date} time
|
|
331
|
+
* @returns {string | null}
|
|
332
|
+
*/
|
|
333
|
+
function timeToDate(time) {
|
|
334
|
+
if (!time) return null;
|
|
335
|
+
const date = new Date(time);
|
|
336
|
+
if (isNaN(date.getTime())) return null;
|
|
337
|
+
return date.toISOString().split("T")[0];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Returns the OL-equivalent zoom from `mapPosition[2]`. In 2D the raw value
|
|
342
|
+
* is already the OL zoom. In globe mode the raw value is camera altitude in
|
|
343
|
+
* meters; convert it via eox-map's internal formula so callers can compare
|
|
344
|
+
* against a single (2D-style) threshold.
|
|
345
|
+
*
|
|
346
|
+
* @param {number} rawZ
|
|
347
|
+
* @returns {number}
|
|
348
|
+
*/
|
|
349
|
+
function normalizeGlobeZoom(rawZ) {
|
|
350
|
+
if (!isGlobe.value) return rawZ;
|
|
351
|
+
const zoomFactor = mapEl.value?.globeConfig?.useHighLOD ? 1 : 2;
|
|
352
|
+
return Math.log2(21_050_000 / rawZ) + zoomFactor;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* check whether the filtered dataset has any assets in the current 2D view
|
|
357
|
+
*/
|
|
358
|
+
async function checkMosaicDataInView() {
|
|
359
|
+
const { hasDataInView, query, mosaicEndpoint, visibilityThreshold } =
|
|
360
|
+
useMosaicState();
|
|
361
|
+
const extent = mapEl.value?.lonLatExtent;
|
|
362
|
+
if (
|
|
363
|
+
!extent ||
|
|
364
|
+
isGlobe.value ||
|
|
365
|
+
!mosaicEndpoint.value ||
|
|
366
|
+
normalizeGlobeZoom(mapPosition.value[2] ?? 0) < visibilityThreshold.value
|
|
367
|
+
) {
|
|
368
|
+
hasDataInView.value = true;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (extent.some(Number.isNaN)) return;
|
|
373
|
+
|
|
374
|
+
const base = mosaicEndpoint.value.replace(
|
|
375
|
+
"/WebMercatorQuad/tilejson.json",
|
|
376
|
+
"",
|
|
377
|
+
);
|
|
378
|
+
const [minx, miny, maxx, maxy] = extent;
|
|
379
|
+
const params = new URLSearchParams({ limit: "1", ...(query.value ?? {}) });
|
|
380
|
+
try {
|
|
381
|
+
const { data } = await axios.get(
|
|
382
|
+
`${base}/bbox/${minx},${miny},${maxx},${maxy}/assets?${params}`,
|
|
383
|
+
);
|
|
384
|
+
const count = Array.isArray(data)
|
|
385
|
+
? data.length
|
|
386
|
+
: Object.keys(data ?? {}).length;
|
|
387
|
+
hasDataInView.value = count > 0;
|
|
388
|
+
} catch {
|
|
389
|
+
hasDataInView.value = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Shared debounced data-presence check; coalesces rapid pan/zoom/filter changes. */
|
|
394
|
+
const scheduleMosaicDataCheck = useDebounceFn(checkMosaicDataInView, 300);
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @param {number} zoomLevel
|
|
398
|
+
* @param {number} [threshold] - defaults to the shared visibility threshold
|
|
399
|
+
*/
|
|
400
|
+
function toggleMosaicVisibility(zoomLevel, threshold) {
|
|
401
|
+
// Only globe needs manual toggling.
|
|
402
|
+
if (!isGlobe.value) return;
|
|
403
|
+
const { latestLayer, visibilityThreshold } = useMosaicState();
|
|
404
|
+
if (!latestLayer.value) return;
|
|
405
|
+
|
|
406
|
+
const layerId = /** @type {string} */ (latestLayer.value.properties.id);
|
|
407
|
+
const layer = mapEl.value?.getLayerById(layerId);
|
|
408
|
+
if (!layer) return;
|
|
409
|
+
|
|
410
|
+
const eqZoom = normalizeGlobeZoom(zoomLevel);
|
|
411
|
+
layer.setVisible(eqZoom >= (visibilityThreshold.value));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Writes layers into the AnalysisGroup and triggers a map re-render.
|
|
416
|
+
* @param {Record<string, any>[]} layersToAssign
|
|
417
|
+
*/
|
|
418
|
+
function assignAnalysisLayer(layersToAssign) {
|
|
419
|
+
const mapLayers = getLayers();
|
|
420
|
+
const { analysisGroup, layers } = ensureAnalysisGroup(mapLayers);
|
|
421
|
+
analysisGroup.layers = layersToAssign;
|
|
422
|
+
if (mapEl.value) {
|
|
423
|
+
// Reassign to trigger map re-render after in-place layer mutation
|
|
424
|
+
mapEl.value.layers = /** @type {import("@eox/map").EoxLayer[]} */ ([
|
|
425
|
+
...layers,
|
|
426
|
+
]);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Ensure the AnalysisGroup exists in the layers collection.
|
|
432
|
+
* @param {Record<string, any>[]} layersCollection
|
|
433
|
+
* @returns {{ layers: Record<string, any>[], analysisGroup: Record<string, any> }}
|
|
434
|
+
*/
|
|
435
|
+
function ensureAnalysisGroup(layersCollection) {
|
|
436
|
+
let analysisGroup = layersCollection.find(
|
|
437
|
+
(l) => l?.properties?.id === "AnalysisGroup",
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (!analysisGroup) {
|
|
441
|
+
analysisGroup = {
|
|
442
|
+
type: "Group",
|
|
443
|
+
properties: {
|
|
444
|
+
id: "AnalysisGroup",
|
|
445
|
+
title: "Data Layers",
|
|
446
|
+
},
|
|
447
|
+
layers: [],
|
|
448
|
+
};
|
|
449
|
+
layersCollection.push(analysisGroup);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return { layers: layersCollection, analysisGroup };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export { useScheduleMosaicUpdate as a, buildCqlFilter as b, useInitMosaic as c, normalizeGlobeZoom as n, renderLatestMosaic as r, useMosaicState as u };
|