@eodash/eodash 5.7.0 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) 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 +87 -24
  5. package/core/client/eodashSTAC/cql.js +64 -0
  6. package/core/client/eodashSTAC/createLayers.js +353 -187
  7. package/core/client/eodashSTAC/helpers.js +694 -110
  8. package/core/client/eodashSTAC/mosaic.js +422 -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 +54 -4
  12. package/core/client/store/states.js +20 -2
  13. package/core/client/types.ts +152 -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-DEs4f_Vy.js} +2 -2
  17. package/dist/client/{DynamicWebComponent-CnKhYo0K.js → DynamicWebComponent-PvnV88BA.js} +1 -1
  18. package/dist/client/EodashChart-D-3AGKZY.js +260 -0
  19. package/dist/client/{EodashDatePicker-CMPDT-ce.js → EodashDatePicker-Dm_goZzs.js} +3 -3
  20. package/dist/client/{EodashItemFilter-BpdA8QQa.js → EodashItemFilter-CJy3zYzD.js} +2 -1
  21. package/dist/client/{EodashLayerControl-BHpDA4_0.js → EodashLayerControl-rL8UnpkA.js} +55 -33
  22. package/dist/client/{EodashLayoutSwitcher-B1fRUt7G.js → EodashLayoutSwitcher-ZRlESI9O.js} +3 -3
  23. package/dist/client/{EodashMapBtns-Cpned0tc.js → EodashMapBtns-CG7u2Aq8.js} +227 -112
  24. package/dist/client/EodashStacInfo-CJhhxthD.js +169 -0
  25. package/dist/client/{EodashTools-1zHz26gv.js → EodashTools-CWyK8QJT.js} +4 -4
  26. package/dist/client/{ExportState-DbOm8hBI.js → ExportState-BXhy5TOU.js} +20 -174
  27. package/dist/client/{Footer-BKg-VVkj.js → Footer-DZPvnhcT.js} +1 -1
  28. package/dist/client/{Header-DCrwCvmY.js → Header-CiGsDE_a.js} +2 -2
  29. package/dist/client/{MobileLayout-vWSHpluo.js → MobileLayout-BJHW7x5V.js} +2 -2
  30. package/dist/client/{PopUp-9uimfK49.js → PopUp-7_XnhvCc.js} +4 -168
  31. package/dist/client/{ProcessList-CTTIZa06.js → ProcessList-LL_UCbvc.js} +5 -5
  32. package/dist/client/VDivider-DWYM0WUr.js +168 -0
  33. package/dist/client/{VImg-CrmB76ZY.js → VImg-B75dgw2K.js} +2 -2
  34. package/dist/client/{VMain-OKZNpcEG.js → VMain-DvMmOL4f.js} +1 -1
  35. package/dist/client/{VTooltip-CJw2FyWk.js → VTooltip-ClWHUmWL.js} +2 -2
  36. package/dist/client/{WidgetsContainer-BaWOnE5R.js → WidgetsContainer-BqaWs4ch.js} +1 -1
  37. package/dist/client/{asWebComponent-BHWpMuE3.js → asWebComponent-IZanwL-A.js} +677 -379
  38. package/dist/client/{async-DSKj6g1f.js → async-CiNIOBVz.js} +31 -29
  39. package/dist/client/{create-layers-config-CBoYedqB.js → create-layers-config-3S3y_kZg.js} +17 -17
  40. package/dist/client/dialog-transition-c4ZSnzac.js +170 -0
  41. package/dist/client/eo-dash.js +1 -1
  42. package/dist/client/{forwardRefs-DH51zPUr.js → forwardRefs-B-c1_6yo.js} +8 -87
  43. package/dist/client/{handling-BrcDDlgS.js → handling-NvY8Csry.js} +3 -4
  44. package/dist/client/{helpers-DjZVl_WF.js → helpers-BFuh19CP.js} +1094 -194
  45. package/dist/client/{index-1JKE68n3.js → index-1wMAyuNL.js} +219 -67
  46. package/dist/client/{index-CSG_JoOA.js → index-B8jGJSTA.js} +62 -56
  47. package/dist/client/{index-Cu3-13Xg.js → index-BKQQJkhy.js} +167 -61
  48. package/dist/client/index-D0jUyol8.js +3167 -0
  49. package/dist/client/{index-CUjl-ZsI.js → index-KGxz5Egv.js} +2 -2
  50. package/dist/client/mosaic-vHvSuih6.js +459 -0
  51. package/dist/client/templates.js +251 -121
  52. package/dist/client/{transition-OHcz5Qq1.js → transition-BkyUsZh_.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 +118 -19
  60. package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
  61. package/dist/types/core/client/store/stac.d.ts +38 -8
  62. package/dist/types/core/client/store/states.d.ts +24 -2
  63. package/dist/types/core/client/types.d.ts +128 -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 +10 -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/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
  83. package/dist/types/widgets/EodashProcess/states.d.ts +1 -1
  84. package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
  85. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +20 -0
  86. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +3 -8
  87. package/dist/types/widgets/EodashTimeSlider/types.d.ts +6 -16
  88. package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
  89. package/package.json +34 -34
  90. package/templates/baseConfig.js +37 -6
  91. package/templates/compare.js +61 -7
  92. package/templates/expert.js +24 -2
  93. package/templates/explore.js +12 -5
  94. package/templates/lite.js +13 -0
  95. package/widgets/EodashChart.vue +162 -21
  96. package/widgets/EodashItemCatalog/index.vue +363 -35
  97. package/widgets/EodashItemCatalog/methods/filters.js +134 -70
  98. package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
  99. package/widgets/EodashItemCatalog/methods/map.js +141 -28
  100. package/widgets/EodashLayerControl.vue +51 -25
  101. package/widgets/EodashMap/EodashMapBtns.vue +109 -48
  102. package/widgets/EodashMap/index.vue +91 -105
  103. package/widgets/EodashMap/methods/btns.js +64 -31
  104. package/widgets/EodashMap/methods/create-layers-config.js +16 -16
  105. package/widgets/EodashMap/methods/index.js +71 -8
  106. package/widgets/EodashMap/methods/use-map-loading.js +58 -0
  107. package/widgets/EodashProcess/index.vue +136 -16
  108. package/widgets/EodashProcess/methods/composables.js +13 -0
  109. package/widgets/EodashProcess/methods/outputs.js +0 -1
  110. package/widgets/EodashProcess/methods/utils.js +29 -27
  111. package/widgets/EodashProcess/states.js +1 -1
  112. package/widgets/EodashStacInfo.vue +64 -2
  113. package/widgets/EodashTimeSlider/index.vue +58 -2
  114. package/widgets/EodashTimeSlider/methods/index.js +5 -48
  115. package/widgets/EodashTimeSlider/types.ts +7 -17
  116. package/widgets/ExportState.vue +13 -4
  117. package/dist/client/EodashChart-CLDqE93C.js +0 -144
  118. package/dist/client/EodashStacInfo-BQspAHKH.js +0 -107
  119. package/dist/client/index-5Jhsa8Xe.js +0 -570
@@ -1,17 +1,29 @@
1
1
  import { useOnLayersUpdate } from "@/composables";
2
- import { mapEl } from "@/store/states";
3
2
  import { onMounted, onUnmounted } from "vue";
3
+ import { createOnSelectHandler } from "./handlers";
4
+ import { tooltipAdapter } from "@/store/states";
4
5
 
5
6
  /**
6
7
  *
7
8
  * @param {import("@/types").GeoJsonFeature[]} features
9
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
10
+ * @param {string[] | undefined} hoverProperties
11
+ * @param {object} [stacItemsStyle]
12
+ * @param {object} [stacItemsInteractionStyle]
8
13
  */
9
- export function renderItemsFeatures(features) {
14
+ export function renderItemsFeatures(
15
+ features,
16
+ mapElement,
17
+ hoverProperties,
18
+ stacItemsStyle,
19
+ stacItemsInteractionStyle,
20
+ ) {
21
+ const currentMap = mapElement.value;
10
22
  let analysisLayers =
11
23
  /** @type {import("@eox/map/src/layers").EOxLayerTypeGroup} */ (
12
- mapEl.value?.layers?.find((l) => l.properties?.id === "AnalysisGroup")
24
+ currentMap?.layers?.find((l) => l.properties?.id === "AnalysisGroup")
13
25
  );
14
- if (!mapEl.value || !features) {
26
+ if (!currentMap || !features) {
15
27
  return;
16
28
  }
17
29
  if (!analysisLayers) {
@@ -23,9 +35,9 @@ export function renderItemsFeatures(features) {
23
35
  },
24
36
  layers: [],
25
37
  };
26
- mapEl.value.layers = [...mapEl.value.layers, analysisLayers];
38
+ currentMap.layers = [...currentMap.layers, analysisLayers];
27
39
  }
28
-
40
+ /** @type {import("@eox/map/src/layers").EOxLayerType<"Vector","Vector">} */
29
41
  const stacItemsLayer = {
30
42
  type: "Vector",
31
43
  properties: {
@@ -40,8 +52,9 @@ export function renderItemsFeatures(features) {
40
52
  JSON.stringify({ type: "FeatureCollection", features }),
41
53
  ),
42
54
  format: "GeoJSON",
55
+ projection: "EPSG:4326",
43
56
  },
44
- style: {
57
+ style: stacItemsStyle || {
45
58
  "fill-color": "transparent",
46
59
  "stroke-color": "#003170",
47
60
  },
@@ -49,35 +62,47 @@ export function renderItemsFeatures(features) {
49
62
  {
50
63
  type: "select",
51
64
  options: {
52
- id: "stac-items",
65
+ id: "stac-item-hover",
53
66
  condition: "pointermove",
54
- style: {
67
+ tooltip: hoverProperties?.length,
68
+ style: stacItemsInteractionStyle || {
55
69
  "stroke-color": "white",
56
70
  "stroke-width": 3,
57
71
  },
58
72
  },
59
73
  },
74
+ {
75
+ type: "select",
76
+ options: {
77
+ id: "stac-items",
78
+ condition: "click",
79
+ tooltip: false,
80
+ style: stacItemsInteractionStyle || {
81
+ "stroke-color": "#003047",
82
+ "stroke-width": 3.2,
83
+ },
84
+ },
85
+ },
60
86
  ],
61
87
  };
62
88
  const exists = analysisLayers.layers.some(
63
- (l) => l.properties?.id === "stac-items",
89
+ (l) => l.properties?.id === stacItemsLayer.properties?.id,
64
90
  );
65
91
  if (exists) {
66
- //@ts-expect-error todo
67
- mapEl.value.addOrUpdateLayer(stacItemsLayer);
92
+ currentMap.addOrUpdateLayer(stacItemsLayer);
68
93
  return;
69
94
  } else {
70
- //@ts-expect-error todo
71
- analysisLayers.layers.push(stacItemsLayer);
72
- mapEl.value.layers = [...mapEl.value.layers];
95
+ analysisLayers.layers.unshift(stacItemsLayer);
96
+ currentMap.layers = [...currentMap.layers];
73
97
  }
74
98
  }
75
99
 
76
100
  /**
77
101
  * @param {import("vue").Ref<any>} itemFilter
78
102
  * @param {boolean} bboxFilter
103
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
79
104
  */
80
- export const useSearchOnMapMove = (itemFilter, bboxFilter) => {
105
+ export const useSearchOnMapMove = (itemFilter, bboxFilter, mapElement) => {
81
106
  if (!bboxFilter) {
82
107
  return;
83
108
  }
@@ -86,58 +111,146 @@ export const useSearchOnMapMove = (itemFilter, bboxFilter) => {
86
111
  const handler = () => {
87
112
  clearTimeout(timeout);
88
113
  timeout = setTimeout(() => {
114
+ if (mapElement.value?.projection == "globe") {
115
+ return;
116
+ }
89
117
  itemFilter.value?.search();
90
- }, 800); // 800ms debounce
118
+ }, 500);
91
119
  };
92
120
  onMounted(() => {
93
- mapEl.value?.map.on("moveend", handler);
121
+ mapElement.value?.map.on("moveend", handler);
94
122
  });
95
123
  onUnmounted(() => {
96
- mapEl.value?.map.un("moveend", handler);
124
+ mapElement.value?.map.un("moveend", handler);
97
125
  });
98
126
  };
99
127
  /**
100
128
  *
101
129
  * @param {import("vue").Ref<import("@/types").GeoJsonFeature[]>} currentItems
130
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
131
+ * @param {string[] | undefined} hoverProperties
132
+ * @param {object} [stacItemsStyle]
133
+ * @param {object} [stacItemsInteractionStyle]
102
134
  */
103
- export const useRenderItemsFeatures = (currentItems) => {
135
+ export const useRenderItemsFeatures = (
136
+ currentItems,
137
+ mapElement,
138
+ hoverProperties,
139
+ stacItemsStyle,
140
+ stacItemsInteractionStyle,
141
+ ) => {
104
142
  onMounted(() => {
105
- renderItemsFeatures(currentItems.value);
143
+ renderItemsFeatures(
144
+ currentItems.value,
145
+ mapElement,
146
+ hoverProperties,
147
+ stacItemsStyle,
148
+ stacItemsInteractionStyle,
149
+ );
106
150
  });
107
151
 
108
152
  useOnLayersUpdate(() => {
109
153
  // consider cases where this is not needed
110
- renderItemsFeatures(currentItems.value);
154
+ renderItemsFeatures(
155
+ currentItems.value,
156
+ mapElement,
157
+ hoverProperties,
158
+ stacItemsStyle,
159
+ stacItemsInteractionStyle,
160
+ );
111
161
  });
112
162
  };
163
+ /**
164
+ * Registers a tooltip adapter that displays item properties when hovering over map features.
165
+ * @param {string[]} [hoverProperties]
166
+ */
167
+ export function useHoverTooltip(hoverProperties) {
168
+ /**
169
+ * @param {*} val
170
+ */
171
+ const formatValue = (val) => {
172
+ if (typeof val === "number") {
173
+ return val.toFixed(0);
174
+ }
175
+
176
+ if (typeof val === "string") {
177
+ // Attempt to parse as date if string looks like ISO date
178
+ if (val.length > 9 && /\d{4}-\d{2}-\d{2}/.test(val)) {
179
+ const timestamp = Date.parse(val);
180
+ if (!isNaN(timestamp)) {
181
+ return new Date(timestamp).toUTCString();
182
+ }
183
+ }
184
+ }
185
+ return val;
186
+ };
187
+ /**
188
+ * @param {string} key
189
+ */
190
+ const formatKey = (key) => {
191
+ let formattedKey = key;
192
+ if (key.includes(":")) {
193
+ formattedKey = key.split(":")[1];
194
+ }
195
+ return formattedKey
196
+ .replace(/_/g, " ")
197
+ .replace(/\b\w/g, (c) => c.toUpperCase());
198
+ };
199
+ onMounted(() => {
200
+ tooltipAdapter.value = ({ key, value }) => {
201
+ if (hoverProperties && hoverProperties.includes(key)) {
202
+ return { key: formatKey(key), value: formatValue(value) };
203
+ }
204
+ return undefined;
205
+ };
206
+ });
207
+ onUnmounted(() => {
208
+ tooltipAdapter.value = null;
209
+ });
210
+ }
211
+
113
212
  /**
114
213
  *
115
214
  * @param {import("vue").Ref<any>} itemfilterEl
215
+ * @param {ReturnType< typeof import("@/store/stac").useSTAcStore>} store
216
+ * @param {import("vue").Ref<import("@eox/map").EOxMap | null>} mapElement
217
+ * @param {boolean} enableCompare
116
218
  */
117
- export function useHighlightOnFeatureHover(itemfilterEl) {
219
+ export function useRenderOnFeatureClick(
220
+ itemfilterEl,
221
+ store,
222
+ mapElement,
223
+ enableCompare,
224
+ ) {
225
+ const onSelectItem = createOnSelectHandler(store, enableCompare, mapElement);
118
226
  /**
119
227
  *
120
228
  * @param {CustomEvent} evt
121
229
  */
122
230
  const handler = (evt) => {
231
+ if (evt.detail.originalEvent.type !== "click") {
232
+ return;
233
+ }
123
234
  const itemId = evt.detail?.feature?.getId();
124
235
  if (!itemId) {
125
236
  return;
126
237
  }
127
- const item = itemfilterEl.value.items?.find(
238
+ const item = itemfilterEl.value.results?.find(
128
239
  //@ts-expect-error todo
129
- (r) => r.id === itemId,
240
+ (r) => r?.id === itemId,
130
241
  );
242
+
131
243
  if (item) {
132
- itemfilterEl.value.selectedResult = item;
244
+ onSelectItem(new CustomEvent("select", { detail: item }));
133
245
  }
134
246
  };
247
+
135
248
  onMounted(() => {
136
249
  //@ts-expect-error todo
137
- mapEl.value?.addEventListener("select", handler);
250
+ mapElement.value?.addEventListener("select", handler);
138
251
  });
139
252
  onUnmounted(() => {
140
253
  //@ts-expect-error todo
141
- mapEl.value?.removeEventListener("select", handler);
254
+ mapElement.value?.removeEventListener("select", handler);
142
255
  });
143
256
  }
@@ -5,37 +5,49 @@
5
5
  :key="mapElement"
6
6
  v-bind="config"
7
7
  :for="mapElement"
8
+ .colormapRegistry="colormapRegistry"
9
+ .showLayerZoomState="true"
8
10
  .customEditorInterfaces="bandsEditorInterface"
9
11
  @datetime:updated="debouncedHandleDateTime"
10
12
  toolsAsList="true"
11
13
  ref="eoxLayercontrol"
12
14
  @layerConfig:change="onLayerConfigChange"
13
15
  >
14
- <slot name="layerstitle">
15
- <div>
16
- <p v-if="title" class="mt-2 mb-2">
17
- <strong>{{ title }}</strong>
18
- </p>
19
- </div>
20
- </slot>
16
+ <span
17
+ slot="layerstitle"
18
+ class="d-flex justify-space-between ma-2 pa-2 flex-shrink-0"
19
+ >
20
+ <h4 v-if="title">{{ title }}</h4>
21
+ <EodashLayoutSwitcher
22
+ v-if="enableLayoutSwitcher"
23
+ :target="layoutTarget"
24
+ :icon="layoutIcon"
25
+ />
26
+ </span>
21
27
  </eox-layercontrol>
22
28
  </span>
23
29
  </template>
24
30
  <script setup>
25
31
  import "color-legend-element";
26
32
  import "@eox/timecontrol";
27
- import { computed, ref, watch } from "vue";
33
+ import { computed, ref } from "vue";
28
34
  import { mapEl, mapCompareEl } from "@/store/states";
29
- import { getColFromLayer } from "@/eodashSTAC/helpers";
30
35
  import {
31
36
  eodashCollections,
32
37
  eodashCompareCollections,
33
38
  layerControlFormValue,
34
39
  layerControlFormValueCompare,
35
40
  } from "@/utils/states";
41
+ import {
42
+ getColFromLayer,
43
+ updateGeoZarrBands,
44
+ updateLayerUrl,
45
+ } from "@/eodashSTAC/helpers";
36
46
  import { storeToRefs } from "pinia";
37
47
  import { useSTAcStore } from "@/store/stac";
38
48
  import { bandsEditorInterface } from "@/utils/bands-editor";
49
+ import EodashLayoutSwitcher from "^/EodashLayoutSwitcher.vue";
50
+ import { mdiViewDashboard } from "@mdi/js";
39
51
  import { useEmitLayersUpdate } from "@/composables";
40
52
 
41
53
  if (!customElements.get("eox-layercontrol")) {
@@ -46,22 +58,39 @@ if (!customElements.get("eox-jsonform")) {
46
58
  }
47
59
 
48
60
  const props = defineProps({
61
+ /** Which map instance this control is bound to. Use `"second"` in a compare-mode layout. */
49
62
  map: {
50
63
  /** @type {import("vue").PropType<"first" | "second">} */
51
64
  //@ts-expect-error todo
52
65
  type: String,
53
66
  default: "first",
54
67
  },
68
+ /** Tool tabs shown inside `eox-layercontrol`. Remove entries to hide individual tabs. */
55
69
  tools: {
56
- type: Array,
70
+ type: /** @type {import("vue").PropType<string[]>} */ (Array),
57
71
  default: () => ["datetime", "info", "config", "legend", "opacity"],
58
72
  },
73
+ /** Heading rendered above the layer list. Set to `false` to hide it. */
59
74
  title: {
60
- type: String || Boolean,
75
+ type: /** @type {import("vue").PropType<string | false>} */ ([
76
+ String,
77
+ Boolean,
78
+ ]),
61
79
  default: "Layers",
62
80
  },
81
+ /** CSS custom-property overrides forwarded to the underlying `eox-layercontrol` element via its `style` attribute. */
63
82
  cssVars: {
64
- type: Object,
83
+ type: /** @type {import("vue").PropType<Record<string, string>>} */ (
84
+ Object
85
+ ),
86
+ default: {},
87
+ },
88
+ layoutIcon: {
89
+ type: String,
90
+ default: mdiViewDashboard,
91
+ },
92
+ layoutTarget: {
93
+ type: String,
65
94
  },
66
95
  });
67
96
 
@@ -70,7 +99,12 @@ const config = {
70
99
  style: props.cssVars,
71
100
  };
72
101
 
73
- const { selectedCompareStac, selectedStac } = storeToRefs(useSTAcStore());
102
+ const enableLayoutSwitcher = computed(
103
+ () => !!props.layoutTarget && !!props.layoutIcon,
104
+ );
105
+
106
+ const { selectedCompareStac, selectedStac, colormapRegistry } =
107
+ storeToRefs(useSTAcStore());
74
108
 
75
109
  const showControls = computed(() => {
76
110
  if (props.map === "second") {
@@ -83,23 +117,12 @@ const eodashCols =
83
117
  props.map === "second" ? eodashCompareCollections : eodashCollections;
84
118
  const mapElement = props.map === "second" ? mapCompareEl : mapEl;
85
119
 
86
- /** @type { import("vue").Ref<HTMLElement & Record<string,any> | null>} */
120
+ /** @type { import("vue").Ref<import("@eox/layercontrol").EOxLayerControl | null>} */
87
121
  const eoxLayercontrol = ref(null);
88
122
 
89
- // eox-timecontrol re-fires datetime:updated after layer reassignment;
90
- // dedupe by (collectionId, datetime)
91
- const processedDatetimes = new Map();
92
- watch([selectedStac, selectedCompareStac], () => processedDatetimes.clear());
93
-
94
123
  /** @param {CustomEvent<{layer:import('ol/layer').Layer; datetime:string;}>} evt */
95
124
  const handleDatetimeUpdate = async (evt) => {
96
125
  const { layer, datetime } = evt.detail;
97
- const collectionId = layer.get("id")?.split(";:;")[0] ?? layer.get("id");
98
- if (processedDatetimes.get(collectionId) === datetime) return;
99
- // First event per collection is eox-timecontrol's mount echo.
100
- const isFirstEvent = !processedDatetimes.has(collectionId);
101
- processedDatetimes.set(collectionId, datetime);
102
- if (isFirstEvent) return;
103
126
 
104
127
  const ec = await getColFromLayer(eodashCols, layer);
105
128
 
@@ -155,6 +178,9 @@ const debouncedHandleDateTime = (evt) => {
155
178
  * @param {Event & {detail:{layer:import("ol/layer").Layer;jsonformValue:Record<string,any>}}} evt
156
179
  */
157
180
  const onLayerConfigChange = (evt) => {
181
+ updateGeoZarrBands(evt.detail.layer, evt.detail.jsonformValue);
182
+ updateLayerUrl(evt.detail.layer, evt.detail.jsonformValue);
183
+
158
184
  if (props.map === "second") {
159
185
  layerControlFormValueCompare.value = evt.detail.jsonformValue;
160
186
  } else {
@@ -5,30 +5,28 @@
5
5
  class="primary small circle small-elevate"
6
6
  @click="onMapZoomIn"
7
7
  >
8
- <i class="small"
9
- ><svg viewBox="0 0 24 24"><path :d="mdiPlus" /></svg>
8
+ <i class="small">
9
+ <svg viewBox="0 0 24 24"><path :d="mdiPlus"></path></svg>
10
10
  </i>
11
11
  <div class="tooltip left">Zoom in</div>
12
12
  </button>
13
-
14
13
  <button
15
14
  v-if="enableZoom && !isGlobe"
16
15
  class="primary small circle small-elevate"
17
16
  @click="onMapZoomOut"
18
17
  >
19
- <i class="small"
20
- ><svg viewBox="0 0 24 24"><path :d="mdiMinus" /></svg>
18
+ <i class="small">
19
+ <svg viewBox="0 0 24 24"><path :d="mdiMinus"></path></svg>
21
20
  </i>
22
21
  <div class="tooltip left">Zoom out</div>
23
22
  </button>
24
-
25
23
  <button
26
24
  v-if="exportMap"
27
25
  class="primary small circle small-elevate"
28
26
  @click="showMapState = !showMapState"
29
27
  >
30
- <i class="small"
31
- ><svg viewBox="0 0 24 24"><path :d="mdiMapPlus" /></svg>
28
+ <i class="small">
29
+ <svg viewBox="0 0 24 24"><path :d="mdiMapPlus"></path></svg>
32
30
  </i>
33
31
  <div class="tooltip left">Extract storytelling configuration</div>
34
32
  </button>
@@ -39,8 +37,8 @@
39
37
  class="primary small circle small-elevate"
40
38
  @click="changeMapProjection(availableMapProjection)"
41
39
  >
42
- <i class="small"
43
- ><svg viewBox="0 0 24 24"><path :d="mdiEarthBox" /></svg>
40
+ <i class="small">
41
+ <svg viewBox="0 0 24 24"><path :d="mdiEarthBox"></path></svg>
44
42
  </i>
45
43
  <div class="tooltip left">Change map projection</div>
46
44
  </button>
@@ -49,23 +47,11 @@
49
47
  class="primary small circle small-elevate"
50
48
  @click="onCompareClick(compareIndicators)"
51
49
  >
52
- <i class="small"
53
- ><svg viewBox="0 0 24 24"><path :d="compareIcon" /></svg>
50
+ <i class="small">
51
+ <svg viewBox="0 0 24 24"><path :d="compareIcon"></path></svg>
54
52
  </i>
55
53
  <div class="tooltip left">Compare mode</div>
56
54
  </button>
57
- <button
58
- v-if="backToPOIs && (poi || comparePoi)"
59
- class="primary small circle small-elevate"
60
- @click="loadPOiIndicator()"
61
- >
62
- <i class="small"
63
- ><svg viewBox="0 0 24 24">
64
- <path :d="mdiStarFourPointsCircleOutline" />
65
- </svg>
66
- </i>
67
- <div class="tooltip left">Back to POIs</div>
68
- </button>
69
55
  <button
70
56
  v-if="enableGlobe && !isInCompareMode"
71
57
  class="primary small circle small-elevate"
@@ -102,20 +88,36 @@
102
88
  @close="showFeedback = false"
103
89
  ></eox-feedback>
104
90
  </div>
105
- <eox-geosearch
91
+ <button
106
92
  v-if="mapEl && !isGlobe && enableSearch"
107
- :for="mapEl"
108
- :endpoint="opencageUrl"
109
- :params="searchParams"
110
- class="geosearch-detached"
111
- label="Search"
112
- small
113
- button
114
- list-direction="left"
115
- results-direction="down"
116
- tooltip="Search"
117
- tooltip-direction="left"
118
- ></eox-geosearch>
93
+ class="primary small circle small-elevate"
94
+ >
95
+ <eox-geosearch
96
+ :for="mapEl"
97
+ :endpoint="opencageUrl"
98
+ :params="searchParams"
99
+ class="geosearch-detached"
100
+ label="Search"
101
+ small
102
+ button
103
+ list-direction="left"
104
+ results-direction="down"
105
+ tooltip="Search"
106
+ tooltip-direction="left"
107
+ ></eox-geosearch>
108
+ </button>
109
+ <button
110
+ v-if="backToPOIs && (poi || comparePoi)"
111
+ class="primary small circle small-elevate"
112
+ @click="loadPOiIndicator()"
113
+ >
114
+ <i class="small">
115
+ <svg viewBox="0 0 24 24">
116
+ <path :d="mdiStarFourPointsCircleOutline"></path>
117
+ </svg>
118
+ </i>
119
+ <div class="tooltip left">Back to POIs</div>
120
+ </button>
119
121
  <PopUp
120
122
  v-model="showCompareIndicators"
121
123
  :maxWidth="popupWidth"
@@ -126,9 +128,29 @@
126
128
  <EodashItemFilter
127
129
  v-bind="itemFilterConfig"
128
130
  :enableCompare="true"
129
- @select="onSelectCompareIndicator"
131
+ @select="onSelectCompareIndicator(compareIndicators)"
130
132
  />
131
133
  </PopUp>
134
+ <v-alert
135
+ v-if="showMosaicHint"
136
+ class="mosaic-hint pa-2"
137
+ color="secondary"
138
+ type="info"
139
+ variant="elevated"
140
+ density="compact"
141
+ elevation="4"
142
+ >
143
+ <template v-if="showItemViewHint">
144
+ Viewing individual item —
145
+ <a class="mosaic-hint-link" @click.prevent="returnToOverview.emit()">
146
+ Back to overview
147
+ </a>
148
+ </template>
149
+ <template v-else-if="showZoomHint">Zoom in to explore the data</template>
150
+ <template v-else-if="showNoDataHint">
151
+ No data here - pan or change filters
152
+ </template>
153
+ </v-alert>
132
154
  </div>
133
155
  </template>
134
156
  <script setup>
@@ -140,8 +162,10 @@ import {
140
162
  comparePoi,
141
163
  isGlobe,
142
164
  mapEl,
165
+ mapPosition,
143
166
  poi,
144
167
  } from "@/store/states";
168
+ import { useMosaicState, normalizeGlobeZoom } from "@/eodashSTAC/mosaic";
145
169
  import {
146
170
  mdiCompare,
147
171
  mdiCompareRemove,
@@ -195,11 +219,13 @@ const {
195
219
  default: true,
196
220
  },
197
221
  compareIndicators: {
198
- /** @type {import("vue").PropType<boolean | {
199
- compareTemplate?:string;
200
- fallbackTemplate?:string;
201
- itemFilterConfig?:Partial<InstanceType<import("./EodashItemFilter.vue").default>["$props"]>
202
- }> }*/
222
+ /**
223
+ * @type {import("vue").PropType<boolean | {
224
+ * compareTemplate?:string;
225
+ * fallbackTemplate?:string;
226
+ * itemFilterConfig?:Partial<InstanceType<import("./EodashItemFilter.vue").default>["$props"]>
227
+ * }> }
228
+ */
203
229
  type: [Boolean, Object],
204
230
  default: true,
205
231
  },
@@ -267,6 +293,32 @@ useTransparentPanel(rootRef);
267
293
 
268
294
  const opencageApiKey = process.env.EODASH_OPENCAGE || "NO_KEY_FOUND";
269
295
  const opencageUrl = `https://api.opencagedata.com/geocode/v1/json?key=${opencageApiKey}`;
296
+
297
+ const {
298
+ latestLayer,
299
+ isItemView,
300
+ visibilityThreshold,
301
+ returnToOverview,
302
+ hasDataInView,
303
+ } = useMosaicState();
304
+
305
+ const showItemViewHint = computed(
306
+ () => isItemView.value && !!latestLayer.value,
307
+ );
308
+
309
+ const showZoomHint = computed(() => {
310
+ if (!latestLayer.value || isItemView.value) return false;
311
+ const rawZ = mapPosition.value?.[2] ?? 4;
312
+ return normalizeGlobeZoom(rawZ) < visibilityThreshold.value;
313
+ });
314
+
315
+ const showNoDataHint = computed(
316
+ () => !!latestLayer.value && !isItemView.value && !hasDataInView.value,
317
+ );
318
+
319
+ const showMosaicHint = computed(
320
+ () => showItemViewHint.value || showZoomHint.value || showNoDataHint.value,
321
+ );
270
322
  </script>
271
323
 
272
324
  <style scoped>
@@ -296,11 +348,20 @@ const opencageUrl = `https://api.opencagedata.com/geocode/v1/json?key=${opencage
296
348
  pointer-events: auto !important;
297
349
  }
298
350
 
299
- /* Container constraints removal */
300
- eox-geosearch {
301
- position: relative !important;
302
- width: 40px;
303
- overflow: visible !important;
351
+ .mosaic-hint {
352
+ position: fixed;
353
+ left: 50%;
354
+ transform: translateX(-50%);
304
355
  z-index: 10;
356
+ opacity: 0.8;
357
+ border-radius: 8px;
358
+ pointer-events: auto;
359
+ }
360
+
361
+ .mosaic-hint-link {
362
+ color: inherit;
363
+ cursor: pointer;
364
+ text-decoration: underline;
365
+ pointer-events: auto;
305
366
  }
306
367
  </style>