@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
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import axios from "@/plugins/axios";
|
|
2
|
+
import {
|
|
3
|
+
datetime,
|
|
4
|
+
indicator,
|
|
5
|
+
isGlobe,
|
|
6
|
+
mapEl,
|
|
7
|
+
mapPosition,
|
|
8
|
+
} from "@/store/states";
|
|
9
|
+
import { getLayers } from "@/store/actions";
|
|
10
|
+
import {
|
|
11
|
+
computed,
|
|
12
|
+
nextTick,
|
|
13
|
+
onMounted,
|
|
14
|
+
onUnmounted,
|
|
15
|
+
ref,
|
|
16
|
+
shallowRef,
|
|
17
|
+
watch,
|
|
18
|
+
} from "vue";
|
|
19
|
+
import {
|
|
20
|
+
createSharedComposable,
|
|
21
|
+
useDebounceFn,
|
|
22
|
+
useEventBus,
|
|
23
|
+
} from "@vueuse/core";
|
|
24
|
+
import { useEodash, useOnLayersUpdate } from "@/composables";
|
|
25
|
+
import {
|
|
26
|
+
encodeURLObject,
|
|
27
|
+
extractLayerTimeValues,
|
|
28
|
+
resolveRenders,
|
|
29
|
+
} from "./helpers";
|
|
30
|
+
import { useSTAcStore } from "@/store/stac";
|
|
31
|
+
import { eodashCollections } from "@/utils/states";
|
|
32
|
+
import log from "loglevel";
|
|
33
|
+
import { buildCqlFilter } from "./cql";
|
|
34
|
+
|
|
35
|
+
const mosaicReturnToOverviewKey = Symbol("mosaic:return-to-overview");
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shared mosaic state across all components. Stores the latest mosaic layer,
|
|
39
|
+
* query parameters, visibility threshold, item-view flag, and an event bus
|
|
40
|
+
* for the "Back to overview" user action .
|
|
41
|
+
*/
|
|
42
|
+
export const useMosaicState = createSharedComposable(() => {
|
|
43
|
+
/** @type {import("vue").ShallowRef<Record<string, any> | null>} */
|
|
44
|
+
const latestLayer = shallowRef(null);
|
|
45
|
+
/** @type {import("vue").Ref<Record<string, string> | null>} */
|
|
46
|
+
const query = ref(null);
|
|
47
|
+
/** @type {import("vue").Ref<number>} */
|
|
48
|
+
const visibilityThreshold = ref(8);
|
|
49
|
+
/** @type {import("vue").Ref<boolean>} */
|
|
50
|
+
const isItemView = ref(false);
|
|
51
|
+
/** @type {import("vue").Ref<boolean>} - filtered mosaic has assets in the current view */
|
|
52
|
+
const hasDataInView = ref(true);
|
|
53
|
+
const returnToOverview = useEventBus(mosaicReturnToOverviewKey);
|
|
54
|
+
|
|
55
|
+
const store = useSTAcStore();
|
|
56
|
+
/** @type {import("vue").ComputedRef<string | null>} */
|
|
57
|
+
const mosaicEndpoint = computed(() => {
|
|
58
|
+
if (!store.rasterEndpoint || !store.selectedStac?.id) return null;
|
|
59
|
+
return `${store.rasterEndpoint}/collections/${store.selectedStac.id}/WebMercatorQuad/tilejson.json`;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
latestLayer,
|
|
64
|
+
query,
|
|
65
|
+
visibilityThreshold,
|
|
66
|
+
isItemView,
|
|
67
|
+
hasDataInView,
|
|
68
|
+
returnToOverview,
|
|
69
|
+
mosaicEndpoint,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Modifies the layers collection to display the mosaic layer.
|
|
75
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
76
|
+
* @param {Record<string, string> | null} [params] - Query params forwarded to the tilejson URL.
|
|
77
|
+
*/
|
|
78
|
+
export async function renderMosaic(mosaicEndpoint, params) {
|
|
79
|
+
const mosaicLayers = await createMosaicLayers(mosaicEndpoint, params);
|
|
80
|
+
|
|
81
|
+
if (!mosaicLayers.length) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { selectedStac } = useSTAcStore();
|
|
86
|
+
if (selectedStac) {
|
|
87
|
+
const eodashCol = eodashCollections.find(
|
|
88
|
+
(ec) => ec.collectionStac?.id === selectedStac.id,
|
|
89
|
+
);
|
|
90
|
+
const dates = (await eodashCol?.getDates()) ?? [];
|
|
91
|
+
const { timeControlValues } = extractLayerTimeValues(dates, datetime.value);
|
|
92
|
+
if (timeControlValues) {
|
|
93
|
+
mosaicLayers[0].properties.timeControlValues = timeControlValues;
|
|
94
|
+
mosaicLayers[0].properties.timeControlProperty = "TIME";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
assignAnalysisLayer(mosaicLayers);
|
|
99
|
+
const { latestLayer } = useMosaicState();
|
|
100
|
+
latestLayer.value = mosaicLayers[0];
|
|
101
|
+
log.debug("[eodash] Mosaic layer rendered.", mosaicLayers[0].source.url);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Updates the mosaic layer based on the current filters.
|
|
106
|
+
* @param {string | undefined | null} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
107
|
+
* @param {{ timeRange?: [string, string]; filters?: import("@/types").ItemFilterFilters }} [queries]
|
|
108
|
+
*/
|
|
109
|
+
export async function updateMosaicLayer(
|
|
110
|
+
mosaicEndpoint,
|
|
111
|
+
{ timeRange, filters } = {},
|
|
112
|
+
) {
|
|
113
|
+
if (!mosaicEndpoint) return;
|
|
114
|
+
|
|
115
|
+
/** @type {Record<string, string>} */
|
|
116
|
+
const params = {};
|
|
117
|
+
|
|
118
|
+
if (timeRange && Array.isArray(timeRange) && timeRange.length === 2) {
|
|
119
|
+
const start = timeToDate(timeRange[0]);
|
|
120
|
+
const end = timeToDate(timeRange[1]);
|
|
121
|
+
if (start && end) {
|
|
122
|
+
params.datetime = start === end ? start : `${start}/${end}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const cqlFilter = buildCqlFilter(filters);
|
|
127
|
+
if (cqlFilter) {
|
|
128
|
+
params.filter = cqlFilter;
|
|
129
|
+
params["filter-lang"] = "cql2-text";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const queryParams = Object.keys(params).length > 0 ? params : null;
|
|
133
|
+
const { query } = useMosaicState();
|
|
134
|
+
query.value = queryParams;
|
|
135
|
+
await renderMosaic(mosaicEndpoint, queryParams);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Renders the latest mosaic layer stored in the shared mosaic state.
|
|
140
|
+
*/
|
|
141
|
+
export function renderLatestMosaic() {
|
|
142
|
+
const { latestLayer } = useMosaicState();
|
|
143
|
+
if (!latestLayer.value) return;
|
|
144
|
+
assignAnalysisLayer([latestLayer.value]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
149
|
+
* @param {[string, string] | undefined} timeRange
|
|
150
|
+
*/
|
|
151
|
+
export async function initMosaic(mosaicEndpoint, timeRange) {
|
|
152
|
+
await updateMosaicLayer(mosaicEndpoint, { timeRange });
|
|
153
|
+
await nextTick();
|
|
154
|
+
const zoom =
|
|
155
|
+
mapPosition.value[2] ?? mapEl.value?.map?.getView()?.getZoom() ?? 0;
|
|
156
|
+
toggleMosaicVisibility(zoom);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Vue composable that initializes the mosaic layer and keeps it in sync with
|
|
161
|
+
* map zoom and layer updates.
|
|
162
|
+
* @param {string | null | undefined} mosaicEndpoint - Pass null/undefined to disable.
|
|
163
|
+
* @param {import("vue").Ref<[string, string]> | undefined} timeRange
|
|
164
|
+
* @param {string[]} [indicators] - If provided, mosaic only activates for these collection IDs.
|
|
165
|
+
*/
|
|
166
|
+
export function useInitMosaic(mosaicEndpoint, timeRange, indicators) {
|
|
167
|
+
if (!mosaicEndpoint) return;
|
|
168
|
+
|
|
169
|
+
const store = useSTAcStore();
|
|
170
|
+
const { isItemView, latestLayer, query, hasDataInView } = useMosaicState();
|
|
171
|
+
|
|
172
|
+
function shouldInitiate() {
|
|
173
|
+
if (isItemView.value) return false;
|
|
174
|
+
if (!store.selectedStac?.id) return false;
|
|
175
|
+
if (indicators !== undefined && !indicators.includes(store.selectedStac.id))
|
|
176
|
+
return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const stopWatcher = watch(mapPosition, (updatedPos, oldPos) => {
|
|
181
|
+
// Any pan or zoom changes the viewport bbox; re-check data presence.
|
|
182
|
+
scheduleMosaicDataCheck();
|
|
183
|
+
const [_oldX, _oldY, oldZ] = oldPos;
|
|
184
|
+
const [_x, _y, z] = updatedPos;
|
|
185
|
+
if (!z || z === oldZ) return;
|
|
186
|
+
toggleMosaicVisibility(z);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Re-check when filters/time change, and re-apply visibility once it lands.
|
|
190
|
+
const stopQueryWatch = watch(query, () => scheduleMosaicDataCheck());
|
|
191
|
+
|
|
192
|
+
onMounted(async () => {
|
|
193
|
+
if (!shouldInitiate()) return;
|
|
194
|
+
initMosaic(mosaicEndpoint, timeRange?.value);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
useOnLayersUpdate((evt) => {
|
|
198
|
+
if (evt !== "layers:updated") return;
|
|
199
|
+
if (!shouldInitiate()) return;
|
|
200
|
+
initMosaic(mosaicEndpoint, timeRange?.value);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
onUnmounted(() => {
|
|
204
|
+
latestLayer.value = null;
|
|
205
|
+
query.value = null;
|
|
206
|
+
hasDataInView.value = true;
|
|
207
|
+
stopWatcher();
|
|
208
|
+
stopQueryWatch();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Shared debounced scheduler for mosaic updates. All callers share a single
|
|
214
|
+
* timer so concurrent updates (e.g. datetime + filter changes from different
|
|
215
|
+
* widgets within the debounce window) coalesce into one final update.
|
|
216
|
+
*
|
|
217
|
+
* @returns {(mosaicEndpoint: string | undefined | null, timeRange: [string,string] | undefined, filters?: import("@/types").ItemFilterFilters) => void}
|
|
218
|
+
*/
|
|
219
|
+
export const useScheduleMosaicUpdate = createSharedComposable(() =>
|
|
220
|
+
useDebounceFn(
|
|
221
|
+
/**
|
|
222
|
+
* @param {string | undefined | null} mosaicEndpoint
|
|
223
|
+
* @param {[string, string] | undefined} timeRange
|
|
224
|
+
* @param {import("@/types").ItemFilterFilters} [filters]
|
|
225
|
+
*/
|
|
226
|
+
(mosaicEndpoint, timeRange, filters) => {
|
|
227
|
+
updateMosaicLayer(mosaicEndpoint, { timeRange, filters });
|
|
228
|
+
},
|
|
229
|
+
300,
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Creates a layer configuration for the mosaic.
|
|
235
|
+
* @param {string} mosaicEndpoint - The TileJSON URL for the mosaic.
|
|
236
|
+
* @param {Record<string, string> | null} [params] - Query params (datetime, filter, etc.).
|
|
237
|
+
* @returns {Promise<Record<string, any>[]>}
|
|
238
|
+
*/
|
|
239
|
+
async function createMosaicLayers(mosaicEndpoint, params) {
|
|
240
|
+
const { selectedStac } = useSTAcStore();
|
|
241
|
+
const renders = resolveRenders(selectedStac, useEodash()?.options?.renders);
|
|
242
|
+
const preset = renders ? Object.values(renders)[0] : undefined;
|
|
243
|
+
if (!preset) {
|
|
244
|
+
console.warn(
|
|
245
|
+
"[eodash] No render preset (collection `renders` nor `options.renders`) for the mosaic layer.",
|
|
246
|
+
);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const renderParamsStr = encodeURLObject({
|
|
251
|
+
assets: preset.assets,
|
|
252
|
+
expression: preset.expression,
|
|
253
|
+
nodata: preset.nodata,
|
|
254
|
+
resampling: preset.resampling,
|
|
255
|
+
color_formula: preset.color_formula,
|
|
256
|
+
colormap: preset.colormap,
|
|
257
|
+
colormap_name: preset.colormap_name,
|
|
258
|
+
rescale: preset.rescale,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const tileParams = new URLSearchParams({ tilesize: "512", ...params });
|
|
262
|
+
const tileJsonUrl = `${mosaicEndpoint}?${renderParamsStr}${tileParams.toString()}`;
|
|
263
|
+
|
|
264
|
+
const tileJSON = await axios
|
|
265
|
+
.get(tileJsonUrl)
|
|
266
|
+
.then((res) => res.data)
|
|
267
|
+
.catch((err) => {
|
|
268
|
+
console.error("Failed to fetch mosaic TileJSON", err);
|
|
269
|
+
return null;
|
|
270
|
+
});
|
|
271
|
+
if (!tileJSON?.tiles?.[0]) {
|
|
272
|
+
console.warn("[eodash] No tile URL found in mosaic TileJSON response.");
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
type: "Tile",
|
|
279
|
+
minZoom: useMosaicState().visibilityThreshold.value,
|
|
280
|
+
properties: {
|
|
281
|
+
id: `${indicator.value};:;mosaic`,
|
|
282
|
+
title: "Mosaic Layer",
|
|
283
|
+
},
|
|
284
|
+
source: {
|
|
285
|
+
type: "XYZ",
|
|
286
|
+
url: tileJSON.tiles[0],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @param {string | Date} time
|
|
294
|
+
* @returns {string | null}
|
|
295
|
+
*/
|
|
296
|
+
function timeToDate(time) {
|
|
297
|
+
if (!time) return null;
|
|
298
|
+
const date = new Date(time);
|
|
299
|
+
if (isNaN(date.getTime())) return null;
|
|
300
|
+
return date.toISOString().split("T")[0];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Returns the OL-equivalent zoom from `mapPosition[2]`. In 2D the raw value
|
|
305
|
+
* is already the OL zoom. In globe mode the raw value is camera altitude in
|
|
306
|
+
* meters; convert it via eox-map's internal formula so callers can compare
|
|
307
|
+
* against a single (2D-style) threshold.
|
|
308
|
+
*
|
|
309
|
+
* @param {number} rawZ
|
|
310
|
+
* @returns {number}
|
|
311
|
+
*/
|
|
312
|
+
export function normalizeGlobeZoom(rawZ) {
|
|
313
|
+
if (!isGlobe.value) return rawZ;
|
|
314
|
+
const zoomFactor = mapEl.value?.globeConfig?.useHighLOD ? 1 : 2;
|
|
315
|
+
return Math.log2(21_050_000 / rawZ) + zoomFactor;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* check whether the filtered dataset has any assets in the current 2D view
|
|
320
|
+
*/
|
|
321
|
+
export async function checkMosaicDataInView() {
|
|
322
|
+
const { hasDataInView, query, mosaicEndpoint, visibilityThreshold } =
|
|
323
|
+
useMosaicState();
|
|
324
|
+
const extent = mapEl.value?.lonLatExtent;
|
|
325
|
+
if (
|
|
326
|
+
!extent ||
|
|
327
|
+
isGlobe.value ||
|
|
328
|
+
!mosaicEndpoint.value ||
|
|
329
|
+
normalizeGlobeZoom(mapPosition.value[2] ?? 0) < visibilityThreshold.value
|
|
330
|
+
) {
|
|
331
|
+
hasDataInView.value = true;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (extent.some(Number.isNaN)) return;
|
|
336
|
+
|
|
337
|
+
const base = mosaicEndpoint.value.replace(
|
|
338
|
+
"/WebMercatorQuad/tilejson.json",
|
|
339
|
+
"",
|
|
340
|
+
);
|
|
341
|
+
const [minx, miny, maxx, maxy] = extent;
|
|
342
|
+
const params = new URLSearchParams({ limit: "1", ...(query.value ?? {}) });
|
|
343
|
+
try {
|
|
344
|
+
const { data } = await axios.get(
|
|
345
|
+
`${base}/bbox/${minx},${miny},${maxx},${maxy}/assets?${params}`,
|
|
346
|
+
);
|
|
347
|
+
const count = Array.isArray(data)
|
|
348
|
+
? data.length
|
|
349
|
+
: Object.keys(data ?? {}).length;
|
|
350
|
+
hasDataInView.value = count > 0;
|
|
351
|
+
} catch {
|
|
352
|
+
hasDataInView.value = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Shared debounced data-presence check; coalesces rapid pan/zoom/filter changes. */
|
|
357
|
+
const scheduleMosaicDataCheck = useDebounceFn(checkMosaicDataInView, 300);
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @param {number} zoomLevel
|
|
361
|
+
* @param {number} [threshold] - defaults to the shared visibility threshold
|
|
362
|
+
*/
|
|
363
|
+
function toggleMosaicVisibility(zoomLevel, threshold) {
|
|
364
|
+
// Only globe needs manual toggling.
|
|
365
|
+
if (!isGlobe.value) return;
|
|
366
|
+
const { latestLayer, visibilityThreshold } = useMosaicState();
|
|
367
|
+
if (!latestLayer.value) return;
|
|
368
|
+
|
|
369
|
+
const layerId = /** @type {string} */ (latestLayer.value.properties.id);
|
|
370
|
+
const layer = mapEl.value?.getLayerById(layerId);
|
|
371
|
+
if (!layer) return;
|
|
372
|
+
|
|
373
|
+
const eqZoom = normalizeGlobeZoom(zoomLevel);
|
|
374
|
+
layer.setVisible(eqZoom >= (threshold ?? visibilityThreshold.value));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Writes layers into the AnalysisGroup and triggers a map re-render.
|
|
379
|
+
* @param {Record<string, any>[]} layersToAssign
|
|
380
|
+
*/
|
|
381
|
+
function assignAnalysisLayer(layersToAssign) {
|
|
382
|
+
const mapLayers = getLayers();
|
|
383
|
+
const { analysisGroup, layers } = ensureAnalysisGroup(mapLayers);
|
|
384
|
+
analysisGroup.layers = layersToAssign;
|
|
385
|
+
if (mapEl.value) {
|
|
386
|
+
// Reassign to trigger map re-render after in-place layer mutation
|
|
387
|
+
mapEl.value.layers = /** @type {import("@eox/map").EoxLayer[]} */ ([
|
|
388
|
+
...layers,
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Ensure the AnalysisGroup exists in the layers collection.
|
|
395
|
+
* @param {Record<string, any>[]} layersCollection
|
|
396
|
+
* @returns {{ layers: Record<string, any>[], analysisGroup: Record<string, any> }}
|
|
397
|
+
*/
|
|
398
|
+
function ensureAnalysisGroup(layersCollection) {
|
|
399
|
+
let analysisGroup = layersCollection.find(
|
|
400
|
+
(l) => l?.properties?.id === "AnalysisGroup",
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
if (!analysisGroup) {
|
|
404
|
+
analysisGroup = {
|
|
405
|
+
type: "Group",
|
|
406
|
+
properties: {
|
|
407
|
+
id: "AnalysisGroup",
|
|
408
|
+
title: "Data Layers",
|
|
409
|
+
},
|
|
410
|
+
layers: [],
|
|
411
|
+
};
|
|
412
|
+
layersCollection.push(analysisGroup);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return { layers: layersCollection, analysisGroup };
|
|
416
|
+
}
|
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import Axios from "axios";
|
|
2
2
|
import { setupCache } from "axios-cache-interceptor";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { loading } from "@/store/states";
|
|
5
5
|
|
|
6
|
+
const instance = Axios.create();
|
|
6
7
|
export const axios = setupCache(instance, { cacheTakeover: false });
|
|
7
8
|
|
|
9
|
+
function installLoadingInterceptors() {
|
|
10
|
+
const reqId = axios.interceptors.request.use((config) => {
|
|
11
|
+
loading.activeLoads++;
|
|
12
|
+
return config;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const resId = axios.interceptors.response.use(
|
|
16
|
+
(response) => {
|
|
17
|
+
loading.activeLoads = Math.max(0, loading.activeLoads - 1);
|
|
18
|
+
return response;
|
|
19
|
+
},
|
|
20
|
+
(error) => {
|
|
21
|
+
loading.activeLoads = Math.max(0, loading.activeLoads - 1);
|
|
22
|
+
return Promise.reject(error);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
axios.interceptors.request.eject(reqId);
|
|
28
|
+
axios.interceptors.response.eject(resId);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const dispose = installLoadingInterceptors();
|
|
33
|
+
|
|
34
|
+
if (import.meta.hot) {
|
|
35
|
+
import.meta.hot.dispose(() => dispose());
|
|
36
|
+
}
|
|
37
|
+
|
|
8
38
|
export default axios;
|
|
@@ -20,7 +20,7 @@ export const getLayers = () => mapEl.value?.layers ?? [];
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Returns the current layers of {@link mapCompareEl}
|
|
23
|
-
*
|
|
23
|
+
* @returns {import("@eox/map").EoxLayer[]}
|
|
24
24
|
*/
|
|
25
25
|
export const getCompareLayers = () => mapCompareEl.value?.layers ?? [];
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* List of supported endpoints for upscaling
|
|
34
|
-
* @type {import("vue").Ref<string
|
|
34
|
+
* @type {import("vue").Ref<Array<string | { url: string; titilerVersion?: 1 | 2 }>>}
|
|
35
35
|
*/
|
|
36
36
|
const supportedUpscalingEndpoints = ref([]);
|
|
37
37
|
|
|
@@ -61,11 +61,20 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
61
61
|
* >}
|
|
62
62
|
*/
|
|
63
63
|
const selectedCompareStac = ref(null);
|
|
64
|
+
/**
|
|
65
|
+
* Currently selected item
|
|
66
|
+
* @type {import("vue").Ref<import("stac-ts").StacLink | import("stac-ts").StacItem | null>}
|
|
67
|
+
*/
|
|
64
68
|
/**
|
|
65
69
|
* Currently selected item
|
|
66
70
|
* @type {import("vue").Ref<import("stac-ts").StacLink | import("stac-ts").StacItem | null>}
|
|
67
71
|
*/
|
|
68
72
|
const selectedItem = ref(null);
|
|
73
|
+
/**
|
|
74
|
+
* Currently selected compare item
|
|
75
|
+
* @type {import("vue").Ref<import("stac-ts").StacLink | import("stac-ts").StacItem | null>}
|
|
76
|
+
*/
|
|
77
|
+
const selectedCompareItem = ref(null);
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
80
|
* Initializes the store by assigning the STAC endpoint.
|
|
@@ -151,8 +160,8 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
151
160
|
}
|
|
152
161
|
//@ts-expect-error "this" type is not exported by pinia
|
|
153
162
|
const patch = this?.$patch;
|
|
154
|
-
if (
|
|
155
|
-
patch({ selectedItem: stacItem });
|
|
163
|
+
if (patch) {
|
|
164
|
+
patch({ selectedItem: stacItem ?? null });
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
await axios
|
|
@@ -182,10 +191,15 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
182
191
|
*
|
|
183
192
|
* @param {string} relativePath - Stac link href
|
|
184
193
|
* @param {boolean} [isPOI=false] - If true, the STAC is loaded for a point of interest
|
|
194
|
+
* @param {Object} [stacItem] - The STAC item to load
|
|
185
195
|
* @returns {Promise<void>}
|
|
186
196
|
* @see {@link selectedCompareStac}
|
|
187
197
|
*/
|
|
188
|
-
async function loadSelectedCompareSTAC(
|
|
198
|
+
async function loadSelectedCompareSTAC(
|
|
199
|
+
relativePath = "",
|
|
200
|
+
isPOI = false,
|
|
201
|
+
stacItem,
|
|
202
|
+
) {
|
|
189
203
|
if (!stacEndpoint.value) {
|
|
190
204
|
return Promise.reject(
|
|
191
205
|
new Error("STAC endpoint is not defined in eodash configuration"),
|
|
@@ -196,6 +210,11 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
196
210
|
// construct absolute URL of a poi
|
|
197
211
|
absoluteUrl.value = constructPoiUrl(relativePath, compareIndicator.value);
|
|
198
212
|
}
|
|
213
|
+
//@ts-expect-error "this" type is not exported by pinia
|
|
214
|
+
const patch = this?.$patch;
|
|
215
|
+
if (stacItem && patch) {
|
|
216
|
+
patch({ selectedCompareItem: stacItem });
|
|
217
|
+
}
|
|
199
218
|
await axios
|
|
200
219
|
.get(absoluteUrl.value)
|
|
201
220
|
.then(async (resp) => {
|
|
@@ -247,6 +266,7 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
247
266
|
|
|
248
267
|
return {
|
|
249
268
|
stacEndpoint,
|
|
269
|
+
rasterEndpoint,
|
|
250
270
|
isApi,
|
|
251
271
|
stac,
|
|
252
272
|
init,
|
|
@@ -257,6 +277,7 @@ export const useSTAcStore = defineStore("stac", () => {
|
|
|
257
277
|
selectedStac,
|
|
258
278
|
selectedCompareStac,
|
|
259
279
|
selectedItem,
|
|
280
|
+
selectedCompareItem,
|
|
260
281
|
supportedUpscalingEndpoints,
|
|
261
282
|
};
|
|
262
283
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import log from "loglevel";
|
|
3
3
|
log.setLevel(log.levels.WARN, true);
|
|
4
4
|
|
|
5
|
-
import { ref, shallowRef } from "vue";
|
|
5
|
+
import { reactive, ref, shallowRef } from "vue";
|
|
6
6
|
|
|
7
7
|
/** Currently selected STAC endpoint */
|
|
8
8
|
export const currentUrl = ref("");
|
|
@@ -30,7 +30,7 @@ export const registeredProjections = ["EPSG:4326", "EPSG:3857"];
|
|
|
30
30
|
/** available projection to be rendered by `EodashMap` */
|
|
31
31
|
export const availableMapProjection = ref("EPSG:3857");
|
|
32
32
|
|
|
33
|
-
/** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
|
|
33
|
+
/** @type {import("vue").Ref<import("@eox/map").EOxMap & { mapUpdateId?: number } | null>} */
|
|
34
34
|
export const mapEl = shallowRef(null);
|
|
35
35
|
|
|
36
36
|
/** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
|
|
@@ -73,3 +73,21 @@ export const chartSpec = ref(null);
|
|
|
73
73
|
* @type {import("vue").Ref<import("vega-embed").VisualizationSpec | null>}
|
|
74
74
|
*/
|
|
75
75
|
export const compareChartSpec = ref(null);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Global loading state.
|
|
79
|
+
* - `loading.activeLoads` — increment/decrement to track concurrent loads
|
|
80
|
+
* - `loading.value` — derived boolean, true when `activeLoads > 0`
|
|
81
|
+
*/
|
|
82
|
+
export const loading = reactive({
|
|
83
|
+
activeLoads: 0,
|
|
84
|
+
get value() {
|
|
85
|
+
return this.activeLoads > 0;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Adapter allows external widgets to hook into tooltip property transformation
|
|
91
|
+
* @type {import("vue").Ref<((param: {key: string, value: any}, map?: string) => {key: string, value: any} | undefined) | null>}
|
|
92
|
+
*/
|
|
93
|
+
export const tooltipAdapter = shallowRef(null);
|