@eodash/eodash 5.7.0 → 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.
Files changed (113) hide show
  1. package/core/client/components/EodashOverlay.vue +27 -4
  2. package/core/client/composables/DefineEodash.js +0 -1
  3. package/core/client/composables/DefineWidgets.js +3 -1
  4. package/core/client/eodashSTAC/EodashCollection.js +74 -20
  5. package/core/client/eodashSTAC/cql.js +64 -0
  6. package/core/client/eodashSTAC/createLayers.js +169 -116
  7. package/core/client/eodashSTAC/helpers.js +391 -98
  8. package/core/client/eodashSTAC/mosaic.js +416 -0
  9. package/core/client/plugins/axios.js +31 -1
  10. package/core/client/store/actions.js +1 -1
  11. package/core/client/store/stac.js +25 -4
  12. package/core/client/store/states.js +20 -2
  13. package/core/client/types.ts +147 -28
  14. package/core/client/utils/bands-editor/exampleSchema.json +50 -50
  15. package/core/client/utils/states.js +10 -0
  16. package/dist/client/{DashboardLayout-kRsmsNW5.js → DashboardLayout-ePbdvUTE.js} +2 -2
  17. package/dist/client/{DynamicWebComponent-CnKhYo0K.js → DynamicWebComponent-g5SdRbjY.js} +1 -1
  18. package/dist/client/{EodashChart-CLDqE93C.js → EodashChart-ChrkA5oS.js} +7 -6
  19. package/dist/client/{EodashDatePicker-CMPDT-ce.js → EodashDatePicker-CY1TrsDo.js} +3 -3
  20. package/dist/client/{EodashItemFilter-BpdA8QQa.js → EodashItemFilter-DzshHDlS.js} +2 -1
  21. package/dist/client/{EodashLayerControl-BHpDA4_0.js → EodashLayerControl-DMCeMkSd.js} +50 -20
  22. package/dist/client/{EodashLayoutSwitcher-B1fRUt7G.js → EodashLayoutSwitcher-iJ8mX1rF.js} +3 -3
  23. package/dist/client/{EodashMapBtns-Cpned0tc.js → EodashMapBtns-C7Cr7q5Y.js} +200 -86
  24. package/dist/client/EodashStacInfo-K7-iuXa7.js +169 -0
  25. package/dist/client/{EodashTools-1zHz26gv.js → EodashTools-BEK7e7JM.js} +4 -4
  26. package/dist/client/{ExportState-DbOm8hBI.js → ExportState-CqIzfMYQ.js} +6 -168
  27. package/dist/client/{Footer-BKg-VVkj.js → Footer-1P0cmBOZ.js} +1 -1
  28. package/dist/client/{Header-DCrwCvmY.js → Header-CkkBaBNh.js} +2 -2
  29. package/dist/client/{MobileLayout-vWSHpluo.js → MobileLayout-BPf49kDU.js} +2 -2
  30. package/dist/client/{PopUp-9uimfK49.js → PopUp-DPZq5SWI.js} +4 -168
  31. package/dist/client/{ProcessList-CTTIZa06.js → ProcessList-DuI6WpS1.js} +5 -5
  32. package/dist/client/VDivider-Bj9LG_JV.js +168 -0
  33. package/dist/client/{VImg-CrmB76ZY.js → VImg-Ddv5OyMz.js} +2 -2
  34. package/dist/client/{VMain-OKZNpcEG.js → VMain-7yqk_qC3.js} +1 -1
  35. package/dist/client/{VTooltip-CJw2FyWk.js → VTooltip-2hAJyEoa.js} +2 -2
  36. package/dist/client/{WidgetsContainer-BaWOnE5R.js → WidgetsContainer-2DOrlwCB.js} +1 -1
  37. package/dist/client/{asWebComponent-BHWpMuE3.js → asWebComponent-BlFN2lkB.js} +456 -306
  38. package/dist/client/{async-DSKj6g1f.js → async-B2H8ZwUK.js} +2 -2
  39. package/dist/client/{create-layers-config-CBoYedqB.js → create-layers-config-u_vn5hPk.js} +17 -17
  40. package/dist/client/dialog-transition-BrMkMiNa.js +170 -0
  41. package/dist/client/eo-dash.js +1 -1
  42. package/dist/client/{forwardRefs-DH51zPUr.js → forwardRefs-DAlror6d.js} +8 -87
  43. package/dist/client/{handling-BrcDDlgS.js → handling-B6I1V5eG.js} +3 -3
  44. package/dist/client/{helpers-DjZVl_WF.js → helpers-CBqIUSXJ.js} +794 -182
  45. package/dist/client/index-BWq1ZKaV.js +3168 -0
  46. package/dist/client/{index-CUjl-ZsI.js → index-CJ9UI3ln.js} +2 -2
  47. package/dist/client/{index-CSG_JoOA.js → index-DXLz4sFR.js} +62 -56
  48. package/dist/client/{index-1JKE68n3.js → index-DhKU6KqM.js} +211 -67
  49. package/dist/client/{index-Cu3-13Xg.js → index-DjQlbWbJ.js} +7 -6
  50. package/dist/client/mosaic-BFYLEwyx.js +455 -0
  51. package/dist/client/templates.js +250 -119
  52. package/dist/client/{transition-OHcz5Qq1.js → transition-D_W6Wn9v.js} +1 -1
  53. package/dist/node/cli.js +5 -5
  54. package/dist/node/types.d.ts +4 -0
  55. package/dist/types/core/client/composables/DefineEodash.d.ts +1 -1
  56. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +57 -25
  57. package/dist/types/core/client/eodashSTAC/cql.d.ts +15 -0
  58. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +42 -23
  59. package/dist/types/core/client/eodashSTAC/helpers.d.ts +77 -18
  60. package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
  61. package/dist/types/core/client/store/stac.d.ts +31 -7
  62. package/dist/types/core/client/store/states.d.ts +24 -2
  63. package/dist/types/core/client/types.d.ts +123 -24
  64. package/dist/types/core/client/utils/states.d.ts +12 -0
  65. package/dist/types/templates/baseConfig.d.ts +1 -100
  66. package/dist/types/templates/compare.d.ts +32 -3
  67. package/dist/types/templates/expert.d.ts +18 -0
  68. package/dist/types/templates/explore.d.ts +9 -1
  69. package/dist/types/templates/lite.d.ts +11 -0
  70. package/dist/types/widgets/EodashChart.vue.d.ts +5 -3
  71. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +129 -2
  72. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +28 -6
  73. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +17 -4
  74. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +16 -4
  75. package/dist/types/widgets/EodashLayerControl.vue.d.ts +35 -16
  76. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +14 -10
  77. package/dist/types/widgets/EodashMap/index.vue.d.ts +86 -37
  78. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -1
  79. package/dist/types/widgets/EodashMap/methods/index.d.ts +5 -1
  80. package/dist/types/widgets/EodashMap/methods/use-map-loading.d.ts +1 -0
  81. package/dist/types/widgets/EodashProcess/index.vue.d.ts +5 -3
  82. package/dist/types/widgets/EodashProcess/states.d.ts +1 -1
  83. package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
  84. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +20 -0
  85. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +3 -8
  86. package/dist/types/widgets/EodashTimeSlider/types.d.ts +6 -16
  87. package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
  88. package/package.json +33 -33
  89. package/templates/baseConfig.js +36 -6
  90. package/templates/compare.js +62 -7
  91. package/templates/expert.js +23 -0
  92. package/templates/explore.js +11 -5
  93. package/templates/lite.js +14 -0
  94. package/widgets/EodashChart.vue +1 -0
  95. package/widgets/EodashItemCatalog/index.vue +363 -35
  96. package/widgets/EodashItemCatalog/methods/filters.js +134 -70
  97. package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
  98. package/widgets/EodashItemCatalog/methods/map.js +142 -28
  99. package/widgets/EodashLayerControl.vue +42 -11
  100. package/widgets/EodashMap/EodashMapBtns.vue +109 -48
  101. package/widgets/EodashMap/index.vue +89 -105
  102. package/widgets/EodashMap/methods/btns.js +37 -5
  103. package/widgets/EodashMap/methods/create-layers-config.js +16 -16
  104. package/widgets/EodashMap/methods/index.js +71 -8
  105. package/widgets/EodashMap/methods/use-map-loading.js +52 -0
  106. package/widgets/EodashProcess/index.vue +1 -0
  107. package/widgets/EodashProcess/states.js +1 -1
  108. package/widgets/EodashStacInfo.vue +64 -2
  109. package/widgets/EodashTimeSlider/index.vue +58 -2
  110. package/widgets/EodashTimeSlider/methods/index.js +5 -48
  111. package/widgets/EodashTimeSlider/types.ts +7 -17
  112. package/dist/client/EodashStacInfo-BQspAHKH.js +0 -107
  113. package/dist/client/index-5Jhsa8Xe.js +0 -570
@@ -80,6 +80,7 @@ import {
80
80
  compareIndicator,
81
81
  poi,
82
82
  isGlobe,
83
+ tooltipAdapter,
83
84
  } from "@/store/states";
84
85
  import { storeToRefs } from "pinia";
85
86
  import { useSTAcStore } from "@/store/stac";
@@ -93,6 +94,7 @@ import {
93
94
  import {
94
95
  useHandleMapMoveEnd,
95
96
  useInitMap,
97
+ useMapLoading,
96
98
  useUpdateTooltipProperties,
97
99
  } from "^/EodashMap/methods";
98
100
  import { inAndOut } from "ol/easing.js";
@@ -100,42 +102,73 @@ import mustache from "mustache";
100
102
  import EodashMapBtns from "^/EodashMap/EodashMapBtns.vue";
101
103
 
102
104
  const props = defineProps({
105
+ baseLayers: {
106
+ /** @type {import("vue").PropType<import("@eox/map").EoxLayer[]>} */
107
+ type: Array,
108
+ default: () => [
109
+ {
110
+ type: "Tile",
111
+ source: { type: "OSM" },
112
+ properties: {
113
+ id: "osm",
114
+ title: "Background",
115
+ layerControlExclusive: true,
116
+ },
117
+ },
118
+ ],
119
+ },
103
120
  enableCompare: {
104
121
  type: Boolean,
105
122
  default: false,
106
123
  },
107
- /** @type {import("vue").PropType<[number,number]>} */
124
+ /**
125
+ * Initial map center as [lon, lat]; falls back to the last stored map position.
126
+ * @default [15, 48]
127
+ */
108
128
  center: {
109
- //@ts-expect-error todo
110
- type: Array,
129
+ type: /** @type {import("vue").PropType<[number, number]>} */ (
130
+ /** @type {unknown} */ (Array)
131
+ ),
111
132
  default: () => [mapPosition.value?.[0] ?? 15, mapPosition.value?.[1] ?? 48],
112
133
  },
134
+ /**
135
+ * Initial zoom level; falls back to the last stored map position.
136
+ * @default 4
137
+ */
113
138
  zoom: {
114
139
  type: Number,
115
140
  default: mapPosition.value?.[2] ?? 4,
116
141
  },
142
+ /** Fly to the selected collection's spatial extent on selection. */
117
143
  zoomToExtent: {
118
144
  type: Boolean,
119
145
  default: true,
120
146
  },
147
+ /** Show live cursor coordinates as a fixed overlay at the bottom-left of the map. */
121
148
  enableCursorCoordinates: {
122
149
  type: Boolean,
123
150
  default: true,
124
151
  },
152
+ /** Show an OpenLayers scale-line control at the bottom-left of the map. */
125
153
  enableScaleLine: {
126
154
  type: Boolean,
127
155
  default: true,
128
156
  },
157
+ /** Grid position of the floating button toolbar. `x` accepts responsive `"mobile/tablet/desktop"` column notation. */
129
158
  btnsPosition: {
130
- type: Object,
159
+ type: /** @type {import("vue").PropType<{ x: string | number; y: number; gap: number }>} */ (
160
+ Object
161
+ ),
131
162
  default: () => ({
132
163
  x: "12/9/10",
133
164
  y: 1,
134
165
  gap: 16,
135
166
  }),
136
167
  },
168
+ /** Toolbar feature flags; set any flag to `false` to hide the corresponding button. */
137
169
  btns: {
138
- /** @type {import("vue").PropType<{
170
+ type: /**
171
+ * @type {import("vue").PropType<{
139
172
  * enableExportMap?: boolean;
140
173
  * enableChangeProjection?: boolean;
141
174
  * enableBackToPOIs?: boolean;
@@ -143,14 +176,14 @@ const props = defineProps({
143
176
  * searchParams?: object;
144
177
  * enableZoom?: boolean;
145
178
  * enableGlobe?: boolean;
179
+ * enableMosaic?: boolean;
146
180
  * enableFeedback?: boolean;
147
181
  * enableCompareIndicators?: boolean | {
148
182
  * compareTemplate?:string;
149
183
  * fallbackTemplate?:string;
150
184
  * itemFilterConfig?:InstanceType<import("../EodashItemFilter.vue").default>["$props"]
151
185
  * };
152
- * }> }*/
153
- type: Object,
186
+ * }> } */ (Object),
154
187
  default: () => ({
155
188
  enableExportMap: true,
156
189
  enableChangeProjection: true,
@@ -159,6 +192,7 @@ const props = defineProps({
159
192
  enableSearch: true,
160
193
  enableZoom: true,
161
194
  enableGlobe: true,
195
+ enableMosaic: true,
162
196
  enableFeedback: true,
163
197
  searchParams: {},
164
198
  }),
@@ -203,6 +237,7 @@ const btnsProps = computed(() => ({
203
237
  enableSearch: props.btns.enableSearch ?? true,
204
238
  enableZoom: props.btns.enableZoom ?? true,
205
239
  enableGlobe: props.btns.enableGlobe ?? true,
240
+ enableMosaic: props.btns.enableMosaic ?? true,
206
241
  enableFeedback: props.btns.enableFeedback ?? true,
207
242
  searchParams: props.btns.searchParams,
208
243
  }));
@@ -218,22 +253,13 @@ const cursorCoordsRef = useTemplateRef("cursor-coords");
218
253
  const tooltipProperties = ref([]);
219
254
  /** @type {import("vue").Ref<Exclude<import("@/types").EodashStyleJson["tooltip"], undefined>>} */
220
255
  const compareTooltipProperties = ref([]);
221
- /** @type {import("vue").ComputedRef<{
222
- Attribution: { collapsible: boolean };
223
- ScaleLine?: { target: HTMLElement };
224
- MousePosition?: { projection: string; coordinateFormat: (c: [number, number]) => string; target: HTMLElement };
225
- }>} */
256
+
226
257
  const controls = computed(() => {
227
- /** @type {{
228
- Attribution: { collapsible: boolean };
229
- ScaleLine?: { target: HTMLElement };
230
- MousePosition?: { projection: string; coordinateFormat: (c: [number, number]) => string; target: HTMLElement };
231
- }} */
232
- const controlsObj = {
258
+ const controlsObj = /** @type {import("@eox/map").ControlDictionary} */ ({
233
259
  Attribution: {
234
260
  collapsible: true,
235
261
  },
236
- };
262
+ });
237
263
 
238
264
  if (props.enableScaleLine && scaleLineRef.value) {
239
265
  controlsObj.ScaleLine = {
@@ -244,12 +270,8 @@ const controls = computed(() => {
244
270
  if (props.enableCursorCoordinates && cursorCoordsRef.value) {
245
271
  controlsObj.MousePosition = {
246
272
  projection: "EPSG:4326",
247
- coordinateFormat: (/** @type {[number, number]} */ c) => {
248
- const lat = c[1];
249
- const lng = c[0];
250
- const latStr = `${Math.abs(lat).toFixed(3)}°${lat >= 0 ? "N" : "S"}`;
251
- const lngStr = `${Math.abs(lng).toFixed(3)}°${lng >= 0 ? "E" : "W"}`;
252
- return `${latStr}, ${lngStr}`;
273
+ coordinateFormat: (c) => {
274
+ return `${c?.[1].toFixed(3)} °N, ${c?.[0].toFixed(3)} °E`;
253
275
  },
254
276
  target: cursorCoordsRef.value,
255
277
  };
@@ -261,28 +283,17 @@ const controls = computed(() => {
261
283
  const initialCenter = toRaw(props.center);
262
284
  const initialZoom = toRaw(mapPosition.value?.[2] ?? props.zoom);
263
285
  /** @type {import("vue").Ref<Record<string,any>[]>} */
264
- const eoxMapLayers = ref([
265
- {
266
- type: "Tile",
267
- source: { type: "OSM" },
268
- properties: {
269
- id: "osm",
270
- title: "Background",
271
- },
272
- },
273
- ]);
286
+ const eoxMapLayers = ref(
287
+ /** @type {Record<string,any>[]} */ (
288
+ structuredClone(toRaw(props.baseLayers))
289
+ ),
290
+ );
274
291
 
275
- /** @type {import("vue").Ref<Record<string,any>[]>} */
276
- const eoxMapCompareLayers = ref([
277
- {
278
- type: "Tile",
279
- source: { type: "OSM" },
280
- properties: {
281
- id: "osm",
282
- title: "Background",
283
- },
284
- },
285
- ]);
292
+ const eoxMapCompareLayers = ref(
293
+ /** @type {Record<string,any>[]} */ (
294
+ structuredClone(toRaw(props.baseLayers))
295
+ ),
296
+ );
286
297
 
287
298
  const animationOptions = ref({
288
299
  duration: 0, // Initially set to 0 for an instant "jump"
@@ -290,9 +301,10 @@ const animationOptions = ref({
290
301
  });
291
302
 
292
303
  /** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
293
- const eoxMap = ref(null);
304
+ const eoxMap = useTemplateRef("eoxMap");
294
305
  /** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
295
- const compareMap = ref(null);
306
+ const compareMap = useTemplateRef("compareMap");
307
+
296
308
  const { selectedCompareStac } = storeToRefs(useSTAcStore());
297
309
  const showCompare = computed(() =>
298
310
  props.enableCompare && !!selectedCompareStac.value ? "" : "first",
@@ -301,10 +313,20 @@ const showCompare = computed(() =>
301
313
  useHandleMapMoveEnd(eoxMap, mapPosition);
302
314
 
303
315
  onMounted(() => {
304
- const { selectedCompareStac, selectedStac, selectedItem } =
305
- storeToRefs(useSTAcStore());
316
+ const {
317
+ selectedCompareStac,
318
+ selectedStac,
319
+ selectedItem,
320
+ selectedCompareItem,
321
+ } = storeToRefs(useSTAcStore());
322
+ if (!eoxMap.value) {
323
+ console.error("EOxMap reference is not available on mounted.");
324
+ return;
325
+ }
306
326
  // assign map Element state to eox map
307
327
  mapEl.value = eoxMap.value;
328
+ // enable terrain
329
+ mapEl.value.globeConfig.terrain = true;
308
330
 
309
331
  if (props.enableCompare) {
310
332
  mapCompareEl.value = compareMap.value;
@@ -319,6 +341,8 @@ onMounted(() => {
319
341
  eoxMapCompareLayers,
320
342
  eoxMap,
321
343
  false,
344
+ selectedCompareItem,
345
+ props.baseLayers,
322
346
  );
323
347
 
324
348
  useUpdateTooltipProperties(
@@ -337,6 +361,7 @@ onMounted(() => {
337
361
  compareMap,
338
362
  props.zoomToExtent,
339
363
  selectedItem,
364
+ props.baseLayers,
340
365
  );
341
366
  // After the initial mount and "jump", set the animation duration for subsequent flyTo calls
342
367
  nextTick(() => {
@@ -344,14 +369,23 @@ onMounted(() => {
344
369
  });
345
370
  });
346
371
 
372
+ // sync map loading with the global loading state
373
+ useMapLoading(eoxMap, compareMap);
374
+
347
375
  useUpdateTooltipProperties(eodashCollections, tooltipProperties);
348
376
 
349
377
  const mainTooltipStyles = computed(() => ({
350
- visibility: tooltipProperties.value.length ? "visible" : "hidden",
378
+ visibility:
379
+ tooltipProperties.value.length || !!tooltipAdapter.value
380
+ ? "visible"
381
+ : "hidden",
351
382
  }));
352
383
 
353
384
  const compareTooltipStyles = computed(() => ({
354
- visibility: compareTooltipProperties.value.length ? "visible" : "hidden",
385
+ visibility:
386
+ compareTooltipProperties.value.length || !!tooltipAdapter.value
387
+ ? "visible"
388
+ : "hidden",
355
389
  }));
356
390
  /**
357
391
  * @param {"main" | "compare"} map
@@ -377,6 +411,9 @@ const tooltipPropertyTransform = (map) => {
377
411
  (prop) => prop.id === param.key,
378
412
  );
379
413
  if (!tooltipProp) {
414
+ if (tooltipAdapter.value) {
415
+ return tooltipAdapter.value(param, map);
416
+ }
380
417
  return undefined;
381
418
  }
382
419
  if (typeof param.value === "object") {
@@ -388,7 +425,6 @@ const tooltipPropertyTransform = (map) => {
388
425
  : 4;
389
426
  param.value = Number(param.value).toFixed(decimals).toString();
390
427
  }
391
-
392
428
  return {
393
429
  key: tooltipProp.title || tooltipProp.id,
394
430
  value: param.value + " " + (tooltipProp.appendix || ""),
@@ -405,58 +441,6 @@ onMounted(() => {
405
441
  </script>
406
442
 
407
443
  <style scoped>
408
- #cursor-coordinates {
409
- position: fixed;
410
- left: 24px;
411
- bottom: 54px; /* Tighter spacing: watermark at 6px + ~48px */
412
- color: rgba(0, 0, 0, 0.9);
413
- font-size: 10px;
414
- font-family: var(--eox-body-font-family);
415
- background: #fffe;
416
- border-radius: 4px;
417
- border: none;
418
- padding: 0px 3px;
419
- max-height: 24px;
420
- }
421
-
422
- @media (max-width: 959px) {
423
- #cursor-coordinates {
424
- display: none; /* Hidden in mobile mode */
425
- }
426
- }
427
-
428
- #scale-line {
429
- position: fixed;
430
- left: 24px;
431
- bottom: 28px; /* Tighter spacing: watermark at 6px + ~22px */
432
- color: #fff;
433
- }
434
-
435
- @media (max-width: 959px) {
436
- #scale-line {
437
- bottom: 102px; /* Adjusted for mobile bottom nav - closer to coordinates */
438
- }
439
- }
440
-
441
- :deep(.ol-scale-line) {
442
- background: #fffe !important;
443
- border-radius: 4px !important;
444
- border: none !important;
445
- padding: 0px 3px 3px 3px !important;
446
- font-size: 10px !important;
447
- font-family: var(--eox-body-font-family);
448
- max-height: 20px;
449
- }
450
- :deep(.ol-scale-line-inner) {
451
- display: flex;
452
- justify-content: center;
453
- border: 1px solid rgba(0, 0, 0, 0.5) !important;
454
- border-top: none !important;
455
- color: #333 !important;
456
- font-weight: 500 !important;
457
- transform: translateY(1px);
458
- }
459
-
460
444
  .map-buttons-container {
461
445
  position: fixed;
462
446
  left: 0;
@@ -11,7 +11,20 @@ export const switchGlobe = () => {
11
11
  return;
12
12
  }
13
13
  if (!isGlobe.value) {
14
- mapEl.value.layers = addCorsAnonym([...getLayers()]);
14
+ // tmp: we remove the vector layers from the globe until
15
+ // we have better support for them
16
+ const layers = addCorsAnonym([...getLayers()])
17
+ .filter((layer) => layer.type !== "Vector")
18
+ .map((layer) => {
19
+ if (layer.type === "Group") {
20
+ return {
21
+ ...layer,
22
+ layers: layer.layers.filter((l) => l.type !== "Vector"),
23
+ };
24
+ }
25
+ return layer;
26
+ });
27
+ mapEl.value.layers = layers;
15
28
  }
16
29
  mapEl.value.projection = isGlobe.value ? "EPSG:3857" : "globe";
17
30
  if (isGlobe.value) {
@@ -20,27 +33,46 @@ export const switchGlobe = () => {
20
33
  isGlobe.value = !isGlobe.value;
21
34
  };
22
35
 
36
+ function getRoot() {
37
+ const eodashComponent = document.querySelector("eo-dash");
38
+ return eodashComponent ? eodashComponent.shadowRoot : document;
39
+ }
40
+
23
41
  function hideAllPanels() {
24
- const allPanels = document.querySelectorAll(
42
+ const root = getRoot();
43
+ const allPanels = root?.querySelectorAll(
25
44
  "eox-layout-item:not([class='bg-panel'])",
26
45
  );
27
- allPanels.forEach((panel) => {
46
+
47
+ allPanels?.forEach((panel) => {
28
48
  if (!panel || !(panel instanceof HTMLElement)) {
29
49
  return;
30
50
  }
31
51
  panel.style.display = "none";
32
52
  });
53
+
54
+ const mapButtons = root?.querySelector(".map-buttons");
55
+ if (mapButtons instanceof HTMLElement) {
56
+ mapButtons.style.padding = "1rem";
57
+ }
33
58
  }
59
+
34
60
  function showAllPanels() {
35
- const allPanels = document.querySelectorAll(
61
+ const root = getRoot();
62
+ const allPanels = root?.querySelectorAll(
36
63
  "eox-layout-item:not([class='bg-panel'])",
37
64
  );
38
- allPanels.forEach((panel) => {
65
+ allPanels?.forEach((panel) => {
39
66
  if (!panel || !(panel instanceof HTMLElement)) {
40
67
  return;
41
68
  }
42
69
  panel.style.display = "";
43
70
  });
71
+
72
+ const mapButtons = root?.querySelector(".map-buttons");
73
+ if (mapButtons instanceof HTMLElement) {
74
+ mapButtons.style.padding = "";
75
+ }
44
76
  }
45
77
  /**
46
78
  *
@@ -10,6 +10,7 @@ import log from "loglevel";
10
10
  * } selectedIndicator
11
11
  * @param {EodashCollection[]} eodashCols
12
12
  * @param {string | import("stac-ts").StacItem | null} [timeOrItem] - time as a string, or a stac item
13
+ * @param {Record<string, any>[]} [defaultBaseLayers] - Optional default baselayers to use if the indicator does not provide any
13
14
  * @returns {Promise<Record<string, any>[]>}
14
15
  */
15
16
 
@@ -17,6 +18,7 @@ export const createLayersConfig = async (
17
18
  selectedIndicator,
18
19
  eodashCols,
19
20
  timeOrItem,
21
+ defaultBaseLayers = [],
20
22
  ) => {
21
23
  log.debug(
22
24
  "Creating layers config",
@@ -38,17 +40,25 @@ export const createLayersConfig = async (
38
40
  for (const ec of eodashCols) {
39
41
  /** @type {Record<string,any>[]} */
40
42
  let layers;
43
+ let dateOrItem;
41
44
  if (timeOrItem) {
42
- const dateOrItem =
43
- typeof timeOrItem === "string" ? new Date(timeOrItem) : timeOrItem;
44
- layers = await ec.createLayersJson(dateOrItem);
45
+ if (typeof timeOrItem === "string" && timeOrItem.includes("/")) {
46
+ dateOrItem = timeOrItem;
47
+ } else {
48
+ dateOrItem =
49
+ typeof timeOrItem === "string" ? new Date(timeOrItem) : timeOrItem;
50
+ }
51
+ layers = await ec.createLayersJson(/** @type {any} */ (dateOrItem));
45
52
  } else {
46
53
  layers = await ec.createLayersJson(undefined);
47
54
  }
55
+
48
56
  // Add expand to all analysis layers
49
57
  layers.forEach((dl) => {
50
- dl.properties.layerControlExpand = true;
51
- dl.properties.layerControlToolsExpand = true;
58
+ if (!dl.properties?.layerControlExclusive) {
59
+ dl.properties.layerControlExpand = true;
60
+ dl.properties.layerControlToolsExpand = true;
61
+ }
52
62
  });
53
63
  dataLayers.layers.push(...layers);
54
64
  }
@@ -141,17 +151,7 @@ export const createLayersConfig = async (
141
151
  });
142
152
  } else {
143
153
  // Default to some baselayer
144
- baseLayers.layers.push({
145
- type: "Tile",
146
- properties: {
147
- id: "osm",
148
- title: "Background",
149
- layerControlExclusive: true,
150
- },
151
- source: {
152
- type: "OSM",
153
- },
154
- });
154
+ baseLayers.layers.push(...defaultBaseLayers);
155
155
  }
156
156
 
157
157
  if (baseLayers.layers.length) {
@@ -6,9 +6,12 @@ import { useSTAcStore } from "@/store/stac";
6
6
  import { storeToRefs } from "pinia";
7
7
  import { isFirstLoad } from "@/utils/states";
8
8
  import { useEmitLayersUpdate, useOnLayersUpdate } from "@/composables";
9
- import { mapPosition } from "@/store/states";
9
+ export { useEmitLayersUpdate, useOnLayersUpdate };
10
+ import { isGlobe, mapPosition } from "@/store/states";
10
11
  import { sanitizeBbox } from "@/eodashSTAC/helpers";
11
12
  import { transformExtent } from "@eox/map";
13
+
14
+ export { useMapLoading } from "./use-map-loading";
12
15
  /**
13
16
  * Holder for previous compare map view as it is overwritten by sync
14
17
  * @type { import("ol").View | null} mapElement
@@ -39,14 +42,52 @@ export const useHandleMapMoveEnd = (mapElement, mapPosition) => {
39
42
  }
40
43
  };
41
44
 
45
+ const handleGlobeMoveEnd = () => {
46
+ const camera = mapElement.value?.globe?.planet?.camera;
47
+ const lonLat = camera?.getLonLat();
48
+ if (!lonLat) return;
49
+ const { lon, lat, height } = lonLat;
50
+ if (![lon, lat, height].some(Number.isNaN)) {
51
+ mapPosition.value = [lon, lat, height];
52
+ }
53
+ };
54
+
55
+ /** @type {{ events: { off: Function } } | null} */
56
+ let subscribedCamera = null;
57
+
58
+ const subscribeGlobe = (retries = 3) => {
59
+ if (!isGlobe.value) return; // toggled back out during retry
60
+ const camera = mapElement.value?.globe?.planet?.camera;
61
+ if (camera) {
62
+ if (subscribedCamera === camera) return;
63
+ camera.events.on("moveend", handleGlobeMoveEnd);
64
+ subscribedCamera = camera;
65
+ handleGlobeMoveEnd();
66
+ return;
67
+ }
68
+ if (retries > 0) setTimeout(() => subscribeGlobe(retries - 1), 50);
69
+ };
70
+ const unsubscribeGlobe = () => {
71
+ subscribedCamera?.events.off("moveend", handleGlobeMoveEnd);
72
+ subscribedCamera = null;
73
+ };
74
+
75
+ const stopGlobeWatch = watch(isGlobe, (globe) =>
76
+ globe ? subscribeGlobe() : unsubscribeGlobe(),
77
+ );
78
+
42
79
  onMounted(() => {
43
- /** @type {import('ol/Map').default} */
44
- (mapElement.value?.map)?.on("moveend", handleMoveEnd);
80
+ const map = mapElement.value?.map;
81
+ map?.on("moveend", handleMoveEnd);
82
+ // Seed mapPosition from the initial view
83
+ handleMoveEnd(/** @type {any} */ ({ map }));
45
84
  });
46
85
 
47
86
  onUnmounted(() => {
48
87
  /** @type {import('ol/Map').default} */
49
88
  (mapElement.value?.map)?.un("moveend", handleMoveEnd);
89
+ unsubscribeGlobe();
90
+ stopGlobeWatch();
50
91
  });
51
92
  };
52
93
 
@@ -61,6 +102,7 @@ export const useHandleMapMoveEnd = (mapElement, mapPosition) => {
61
102
  * @param {import("vue").Ref<import("@eox/map").EOxMap| null>} partnerMap
62
103
  * @param {boolean} zoomToExtent
63
104
  * @param {import("vue").Ref<import("stac-ts").StacItem | import("stac-ts").StacLink | null>} [selectedItem]
105
+ * @param {Record<string, any>[]} [defaultBaseLayers]
64
106
  */
65
107
  export const useInitMap = (
66
108
  mapElement,
@@ -71,6 +113,7 @@ export const useInitMap = (
71
113
  partnerMap,
72
114
  zoomToExtent,
73
115
  selectedItem,
116
+ defaultBaseLayers,
74
117
  ) => {
75
118
  log.debug(
76
119
  "InitMap",
@@ -100,12 +143,24 @@ export const useInitMap = (
100
143
  );
101
144
 
102
145
  if (updatedStac) {
103
- if (
146
+ const isSameStac = updatedStac?.id === previousStac?.id;
147
+ const isSameItem = updatedItem?.id === previousItem?.id;
148
+
149
+ // Item deselect: overview render is owned by the item catalog widget.
150
+ const isItemDeselect =
151
+ previousItem &&
152
+ !updatedItem &&
153
+ isSameStac &&
154
+ updatedTime === previousTime;
155
+ if (isItemDeselect) return;
156
+
157
+ // Re-fire from our own datetime.value write, skip and clear
158
+ const isOwnDatetimeUpdate =
104
159
  internalDatetime !== null &&
105
160
  updatedTime === internalDatetime &&
106
- updatedStac?.id === previousStac?.id &&
107
- updatedItem?.id === previousItem?.id
108
- ) {
161
+ isSameStac &&
162
+ isSameItem;
163
+ if (isOwnDatetimeUpdate) {
109
164
  internalDatetime = null;
110
165
  return;
111
166
  }
@@ -153,7 +208,9 @@ export const useInitMap = (
153
208
  updatedStac,
154
209
  eodashCols,
155
210
  updatedItem ?? updatedTime,
211
+ defaultBaseLayers,
156
212
  );
213
+
157
214
  log.debug(
158
215
  "Assigned layers after changing time only",
159
216
  JSON.parse(JSON.stringify(layersCollection)),
@@ -175,6 +232,11 @@ export const useInitMap = (
175
232
  if (interval && interval.length > 0 && interval[0].length > 1) {
176
233
  // @ts-expect-error this is the defined STAC structure
177
234
  endInterval = new Date(interval[0][1]);
235
+ // If end interval is in the future, set to now,
236
+ // for item fetching based on search endpoint
237
+ if (endInterval.getTime() > Date.now()) {
238
+ endInterval = new Date();
239
+ }
178
240
  log.debug(
179
241
  "Indicator load: found stac extent, setting time to latest value",
180
242
  endInterval,
@@ -200,7 +262,8 @@ export const useInitMap = (
200
262
  layersCollection = await createLayersConfig(
201
263
  updatedStac,
202
264
  eodashCols,
203
- resolvedTime,
265
+ updatedItem ?? resolvedTime,
266
+ defaultBaseLayers,
204
267
  );
205
268
 
206
269
  if (zoomToExtent) {
@@ -0,0 +1,52 @@
1
+ import { onUnmounted, watch } from "vue";
2
+ import { loading } from "@/store/states";
3
+
4
+ /**
5
+ * Tracks map loading state at the source level via `loading.activeLoads`,
6
+ * the same counter used by the axios plugin. Re-attaches source listeners
7
+ * whenever eox-map fires `layerschanged`, using `getFlatLayersArray`.
8
+ *
9
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
10
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} compareMapElement
11
+ */
12
+ export const useMapLoading = (mapElement, compareMapElement) => {
13
+ const startHandler = () => {
14
+ loading.activeLoads++;
15
+ };
16
+ const endHandler = () => {
17
+ loading.activeLoads--;
18
+ };
19
+
20
+ /** @param {import("@eox/map").EOxMap | null} newMapEl */
21
+ const watcherHandler = (newMapEl) => {
22
+ if (!newMapEl) return;
23
+
24
+ const hasStartListener = newMapEl.map
25
+ .getListeners("loadstart")
26
+ ?.some((listner) => listner === startHandler);
27
+ const hasEndListener = newMapEl.map
28
+ .getListeners("loadend")
29
+ ?.some((listner) => listner === endHandler);
30
+
31
+ if (!hasStartListener) {
32
+ newMapEl.map.addEventListener("loadstart", startHandler);
33
+ }
34
+ if (!hasEndListener) {
35
+ newMapEl.map.addEventListener("loadend", endHandler);
36
+ }
37
+ };
38
+ const stopMainWatcher = watch(mapElement, watcherHandler, {
39
+ immediate: true,
40
+ });
41
+ const stopCompareWatcher = watch(compareMapElement, watcherHandler, {
42
+ immediate: true,
43
+ });
44
+
45
+ onUnmounted(() => {
46
+ mapElement.value?.map.removeEventListener("loadstart", startHandler);
47
+ mapElement.value?.map.removeEventListener("loadend", endHandler);
48
+ stopMainWatcher();
49
+ stopCompareWatcher();
50
+ loading.activeLoads = 0;
51
+ });
52
+ };