@eodash/eodash 5.1.0 → 5.3.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 (133) hide show
  1. package/core/client/components/DashboardLayout.vue +1 -2
  2. package/core/client/components/EodashOverlay.vue +4 -5
  3. package/core/client/components/MobileLayout.vue +42 -21
  4. package/core/client/composables/index.js +54 -60
  5. package/core/client/eodashSTAC/EodashCollection.js +199 -108
  6. package/core/client/eodashSTAC/auth.js +86 -0
  7. package/core/client/eodashSTAC/createLayers.js +234 -4
  8. package/core/client/eodashSTAC/helpers.js +281 -59
  9. package/core/client/eodashSTAC/parquet.js +0 -13
  10. package/core/client/eodashSTAC/triggers.js +1 -1
  11. package/core/client/store/actions.js +14 -0
  12. package/core/client/store/stac.js +46 -8
  13. package/core/client/store/states.js +6 -0
  14. package/core/client/types.ts +206 -3
  15. package/core/client/utils/bands-editor/arithmetic.js +144 -0
  16. package/core/client/utils/bands-editor/colors.js +36 -0
  17. package/core/client/utils/bands-editor/dom.js +196 -0
  18. package/core/client/utils/bands-editor/exampleSchema.json +1320 -0
  19. package/core/client/utils/bands-editor/index.js +68 -0
  20. package/core/client/utils/bands-editor/rgb.js +102 -0
  21. package/core/client/utils/index.js +5 -2
  22. package/core/client/views/Dashboard.vue +1 -1
  23. package/core/client/vite-env.d.ts +122 -0
  24. package/dist/client/{DashboardLayout-ByVs1DrY.js → DashboardLayout-Cq15p4TH.js} +5 -6
  25. package/dist/client/{DynamicWebComponent-C3W7HSQm.js → DynamicWebComponent-Cv-fPRG1.js} +1 -1
  26. package/dist/client/{EodashDatePicker-BIAf1sMT.js → EodashDatePicker-CPlJwEIO.js} +20 -22
  27. package/dist/client/{EodashItemFilter-DPznh8UB.js → EodashItemFilter-Ydebgbjj.js} +46 -31
  28. package/dist/client/EodashLayerControl-COhrkNEs.js +1517 -0
  29. package/dist/client/{EodashLayoutSwitcher-C5qTEffW.js → EodashLayoutSwitcher-pnKhTRZV.js} +4 -4
  30. package/dist/client/EodashMapBtns-Cj0Fx119.js +301 -0
  31. package/dist/client/{EodashStacInfo-CSvvF2jI.js → EodashStacInfo-Dadkg_Nj.js} +1 -1
  32. package/dist/client/EodashTimeSlider-CpoHX0S7.js +53 -0
  33. package/dist/client/{EodashTools-Cv1SXQ5y.js → EodashTools-UGBG7KC9.js} +10 -7
  34. package/dist/client/{ExportState-D-iuwaad.js → ExportState-GtJkAqeZ.js} +145 -121
  35. package/dist/client/{Footer-CyF0zRAk.js → Footer-D3ZPG5c4.js} +1 -1
  36. package/dist/client/{Header-CgD8jDKU.js → Header-z6AK-wpN.js} +2 -3
  37. package/dist/client/MobileLayout-BXNsNftb.js +118 -0
  38. package/dist/client/{PopUp-BsYLvWch.js → PopUp-BbQdjENV.js} +79 -44
  39. package/dist/client/{ProcessList-C2xsLU2_.js → ProcessList-C6VsdsYI.js} +18 -12
  40. package/dist/client/{VImg-OHe8YTs2.js → VImg-CxaMSB99.js} +203 -5
  41. package/dist/client/{VMain-PryTLU4a.js → VMain-Ds7yw0wj.js} +1 -1
  42. package/dist/client/{VTooltip-DZ0fjpB3.js → VTooltip-Cze6CEVh.js} +2 -3
  43. package/dist/client/{WidgetsContainer-B9LBadcC.js → WidgetsContainer-D66bj-JJ.js} +1 -1
  44. package/dist/client/asWebComponent-CWbNRdf9.js +8895 -0
  45. package/dist/client/{async-DkSu_u2K.js → async-BA7oWCMX.js} +69 -5
  46. package/dist/client/easing-CH0-9wR8.js +35 -0
  47. package/dist/client/eo-dash.js +1 -1
  48. package/dist/client/{VOverlay-yUn7p-Uf.js → forwardRefs-BUfxOIo-.js} +308 -28
  49. package/dist/client/{handling-CgmFXkW6.js → handling-DlNTtKB-.js} +27 -6
  50. package/dist/client/{helpers-Dy0Q13tP.js → helpers-CtE0W7iu.js} +595 -278
  51. package/dist/client/{index-skjhlH8u.js → index-CeEZIjO6.js} +26 -13
  52. package/dist/client/{index-Ch_HchK3.js → index-CsKbRDeN.js} +238 -77
  53. package/dist/client/{index-Dqj4tbx2.js → index-D4_NRKrf.js} +2 -2
  54. package/dist/client/index-DeECc3lV.js +571 -0
  55. package/dist/client/material-symbols-outlined.woff2 +0 -0
  56. package/dist/client/material-symbols-rounded.woff2 +0 -0
  57. package/dist/client/material-symbols-sharp.woff2 +0 -0
  58. package/dist/client/material-symbols-subset.woff2 +0 -0
  59. package/dist/client/templates.js +106 -49
  60. package/dist/client/{transition-C98Yn4Vo.js → transition-Byvp3L6Y.js} +1 -1
  61. package/dist/node/cli.js +6 -6
  62. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +24 -10
  63. package/dist/types/core/client/eodashSTAC/auth.d.ts +7 -0
  64. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +15 -3
  65. package/dist/types/core/client/eodashSTAC/helpers.d.ts +51 -15
  66. package/dist/types/core/client/plugins/vuetify.d.ts +14 -14
  67. package/dist/types/core/client/store/actions.d.ts +2 -0
  68. package/dist/types/core/client/store/stac.d.ts +16 -7
  69. package/dist/types/core/client/store/states.d.ts +4 -0
  70. package/dist/types/core/client/types.d.ts +171 -3
  71. package/dist/types/core/client/utils/bands-editor/arithmetic.d.ts +8 -0
  72. package/dist/types/core/client/utils/bands-editor/colors.d.ts +15 -0
  73. package/dist/types/core/client/utils/bands-editor/dom.d.ts +42 -0
  74. package/dist/types/core/client/utils/bands-editor/index.d.ts +20 -0
  75. package/dist/types/core/client/utils/bands-editor/rgb.d.ts +15 -0
  76. package/dist/types/core/client/utils/index.d.ts +1 -1
  77. package/dist/types/templates/baseConfig.d.ts +87 -1
  78. package/dist/types/templates/compare.d.ts +0 -25
  79. package/dist/types/templates/expert.d.ts +17 -21
  80. package/dist/types/templates/explore.d.ts +67 -0
  81. package/dist/types/templates/index.d.ts +1 -1
  82. package/dist/types/templates/{light.d.ts → lite.d.ts} +9 -0
  83. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +21 -0
  84. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +49 -0
  85. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +4 -0
  86. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +12 -0
  87. package/dist/types/widgets/EodashItemCatalog/types.d.ts +14 -0
  88. package/dist/types/widgets/{EodashMapBtns.vue.d.ts → EodashMap/EodashMapBtns.vue.d.ts} +6 -0
  89. package/dist/types/widgets/EodashMap/index.vue.d.ts +114 -0
  90. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -1
  91. package/dist/types/widgets/EodashMap/methods/index.d.ts +1 -1
  92. package/dist/types/widgets/EodashProcess/methods/async.d.ts +1 -0
  93. package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
  94. package/dist/types/widgets/EodashTimeSlider.vue.d.ts +7 -0
  95. package/dist/types/widgets/EodashTools.vue.d.ts +10 -10
  96. package/dist/types/widgets/ExportState.vue.d.ts +2 -0
  97. package/package.json +31 -28
  98. package/templates/baseConfig.js +10 -5
  99. package/templates/compare.js +2 -22
  100. package/templates/expert.js +19 -18
  101. package/templates/explore.js +62 -0
  102. package/templates/index.js +1 -1
  103. package/templates/{light.js → lite.js} +11 -2
  104. package/widgets/EodashDatePicker.vue +15 -18
  105. package/widgets/EodashItemCatalog/index.vue +161 -0
  106. package/widgets/EodashItemCatalog/methods/filters.js +216 -0
  107. package/widgets/EodashItemCatalog/methods/handlers.js +50 -0
  108. package/widgets/EodashItemCatalog/methods/map.js +144 -0
  109. package/widgets/EodashItemCatalog/types.ts +15 -0
  110. package/widgets/EodashItemFilter.vue +35 -28
  111. package/widgets/EodashLayerControl.vue +10 -6
  112. package/widgets/EodashLayoutSwitcher.vue +1 -1
  113. package/widgets/EodashMap/EodashMapBtns.vue +278 -0
  114. package/widgets/EodashMap/index.vue +263 -38
  115. package/widgets/EodashMap/methods/create-layers-config.js +9 -6
  116. package/widgets/EodashMap/methods/index.js +27 -13
  117. package/widgets/EodashProcess/ProcessList.vue +13 -1
  118. package/widgets/EodashProcess/index.vue +17 -1
  119. package/widgets/EodashProcess/methods/async.js +22 -1
  120. package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +25 -3
  121. package/widgets/EodashProcess/methods/handling.js +2 -0
  122. package/widgets/EodashProcess/methods/outputs.js +1 -0
  123. package/widgets/EodashProcess/methods/utils.js +45 -1
  124. package/widgets/EodashTimeSlider.vue +40 -0
  125. package/widgets/EodashTools.vue +7 -3
  126. package/widgets/ExportState.vue +53 -22
  127. package/dist/client/EodashLayerControl-Bhxjw4V2.js +0 -154
  128. package/dist/client/EodashMapBtns-WoGq8MuV.js +0 -173
  129. package/dist/client/MobileLayout-EKQ_kpSh.js +0 -1226
  130. package/dist/client/asWebComponent-By_7_JjS.js +0 -19193
  131. package/dist/client/forwardRefs-BXxrv98s.js +0 -272
  132. package/dist/client/index-BuhOHXKv.js +0 -199
  133. package/widgets/EodashMapBtns.vue +0 -155
@@ -46,14 +46,28 @@ export function generateFeatures(links, extraProperties = {}, rel = "item") {
46
46
  *
47
47
  * @param {string} collectionId
48
48
  * @param { import("@/types").EodashStyleJson} [style]
49
+ * @param {Record<string,any>} [rasterJsonform]
49
50
  * */
50
- export function extractLayerConfig(collectionId, style) {
51
- if (!style) {
51
+ export function extractLayerConfig(collectionId, style, rasterJsonform) {
52
+ if (!style && !rasterJsonform) {
52
53
  return { layerConfig: undefined, style: undefined };
53
54
  }
54
- style = { ...style };
55
+ if (style) {
56
+ style = { ...style };
57
+ }
58
+
59
+ if (rasterJsonform) {
60
+ return {
61
+ layerConfig: {
62
+ schema: rasterJsonform.jsonform,
63
+ legend: rasterJsonform.legend,
64
+ type: "tileUrl",
65
+ },
66
+ style,
67
+ };
68
+ }
55
69
 
56
- if (Object.keys(style.variables ?? {}).length) {
70
+ if (style?.variables && Object.keys(style.variables ?? {}).length) {
57
71
  style.variables = getStyleVariablesState(collectionId, style.variables);
58
72
  }
59
73
 
@@ -61,6 +75,7 @@ export function extractLayerConfig(collectionId, style) {
61
75
  let layerConfig = undefined;
62
76
 
63
77
  if (style?.jsonform) {
78
+ // this explicitly sets legend only if jsonform is configured
64
79
  layerConfig = { schema: style.jsonform, type: "style" };
65
80
  delete style.jsonform;
66
81
  if (style?.legend) {
@@ -75,7 +90,25 @@ export function extractLayerConfig(collectionId, style) {
75
90
 
76
91
  return { layerConfig, style };
77
92
  }
78
-
93
+ /**
94
+ *
95
+ * @param {number[]} bbox
96
+ * @returns
97
+ */
98
+ export const sanitizeBbox = (bbox) => {
99
+ if (!bbox || !bbox.length || bbox.length !== 4) {
100
+ return [0, 0, 0, 0];
101
+ }
102
+ let [minX, minY, maxX, maxY] = bbox;
103
+ // Normalize longitudes to be within -180 to 180
104
+ minX = Math.max(((minX + 180) % 360) - 180, -180);
105
+ maxX = Math.min(((maxX - 180) % 360) + 180, 180);
106
+ // Normalize latitudes to be within -90 to 90
107
+ minY = Math.max(((minY + 90) % 180) - 90, -90);
108
+ maxY = Math.min(((maxY - 90) % 180) + 90, 90);
109
+
110
+ return [minX, minY, maxX, maxY];
111
+ };
79
112
  /**
80
113
  * Function to extract collection urls from an indicator
81
114
  * @param {import("stac-ts").StacCatalog
@@ -100,6 +133,10 @@ export function extractCollectionUrls(stacObject, basepath) {
100
133
  return collectionUrls;
101
134
  }
102
135
  children.forEach((link) => {
136
+ if (link.href.startsWith("http")) {
137
+ collectionUrls.push(link.href);
138
+ return;
139
+ }
103
140
  collectionUrls.push(toAbsolute(link.href, basepath));
104
141
  });
105
142
  return collectionUrls;
@@ -115,6 +152,8 @@ export const extractRoles = (properties, linkOrAsset) => {
115
152
  roles?.forEach((role) => {
116
153
  if (role === "visible") {
117
154
  properties.visible = true;
155
+ } else if (role === "invisible") {
156
+ properties.visible = false;
118
157
  }
119
158
  if (role === "overlay" || role === "baselayer") {
120
159
  properties.group = role;
@@ -124,13 +163,19 @@ export const extractRoles = (properties, linkOrAsset) => {
124
163
  };
125
164
 
126
165
  /**
127
- * Extracts style JSON from a STAC Item
166
+ * Extracts a single non-link style JSON from a STAC Item optionally for a selected key mapping
128
167
  * @param {import("stac-ts").StacItem} item
129
168
  * @param {string} itemUrl
130
- * @returns
169
+ * @param {string | undefined} key
170
+ * @returns
131
171
  **/
132
- export const fetchStyle = async (item, itemUrl) => {
133
- const styleLink = item.links.find((link) => link.rel.includes("style"));
172
+ export const fetchStyle = async (item, itemUrl, key=undefined) => {
173
+ let styleLink = null;
174
+ if (key) {
175
+ styleLink = item.links.find((link) => link.rel.includes("style") && link["links:keys"] && /** @type {Array<string>} */ (link["links:keys"]).includes(key) );
176
+ } else {
177
+ styleLink = item.links.find((link) => link.rel.includes("style") && !link["links:keys"]);
178
+ }
134
179
  if (styleLink) {
135
180
  let url = "";
136
181
  if (styleLink.href.startsWith("http")) {
@@ -147,6 +192,27 @@ export const fetchStyle = async (item, itemUrl) => {
147
192
  }
148
193
  };
149
194
 
195
+ /**
196
+ * Fetches all style JSONs from a STAC Item and returns an array with style objects
197
+ * @param {import("stac-ts").StacItem} item
198
+ * @param {string} itemUrl
199
+ * @returns { Promise <Array<import("@/types").EodashStyleJson>>}
200
+ **/
201
+ export const fetchAllStyles = async (item, itemUrl) => {
202
+ const styleLinks = item.links.filter((link) => link.rel.includes("style"));
203
+ const fetchPromises = styleLinks.map(async (link) => {
204
+ let url = link.href.startsWith("http")
205
+ ? link.href
206
+ : toAbsolute(link.href, itemUrl);
207
+
208
+ const styleJson = await axios.get(url).then((resp) => resp.data);
209
+ log.debug("fetched styles JSON", JSON.parse(JSON.stringify(styleJson)));
210
+ return styleJson;
211
+ });
212
+ const results = await Promise.all(fetchPromises);
213
+ return results;
214
+ };
215
+
150
216
  /**
151
217
  * Return projection code which is to be registered in `eox-map`
152
218
  * @param {string|number|{name: string, def: string}} [projection]
@@ -169,59 +235,89 @@ export const getProjectionCode = (projection) => {
169
235
 
170
236
  /**
171
237
  * Extracts layercontrol LayerDatetime property from STAC Links
172
- * @param {import("stac-ts").StacLink[]} [links]
238
+ * @param {import("stac-ts").StacLink[] | import("stac-ts").StacItem[] | undefined} [items]
173
239
  * @param {string|null} [currentStep]
174
240
  **/
175
- export const extractLayerDatetime = (links, currentStep) => {
176
- if (!currentStep || !links?.length) {
177
- return undefined;
241
+ export const extractLayerTimeValues = (items, currentStep) => {
242
+ if (!currentStep || !items?.length) {
243
+ return { layerDatetime: undefined, timeControlValues: undefined };
178
244
  }
179
245
 
180
- // check if links has a datetime value
181
- const dateProperty = getDatetimeProperty(links);
246
+ // check if items has a datetime value
247
+ const dateProperty = getDatetimeProperty(items);
182
248
 
183
249
  if (!dateProperty) {
184
- return undefined;
250
+ return { layerDatetime: undefined, timeControlValues: undefined };
185
251
  }
186
-
187
- /** @type {string[]} */
188
- const controlValues = [];
252
+ /** @type {{date:string;itemId:string}[]} */
253
+ const timeValues = [];
189
254
  try {
190
- currentStep = new Date(currentStep).toISOString();
191
- links.reduce((vals, link) => {
255
+ /**
256
+ * @param {typeof timeValues} vals
257
+ * @param {import("stac-ts").StacLink} link
258
+ */
259
+ const reduceLinks = (vals, link) => {
192
260
  if (link[dateProperty] && link.rel === "item") {
193
- vals.push(
194
- new Date(/** @type {string} */ (link[dateProperty])).toISOString(),
195
- );
261
+ vals.push({
262
+ itemId: /** @type {string} */ (link.id),
263
+ date: new Date(
264
+ /** @type {string} */ (link[dateProperty]),
265
+ ).toISOString(),
266
+ });
196
267
  }
197
268
  return vals;
198
- }, controlValues);
269
+ };
270
+
271
+ /**
272
+ *
273
+ * @param {typeof timeValues} vals
274
+ * @param {import("stac-ts").StacItem} item
275
+ */
276
+ const reduceItems = (vals, item) => {
277
+ const date = item.properties?.[dateProperty];
278
+ if (date) {
279
+ vals.push({
280
+ itemId: /** @type {string} */ (item.id),
281
+ date: new Date(/** @type {string} */ (date)).toISOString(),
282
+ ...item.properties,
283
+ });
284
+ }
285
+ return vals;
286
+ };
287
+ currentStep = new Date(currentStep).toISOString();
288
+ //@ts-expect-error TODO
289
+ items.reduce(isSTACItem(items[0]) ? reduceItems : reduceLinks, timeValues);
199
290
  } catch (e) {
200
291
  console.warn("[eodash] not supported datetime format was provided", e);
201
- return undefined;
292
+ return { layerDatetime: undefined, timeControlValues: undefined };
202
293
  }
203
- // not enough controlValues
204
- if (controlValues.length <= 1) {
205
- return undefined;
294
+ // not enough timeValues
295
+ if (timeValues.length <= 1) {
296
+ return { layerDatetime: undefined, timeControlValues: undefined };
206
297
  }
207
298
 
208
299
  // item datetime is not included in the item links datetime
209
- if (!controlValues.includes(currentStep)) {
300
+ if (!timeValues.some((val) => val.date === currentStep)) {
210
301
  const currentStepTime = new Date(currentStep).getTime();
211
- currentStep = controlValues.reduce((a, b) => {
212
- const aDiff = Math.abs(new Date(a).getTime() - currentStepTime);
213
- const bDiff = Math.abs(new Date(b).getTime() - currentStepTime);
214
- return bDiff < aDiff ? b : a;
215
- });
302
+ currentStep = timeValues.reduce((time, step) => {
303
+ const aDiff = Math.abs(new Date(time).getTime() - currentStepTime);
304
+ const bDiff = Math.abs(new Date(step.date).getTime() - currentStepTime);
305
+ return bDiff < aDiff ? step.date : time;
306
+ }, timeValues[0].date);
216
307
  }
217
308
 
218
- return {
219
- controlValues,
309
+ const layerDatetime = {
310
+ controlValues: timeValues.map((d) => d.date).sort(),
220
311
  currentStep,
221
312
  slider: true,
222
313
  navigation: true,
223
314
  play: false,
224
- displayFormat: "DD.MM.YYYY HH:MM",
315
+ displayFormat: "DD.MM.YYYY HH:mm",
316
+ };
317
+
318
+ return {
319
+ layerDatetime,
320
+ timeControlValues: timeValues,
225
321
  };
226
322
  };
227
323
 
@@ -287,23 +383,23 @@ export const replaceLayer = (currentLayers, oldLayer, newLayers) => {
287
383
  */
288
384
  export const getColFromLayer = (indicators, layer) => {
289
385
  // init cols
290
- const collections = indicators.map((ind) => ind.collectionStac);
386
+ // const collections = indicators.map((ind) => ind.collectionStac);
291
387
  const [collectionId, itemId, ..._other] = layer.get("id").split(";:;");
292
388
 
293
- const chosen = collections.find((col) => {
294
- const isInd =
295
- col?.id === collectionId &&
296
- col?.links?.some(
297
- (link) =>
298
- link.rel === "item" &&
299
- (link.href.includes(itemId) ||
300
- link.id === itemId ||
301
- //@ts-expect-error attaching the item in link when parsing .parquet items, see @/eodashSTAC/parquet.js
302
- (link["item"] && link["item"].id === itemId)),
389
+ return indicators.find(async (ind) => {
390
+ const isCollection = ind.collectionStac?.id === collectionId;
391
+ if (!isCollection) {
392
+ return false;
393
+ }
394
+ /** @type {string[]} */
395
+ const itemIds = [];
396
+ await ind.getItems().then((items) => {
397
+ itemIds.push(
398
+ ...(items?.map((item) => /** @type {string} */ (item.id)) ?? []),
303
399
  );
304
- return isInd;
400
+ });
401
+ return itemIds.includes(itemId);
305
402
  });
306
- return indicators.find((ind) => ind.collectionStac?.id === chosen?.id);
307
403
  };
308
404
 
309
405
  /**
@@ -413,12 +509,20 @@ export async function mergeGeojsons(geojsonUrls) {
413
509
  features: [],
414
510
  };
415
511
  await Promise.all(
416
- geojsonUrls.map((url) =>
417
- axios.get(url).then((resp) => {
512
+ geojsonUrls.map((url) => {
513
+ // Use native fetch for blob URLs to avoid axios/cache interceptor issues
514
+ if (url.startsWith("blob:")) {
515
+ return fetch(url)
516
+ .then(async (resp) => await resp.json())
517
+ .then((geojson) => {
518
+ merged.features.push(...(geojson.features ?? []));
519
+ });
520
+ }
521
+ return axios.get(url).then((resp) => {
418
522
  const geojson = resp.data;
419
523
  merged.features.push(...(geojson.features ?? []));
420
- }),
421
- ),
524
+ });
525
+ }),
422
526
  );
423
527
 
424
528
  return encodeURI(
@@ -532,16 +636,37 @@ export const addTooltipInteraction = (layer, style) => {
532
636
 
533
637
  /**
534
638
  *
535
- * @param {import("stac-ts").StacLink[]} [links]
639
+ * @param {import("stac-ts").StacLink[] | import("stac-ts").StacItem[] |undefined |null} [linksOrItems]
536
640
  */
537
- export function getDatetimeProperty(links) {
538
- if (!links?.length) {
641
+ export function getDatetimeProperty(linksOrItems) {
642
+ if (!linksOrItems?.length) {
539
643
  return undefined;
540
644
  }
645
+ const first = linksOrItems[0];
646
+ let checkProperties = false;
647
+ if (isSTACItem(first)) {
648
+ checkProperties = true;
649
+ }
650
+
541
651
  // TODO: consider other properties for datetime ranges
542
652
  const datetimeProperties = ["datetime", "start_datetime", "end_datetime"];
653
+ if (checkProperties) {
654
+ for (const prop of datetimeProperties) {
655
+ const propExists = linksOrItems.some(
656
+ (l) =>
657
+ //@ts-expect-error TODO
658
+ l["properties"]?.[prop] &&
659
+ //@ts-expect-error TODO
660
+ typeof l["properties"]?.[prop] === "string",
661
+ );
662
+ if (!propExists) {
663
+ continue;
664
+ }
665
+ return prop;
666
+ }
667
+ }
543
668
  for (const prop of datetimeProperties) {
544
- const propExists = links.some(
669
+ const propExists = linksOrItems.some(
545
670
  (l) => l[prop] && typeof l[prop] === "string",
546
671
  );
547
672
  if (!propExists) {
@@ -550,3 +675,100 @@ export function getDatetimeProperty(links) {
550
675
  return prop;
551
676
  }
552
677
  }
678
+ /**
679
+ *
680
+ * @param {*} stacObject
681
+ * @returns {stacObject is import("stac-ts").StacItem}
682
+ */
683
+ export function isSTACItem(stacObject) {
684
+ return (
685
+ stacObject &&
686
+ typeof stacObject === "object" &&
687
+ stacObject.collection &&
688
+ stacObject.id &&
689
+ stacObject.properties &&
690
+ typeof stacObject.properties === "object"
691
+ );
692
+ }
693
+
694
+ /**
695
+ * Fetch all STAC items from a STAC API endpoint.
696
+ * @param {string} itemsUrl
697
+ * @param {string} [query]
698
+ * @param {number} [limit=100] - The maximum number of items to fetch per request.
699
+ * @param {boolean} [returnFirst] - If true, only the first page of results will be returned.
700
+ * @param {number} [maxNumber=1000] - if the matched number of items exceed this, only the first page will be returned.
701
+ */
702
+ export async function fetchApiItems(
703
+ itemsUrl,
704
+ query,
705
+ limit = 100,
706
+ returnFirst = false,
707
+ maxNumber = 1000,
708
+ ) {
709
+ itemsUrl = itemsUrl.includes("?") ? itemsUrl.split("?")[0] : itemsUrl;
710
+ itemsUrl += query ? `?limit=${limit}&${query}` : `?limit=${limit}`;
711
+
712
+ const itemsFeatureCollection = await axios
713
+ .get(itemsUrl)
714
+ .then((resp) => resp.data);
715
+ /** @type {import("stac-ts").StacItem[]} */
716
+ const items = itemsFeatureCollection.features;
717
+ const nextLink = itemsFeatureCollection.links?.find(
718
+ //@ts-expect-error TODO: itemsFeatureCollection is not typed
719
+ (link) => link.rel === "next",
720
+ );
721
+
722
+ if (!nextLink || returnFirst) {
723
+ return items;
724
+ }
725
+ /** @type {number} */
726
+ const matchedItems = itemsFeatureCollection.numberMatched;
727
+ // Avoid fetching too many items
728
+ if (matchedItems >= maxNumber) {
729
+ console.warn(
730
+ `[eodash] The number of items matched (${matchedItems}) exceeds the maximum allowed (${maxNumber})`,
731
+ );
732
+ return items;
733
+ }
734
+
735
+ let [nextLinkURL, nextLinkQuery] = nextLink.href.split("?");
736
+ nextLinkQuery = nextLinkQuery.replace(/limit=\d+/, "");
737
+ if (query) {
738
+ const queryParams = new URLSearchParams(query);
739
+ const nextLinkParams = new URLSearchParams(nextLinkQuery);
740
+
741
+ for (const key of nextLinkParams.keys()) {
742
+ queryParams.delete(key);
743
+ }
744
+ const remainingQuery = queryParams.toString();
745
+ if (remainingQuery) {
746
+ nextLinkQuery += `&${remainingQuery}`;
747
+ }
748
+ }
749
+
750
+ const nextPage = await fetchApiItems(nextLinkURL, nextLinkQuery);
751
+ items.push(...nextPage);
752
+ return items;
753
+ }
754
+ /**
755
+ * @param {import ("stac-ts").StacCollection | undefined | null} collection
756
+ * @returns {object}
757
+ */
758
+ export function extractLayerLegend(collection) {
759
+ let extraProperties = {};
760
+ if (collection?.assets?.legend?.href) {
761
+ extraProperties = {
762
+ description: `<div style="width: 100%">
763
+ <img src="${collection.assets.legend.href}" style="max-height:70px; margin-top:-15px; margin-bottom:-20px;" />
764
+ </div>`,
765
+ };
766
+ }
767
+ // Check if collection has eox:colorlegend definition, if yes overwrite legend description
768
+ if (collection && collection["eox:colorlegend"]) {
769
+ extraProperties = {
770
+ layerLegend: collection["eox:colorlegend"],
771
+ };
772
+ }
773
+ return extraProperties;
774
+ }
@@ -1,6 +1,4 @@
1
1
  import { parquetRead } from "hyparquet";
2
- import WKB from "ol/format/WKB.js";
3
- import GeoJSON from "ol/format/GeoJSON";
4
2
  import log from "loglevel";
5
3
 
6
4
  /**
@@ -62,8 +60,6 @@ export const adjustParquetItems = (items) => {
62
60
 
63
61
  return /** @type {import("stac-ts").StacItem} */ ({
64
62
  ...item,
65
- //@ts-expect-error geometry wkb conversion by stac-geoparquet
66
- geometry: wkbToGeometry(item.geometry),
67
63
 
68
64
  assets: ((assets) => {
69
65
  for (const [key, value] of Object.entries(assets)) {
@@ -82,15 +78,6 @@ export const adjustParquetItems = (items) => {
82
78
  });
83
79
  });
84
80
  };
85
- /**
86
- * @param {Uint8Array} wkb - Well Known Binary
87
- */
88
- function wkbToGeometry(wkb) {
89
- const geoJsonFormatter = new GeoJSON();
90
- const wkbReader = new WKB();
91
- const olGeometry = wkbReader.readGeometry(wkb);
92
- return geoJsonFormatter.writeGeometryObject(olGeometry);
93
- }
94
81
 
95
82
  /**
96
83
  *
@@ -69,7 +69,7 @@ export function getStyleVariablesState(collectionId, variables) {
69
69
  ];
70
70
  return (
71
71
  collection === collectionId &&
72
- ["Vector", "WebGLTile"].includes(layer?.type ?? "")
72
+ ["Vector", "WebGLTile", "VectorTile"].includes(layer?.type ?? "")
73
73
  );
74
74
  });
75
75
 
@@ -5,6 +5,8 @@ import {
5
5
  activeTemplate,
6
6
  poi,
7
7
  comparePoi,
8
+ chartEl,
9
+ compareChartEl,
8
10
  } from "@/store/states";
9
11
  import { getProjectionCode } from "@/eodashSTAC/helpers";
10
12
  import log from "loglevel";
@@ -22,6 +24,18 @@ export const getLayers = () => mapEl.value?.layers.toReversed() ?? [];
22
24
  export const getCompareLayers = () =>
23
25
  mapCompareEl.value?.layers.toReversed() ?? [];
24
26
 
27
+ /**
28
+ * Returns the current chart spec from {@link chartEl}
29
+ * @returns {import("vega-embed").VisualizationSpec | null}
30
+ */
31
+ export const getChartSpec = () => chartEl.value?.spec ?? null;
32
+
33
+ /**
34
+ * Returns the current chart spec from {@link compareChartEl}
35
+ * @returns {import("vega-embed").VisualizationSpec | null}
36
+ */
37
+ export const getCompareChartSpec = () => compareChartEl.value?.spec ?? null;
38
+
25
39
  /**
26
40
  * Register EPSG projection in `eox-map`
27
41
  * @param {string|number|{name: string, def: string, extent?:number[]}} [projection]*/
@@ -19,9 +19,15 @@ import { updateEodashCollections } from "@/utils";
19
19
  export const useSTAcStore = defineStore("stac", () => {
20
20
  /**
21
21
  * STAC catalog endpoint URL
22
- * @type {import("vue").Ref<import("@/types").StacEndpoint | null>}
22
+ * @type {import("vue").Ref<string| null>}
23
23
  */
24
24
  const stacEndpoint = ref(null);
25
+ /**
26
+ * Raster endpoint URL
27
+ * @type {import("vue").Ref<string | null>}
28
+ */
29
+ const rasterEndpoint = ref(null);
30
+ const isApi = ref(false);
25
31
 
26
32
  /**
27
33
  * Links of the root STAC catalog
@@ -49,20 +55,35 @@ export const useSTAcStore = defineStore("stac", () => {
49
55
  * >}
50
56
  */
51
57
  const selectedCompareStac = ref(null);
58
+ /**
59
+ * Currently selected item
60
+ * @type {import("vue").Ref<import("stac-ts").StacLink | import("stac-ts").StacItem | null>}
61
+ */
62
+ const selectedItem = ref(null);
52
63
 
53
64
  /**
54
65
  * Initializes the store by assigning the STAC endpoint.
55
66
  * @param {import("@/types").StacEndpoint} endpoint
56
67
  */
57
68
  function init(endpoint) {
58
- stacEndpoint.value = endpoint;
69
+ if (!endpoint) {
70
+ throw new Error("STAC endpoint is not defined");
71
+ }
72
+
73
+ if (typeof endpoint === "string") {
74
+ stacEndpoint.value = endpoint;
75
+ return;
76
+ }
77
+ stacEndpoint.value = endpoint.endpoint;
78
+ isApi.value = endpoint.api ?? false;
79
+ rasterEndpoint.value = endpoint.rasterEndpoint ?? null;
59
80
  }
60
81
 
61
82
  /**
62
83
  * Fetches root stac catalog and assign it to `stac`
63
84
  *
64
- * @param {import("@/types").StacEndpoint} [url=eodash.stacEndpoint] Default
65
- * is `eodash.stacEndpoint`
85
+ * @param {string} [url=stacEndpoint] Default
86
+ * is the configured `stacEndpoint` url
66
87
  * @returns {Promise<void>}
67
88
  * @see {@link stac}
68
89
  */
@@ -78,14 +99,17 @@ export const useSTAcStore = defineStore("stac", () => {
78
99
  stac.value = null;
79
100
  return;
80
101
  }
102
+ if (isApi.value) {
103
+ url = url + "/collections?limit=1000"; // to get all collections
104
+ }
105
+ const property = isApi.value ? "collections" : "links";
81
106
 
82
107
  log.debug("Loading STAC endpoint", url);
83
108
  await axios
84
109
  .get(url)
85
110
  .then((resp) => {
86
- const links = /** @type {import("stac-ts").StacCatalog} */ (
87
- resp.data
88
- ).links.map((link) => {
111
+ //@ts-expect-error TODO
112
+ const links = resp.data[property].map((link) => {
89
113
  if (!link.title) {
90
114
  link.title = `${link.rel} ${link.href}`;
91
115
  }
@@ -104,10 +128,11 @@ export const useSTAcStore = defineStore("stac", () => {
104
128
  *
105
129
  * @param {string} relativePath - Stac link href
106
130
  * @param {boolean} [isPoi=false] - If true, the STAC is loaded for a point of interest
131
+ * @param {Object} [stacItem] - The STAC item to load
107
132
  * @returns {Promise<void>}
108
133
  * @see {@link selectedStac}
109
134
  */
110
- async function loadSelectedSTAC(relativePath = "", isPoi = false) {
135
+ async function loadSelectedSTAC(relativePath = "", isPoi = false, stacItem) {
111
136
  if (!stacEndpoint.value) {
112
137
  return Promise.reject(new Error("STAC endpoint is not defined"));
113
138
  }
@@ -116,6 +141,11 @@ export const useSTAcStore = defineStore("stac", () => {
116
141
  // construct absolute URL of a poi
117
142
  absoluteUrl.value = constructPoiUrl(relativePath, indicator.value);
118
143
  }
144
+ //@ts-expect-error "this" type is not exported by pinia
145
+ const patch = this?.$patch;
146
+ if (stacItem && patch) {
147
+ patch({ selectedItem: stacItem });
148
+ }
119
149
 
120
150
  await axios
121
151
  .get(absoluteUrl.value)
@@ -125,6 +155,8 @@ export const useSTAcStore = defineStore("stac", () => {
125
155
  resp.data,
126
156
  absoluteUrl.value,
127
157
  collectionsPalette,
158
+ isApi.value,
159
+ rasterEndpoint.value,
128
160
  );
129
161
  selectedStac.value = resp.data;
130
162
  // set indicator and poi
@@ -141,6 +173,7 @@ export const useSTAcStore = defineStore("stac", () => {
141
173
  * Fetches selected stac object and assign it to `selectedCompareStac`
142
174
  *
143
175
  * @param {string} relativePath - Stac link href
176
+ * @param {boolean} [isPOI=false] - If true, the STAC is loaded for a point of interest
144
177
  * @returns {Promise<void>}
145
178
  * @see {@link selectedCompareStac}
146
179
  */
@@ -163,6 +196,8 @@ export const useSTAcStore = defineStore("stac", () => {
163
196
  resp.data,
164
197
  absoluteUrl.value,
165
198
  [...collectionsPalette].reverse(),
199
+ isApi.value,
200
+ rasterEndpoint.value,
166
201
  );
167
202
  selectedCompareStac.value = resp.data;
168
203
  compareIndicator.value = isPOI
@@ -203,6 +238,8 @@ export const useSTAcStore = defineStore("stac", () => {
203
238
  }
204
239
 
205
240
  return {
241
+ stacEndpoint,
242
+ isApi,
206
243
  stac,
207
244
  init,
208
245
  loadSTAC,
@@ -211,5 +248,6 @@ export const useSTAcStore = defineStore("stac", () => {
211
248
  resetSelectedCompareSTAC,
212
249
  selectedStac,
213
250
  selectedCompareStac,
251
+ selectedItem,
214
252
  };
215
253
  });
@@ -45,3 +45,9 @@ export const poi = ref("");
45
45
  * Selected point of interest for comparison, used for location collections
46
46
  */
47
47
  export const comparePoi = ref("");
48
+
49
+ /** @type {import("vue").Ref<import("@eox/chart").EOxChart | null>} */
50
+ export const chartEl = shallowRef(null);
51
+
52
+ /** @type {import("vue").Ref<import("@eox/chart").EOxChart | null>} */
53
+ export const compareChartEl = shallowRef(null);