@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,6 +1,6 @@
1
1
  import { registerProjection } from "@/store/actions";
2
2
  import { mapEl } from "@/store/states";
3
- import axios from "@/plugins/axios";
3
+ import { useEodash } from "@/composables";
4
4
 
5
5
  import {
6
6
  extractRoles,
@@ -11,10 +11,18 @@ import {
11
11
  extractLayerConfig,
12
12
  extractEoxLegendLink,
13
13
  addTooltipInteraction,
14
- fetchStyle,
14
+ fetchRasterForm,
15
+ resolveStyle,
16
+ getBandsProperty,
17
+ applyTitilerUpscaling,
18
+ encodeURLObject,
19
+ normalizeRescale,
20
+ normalizeNodata,
21
+ resolveRenders,
15
22
  } from "./helpers";
16
23
  import { handleAuthenticationOfLink } from "./auth";
17
24
  import log from "loglevel";
25
+ import axios from "@/plugins/axios";
18
26
  import { useSTAcStore } from "@/store/stac";
19
27
 
20
28
  /**
@@ -41,6 +49,7 @@ function buildCapabilitiesUrl(href) {
41
49
  * @param {import("stac-ts").StacItem | import("stac-ts").StacCollection } stacObject
42
50
  * @param {Record<string, unknown>} [layerDatetime]
43
51
  * @param {object | null} [extraProperties]
52
+ * @param {import("stac-ts").StacCollection | null} [collection] - Used to fall back to a collection-level style link.
44
53
  **/
45
54
  export async function createLayersFromAssets(
46
55
  collectionId,
@@ -49,6 +58,7 @@ export async function createLayersFromAssets(
49
58
  stacObject,
50
59
  layerDatetime,
51
60
  extraProperties,
61
+ collection,
52
62
  ) {
53
63
  log.debug("Creating layers from assets");
54
64
  const jsonArray = [];
@@ -60,18 +70,35 @@ export async function createLayersFromAssets(
60
70
 
61
71
  const fgbIdx = [];
62
72
  const fgbSources = [];
73
+ const zarrAssetIds = [];
74
+ const zarrIdx = [];
63
75
  const assetIds = [];
64
76
 
65
77
  for (const [idx, assetId] of Object.keys(assets).entries()) {
66
78
  assetIds.push(assetId);
67
79
 
68
- if (assets[assetId]?.type === "application/geo+json") {
80
+ if (
81
+ assets[assetId]?.type?.includes("application/geo+json") &&
82
+ assets[assetId]?.href?.includes("http")
83
+ ) {
69
84
  geoJsonSources.push(assets[assetId].href);
70
85
  geoJsonIdx.push(idx);
71
- } else if (assets[assetId]?.type === "application/vnd.flatgeobuf") {
86
+ } else if (
87
+ assets[assetId]?.type?.includes("application/vnd.flatgeobuf") &&
88
+ assets[assetId]?.href?.includes("http")
89
+ ) {
72
90
  fgbSources.push(assets[assetId].href);
73
91
  fgbIdx.push(idx);
74
- } else if (assets[assetId]?.type === "image/tiff") {
92
+ } else if (
93
+ assets[assetId]?.type ==
94
+ "application/vnd.zarr; version=3; profile=multiscales"
95
+ ) {
96
+ zarrAssetIds.push(assetId);
97
+ zarrIdx.push(idx);
98
+ } else if (
99
+ assets[assetId]?.type?.includes("image/tiff") &&
100
+ assets[assetId]?.href?.includes("http")
101
+ ) {
75
102
  geoTIFFIdx.push(idx);
76
103
  geoTIFFSources.push({
77
104
  url: assets[assetId].href,
@@ -79,7 +106,7 @@ export async function createLayersFromAssets(
79
106
  ? { attributions: assets[assetId].attribution }
80
107
  : {}),
81
108
  });
82
- } else if (assets[assetId]?.type === "application/geodb+json") {
109
+ } else if (assets[assetId]?.type?.includes("application/geodb+json")) {
83
110
  const responseData = await (await fetch(assets[assetId].href)).json();
84
111
  geoJsonIdx.push(idx);
85
112
  if (
@@ -140,11 +167,122 @@ export async function createLayersFromAssets(
140
167
  }
141
168
  }
142
169
 
170
+ if (geoTIFFSources.length) {
171
+ for (const [i, geotiffSource] of geoTIFFSources.entries()) {
172
+ const assetName = assetIds[geoTIFFIdx[i]];
173
+ const styles = await resolveStyle(
174
+ stacObject,
175
+ collection,
176
+ undefined,
177
+ assetName,
178
+ );
179
+ // get the correct style which is not attached to a link
180
+ let { layerConfig, style } = extractLayerConfig(collectionId, styles);
181
+ let assetLayerId = createAssetID(
182
+ collectionId,
183
+ stacObject.id,
184
+ geoTIFFIdx[i],
185
+ );
186
+ if (
187
+ assets[assetName]?.roles?.includes("overlay") ||
188
+ assets[assetName]?.roles?.includes("baselayer")
189
+ ) {
190
+ // to prevent them being removed by date change on main dataset
191
+ assetLayerId = assetName;
192
+ }
193
+ log.debug("Creating WebGLTile layer from GeoTIFF", assetLayerId);
194
+ log.debug("Configured Sources", geoTIFFSources);
195
+ const sources =
196
+ stacObject?.["eodash:merge_assets"] !== false
197
+ ? geoTIFFSources
198
+ : [geotiffSource];
199
+ const layer = {
200
+ type: "WebGLTile",
201
+ source: {
202
+ type: "GeoTIFF",
203
+ normalize: !style,
204
+ interpolate: false,
205
+ sources,
206
+ },
207
+ properties: {
208
+ id: assetLayerId,
209
+ title: assets[assetName]?.title || title,
210
+ layerConfig,
211
+ layerDatetime,
212
+ },
213
+ style,
214
+ };
215
+ if (extraProperties) {
216
+ layer.properties = { ...layer.properties, ...extraProperties };
217
+ }
218
+ extractRoles(layer.properties, assets[assetName]);
219
+ addTooltipInteraction(layer, style);
220
+ jsonArray.push(layer);
221
+ if (stacObject?.["eodash:merge_assets"] !== false) break;
222
+ }
223
+ }
224
+
225
+ if (zarrAssetIds.length) {
226
+ for (const [i, assetName] of zarrAssetIds.entries()) {
227
+ const fetchedStyle = await resolveStyle(
228
+ stacObject,
229
+ collection,
230
+ undefined,
231
+ assetName,
232
+ );
233
+ const { layerConfig, style } = extractLayerConfig(
234
+ collectionId,
235
+ fetchedStyle,
236
+ );
237
+ const bandsPath = getBandsProperty(layerConfig?.schema);
238
+ const defaultBands = bandsPath?.reduce(
239
+ (node, key) => node?.[key],
240
+ layerConfig?.schema,
241
+ )?.default ?? ["b04", "b03", "b02"];
242
+
243
+ let assetLayerId = createAssetID(collectionId, stacObject.id, zarrIdx[i]);
244
+ if (
245
+ assets[assetName]?.roles?.includes("overlay") ||
246
+ assets[assetName]?.roles?.includes("baselayer")
247
+ ) {
248
+ assetLayerId = assetName;
249
+ }
250
+
251
+ log.debug("Creating WebGLTile layer from GeoZarr", assetLayerId);
252
+
253
+ const layer = {
254
+ type: "WebGLTile",
255
+ properties: {
256
+ id: assetLayerId,
257
+ title: assets[assetName]?.title || title,
258
+ layerConfig,
259
+ layerDatetime,
260
+ },
261
+ source: {
262
+ type: "GeoZarr",
263
+ url: assets[assetName].href,
264
+ bands: defaultBands,
265
+ },
266
+ ...(style ? { style } : {}),
267
+ };
268
+ if (extraProperties) {
269
+ layer.properties = { ...layer.properties, ...extraProperties };
270
+ }
271
+ extractRoles(layer.properties, assets[assetName]);
272
+ jsonArray.push(layer);
273
+ }
274
+ }
275
+
143
276
  if (geoJsonSources.length) {
144
277
  for (const [i, geoJsonSource] of geoJsonSources.entries()) {
145
278
  // fetch styles and separate them by their mapping between links and assets
146
279
  const assetName = assetIds[geoJsonIdx[i]];
147
- const styles = await fetchStyle(stacObject, undefined, assetName);
280
+ const styles = await resolveStyle(
281
+ stacObject,
282
+ collection,
283
+ undefined,
284
+ assetName,
285
+ );
148
286
  // get the correct style which is not attached to a link
149
287
  let { layerConfig, style } = extractLayerConfig(collectionId, styles);
150
288
  let assetLayerId = createAssetID(
@@ -210,7 +348,12 @@ export async function createLayersFromAssets(
210
348
  for (const [i, fgbSource] of fgbSources.entries()) {
211
349
  // fetch styles and separate them by their mapping between links and assets
212
350
  const assetName = assetIds[fgbIdx[i]];
213
- const styles = await fetchStyle(stacObject, undefined, assetName);
351
+ const styles = await resolveStyle(
352
+ stacObject,
353
+ collection,
354
+ undefined,
355
+ assetName,
356
+ );
214
357
  // get the correct style which is not attached to a link
215
358
  let { layerConfig, style } = extractLayerConfig(collectionId, styles);
216
359
  let assetLayerId = createAssetID(collectionId, stacObject.id, fgbIdx[i]);
@@ -270,56 +413,6 @@ export async function createLayersFromAssets(
270
413
  }
271
414
  }
272
415
 
273
- if (geoTIFFSources.length) {
274
- for (const [i, geotiffSource] of geoTIFFSources.entries()) {
275
- const assetName = assetIds[geoTIFFIdx[i]];
276
- const styles = await fetchStyle(stacObject, undefined, assetName);
277
- // get the correct style which is not attached to a link
278
- let { layerConfig, style } = extractLayerConfig(collectionId, styles);
279
- let assetLayerId = createAssetID(
280
- collectionId,
281
- stacObject.id,
282
- geoTIFFIdx[i],
283
- );
284
- if (
285
- assets[assetName]?.roles?.includes("overlay") ||
286
- assets[assetName]?.roles?.includes("baselayer")
287
- ) {
288
- // to prevent them being removed by date change on main dataset
289
- assetLayerId = assetName;
290
- }
291
- log.debug("Creating WebGLTile layer from GeoTIFF", assetLayerId);
292
- log.debug("Configured Sources", geoTIFFSources);
293
- const sources =
294
- stacObject?.["eodash:merge_assets"] !== false
295
- ? geoTIFFSources
296
- : [geotiffSource];
297
- const layer = {
298
- type: "WebGLTile",
299
- source: {
300
- type: "GeoTIFF",
301
- normalize: !style,
302
- interpolate: false,
303
- sources,
304
- },
305
- properties: {
306
- id: assetLayerId,
307
- title: assets[assetName]?.title || title,
308
- layerConfig,
309
- layerDatetime,
310
- },
311
- style,
312
- };
313
- if (extraProperties) {
314
- layer.properties = { ...layer.properties, ...extraProperties };
315
- }
316
- extractRoles(layer.properties, assets[assetName]);
317
- addTooltipInteraction(layer, style);
318
- jsonArray.push(layer);
319
- if (stacObject?.["eodash:merge_assets"] !== false) break;
320
- }
321
- }
322
-
323
416
  return jsonArray;
324
417
  }
325
418
 
@@ -349,6 +442,10 @@ export const createLayersFromLinks = async (
349
442
  item.links.filter((l) => l.rel === "vector-tile") ?? [];
350
443
  const mapboxStyleDocumentArray =
351
444
  item.links.filter((l) => l.rel === "mapbox-style-document") ?? [];
445
+ // An xyz link takes precedence over a tilejson link;
446
+ const tilejsonArray = xyzArray.length
447
+ ? []
448
+ : (item.links.filter((l) => l.rel === "tilejson") ?? []);
352
449
  // Taking projection code from main map view, as main view defines
353
450
  // projection for comparison map
354
451
  const viewProjectionCode = mapEl?.value?.projection || "EPSG:3857";
@@ -371,6 +468,20 @@ export const createLayersFromLinks = async (
371
468
  wmsLink,
372
469
  viewProjectionCode,
373
470
  );
471
+ const rasterForm = await fetchRasterForm(
472
+ /** @type {string|object|undefined} */ (
473
+ wmsLink?.["eodash:rasterform"] ||
474
+ item?.["eodash:rasterform"] ||
475
+ collection?.["eodash:rasterform"]
476
+ ),
477
+ );
478
+ let { layerConfig } = extractLayerConfig(
479
+ collectionId,
480
+ {},
481
+ rasterForm,
482
+ "tileUrl",
483
+ );
484
+
374
485
  log.debug("WMS Layer added", linkId);
375
486
  const tileSize = /** @type {number[]} */ (
376
487
  "wms:tilesize" in wmsLink
@@ -383,6 +494,7 @@ export const createLayersFromLinks = async (
383
494
  id: linkId,
384
495
  title: wmsLink.title || title || item.id,
385
496
  layerDatetime,
497
+ layerConfig,
386
498
  },
387
499
  source: {
388
500
  type: "TileWMS",
@@ -438,15 +550,18 @@ export const createLayersFromLinks = async (
438
550
  (wmtsLink?.["proj:epsg"] || wmtsLink?.["eodash:proj4_def"]);
439
551
 
440
552
  await registerProjection(wmtsLinkProjection);
441
- const key =
442
- /** @type {string | undefined} */ (wmtsLink["key"]) || undefined;
443
553
 
444
- const styles = await fetchStyle(item, key);
445
- // get the correct style which is attached to a link
554
+ const rasterForm = await fetchRasterForm(
555
+ /** @type {string|object|undefined} */ (
556
+ wmtsLink?.["eodash:rasterform"] ||
557
+ item?.["eodash:rasterform"] ||
558
+ collection?.["eodash:rasterform"]
559
+ ),
560
+ );
446
561
  const returnedLayerConfig = extractLayerConfig(
447
562
  collectionId,
448
- styles,
449
- undefined,
563
+ {},
564
+ rasterForm,
450
565
  "tileUrl",
451
566
  );
452
567
  const projectionCode = getProjectionCode(wmtsLinkProjection || "EPSG:3857");
@@ -469,14 +584,6 @@ export const createLayersFromLinks = async (
469
584
  // TODO, this does not yet work between layer time changes because we do not get
470
585
  // updated variables from OL layer due to usage of tileurlfunction
471
586
 
472
- // update dimensions with current value of style variables if applicable
473
- const variables = returnedLayerConfig?.style?.variables;
474
- if (variables) {
475
- for (const [kk, vv] of Object.entries(variables)) {
476
- dimensionsWithoutStyle[kk] = vv;
477
- }
478
- }
479
-
480
587
  if (wmtsLink.href.includes("marine.copernicus")) {
481
588
  log.debug(
482
589
  "Warning: WMTS Layer from capabilities added, function needs to be updated",
@@ -517,6 +624,7 @@ export const createLayersFromLinks = async (
517
624
  id: linkId,
518
625
  title: wmtsLink.title || title || item.id,
519
626
  layerDatetime,
627
+ layerConfig: returnedLayerConfig.layerConfig,
520
628
  },
521
629
  source: {
522
630
  type: "WMTSCapabilities",
@@ -546,19 +654,16 @@ export const createLayersFromLinks = async (
546
654
  const xyzLinkProjection =
547
655
  /** @type {number | string | {name: string, def: string} | undefined} */
548
656
  (xyzLink?.["proj:epsg"] || xyzLink?.["eodash:proj4_def"]);
549
- const key = /** @type {string | undefined} */ (xyzLink["key"]) || undefined;
550
- const rasterformURL = /** @type {string|undefined} */ (
551
- collection?.["eodash:rasterform"]
657
+ const rasterForm = await fetchRasterForm(
658
+ /** @type {string|object|undefined} */ (
659
+ xyzLink?.["eodash:rasterform"] ||
660
+ item?.["eodash:rasterform"] ||
661
+ collection?.["eodash:rasterform"]
662
+ ),
552
663
  );
553
- /** @type {import("@/types").EodashRasterJSONForm|undefined} */
554
- const rasterForm = rasterformURL
555
- ? await axios.get(rasterformURL).then((resp) => resp.data)
556
- : undefined;
557
- const styles = await fetchStyle(item, key);
558
- // get the correct style which is attached to a link
559
- let { layerConfig, style } = extractLayerConfig(
664
+ let { layerConfig } = extractLayerConfig(
560
665
  collectionId,
561
- styles,
666
+ {},
562
667
  rasterForm,
563
668
  "tileUrl",
564
669
  );
@@ -571,25 +676,14 @@ export const createLayersFromLinks = async (
571
676
  viewProjectionCode,
572
677
  );
573
678
  let xyzUrl = xyzLink.href;
574
-
575
- // TODO, this does not yet work between layer time changes because we do not get
576
- // updated variables from OL layer due to usage of tileurlfunction
577
-
578
- // update url query params with current value of style variables if applicable
579
- const variables = style?.variables;
580
- if (variables) {
581
- const [base, query] = xyzUrl.split("?");
582
- const params = new URLSearchParams(query);
583
- for (const [kk, vv] of Object.entries(variables)) {
584
- params.set(kk, JSON.stringify(vv));
585
- }
586
- xyzUrl = `${base}?${params.toString()}`;
587
- }
588
-
589
679
  const { supportedUpscalingEndpoints } = useSTAcStore();
590
- const isUpscalingSupported = supportedUpscalingEndpoints.some(
591
- (/** @type {string} */ endpoint) => xyzUrl.includes(endpoint),
680
+ const upscaling = applyTitilerUpscaling(
681
+ xyzUrl,
682
+ supportedUpscalingEndpoints,
592
683
  );
684
+ if (upscaling) {
685
+ xyzUrl = upscaling.url;
686
+ }
593
687
 
594
688
  // Add sharding for s2maps automatically
595
689
  if (xyzUrl.includes("s2maps-tiles.eu")) {
@@ -608,15 +702,15 @@ export const createLayersFromLinks = async (
608
702
  },
609
703
  source: {
610
704
  type: "XYZ",
611
- url: isUpscalingSupported ? xyzUrl.replace("{y}", "{y}@2x") : xyzUrl,
705
+ url: xyzUrl,
612
706
  projection: projectionCode,
613
707
  ...(xyzLink.attribution ? { attributions: xyzLink.attribution } : {}),
614
708
  },
615
709
  };
616
- if (isUpscalingSupported) {
710
+ if (upscaling) {
617
711
  // @ts-expect-error tileGrid is added here and supported in eox-map layer definition
618
712
  json.source.tileGrid = {
619
- tileSize: [512, 512],
713
+ tileSize: upscaling.tileSize,
620
714
  };
621
715
  }
622
716
  if (
@@ -640,6 +734,96 @@ export const createLayersFromLinks = async (
640
734
  jsonArray.push(json);
641
735
  }
642
736
 
737
+ for (const tilejsonLink of tilejsonArray) {
738
+ // The tilejson href is a complete URL with the render params baked in by the
739
+ // STAC producer; fetch it and use its `tiles[0]` template as an XYZ source.
740
+ const tileJSON = await axios
741
+ .get(tilejsonLink.href)
742
+ .then((res) => res.data)
743
+ .catch((err) => {
744
+ console.error("[eodash] Failed to fetch item TileJSON", err);
745
+ return null;
746
+ });
747
+ if (!tileJSON?.tiles?.[0]) {
748
+ console.warn(
749
+ "[eodash] No tile URL in item TileJSON response",
750
+ tilejsonLink.href,
751
+ );
752
+ continue;
753
+ }
754
+ // Only raster XYZ tiles are supported; skip vector & TMS scheme TileJSON
755
+ if (tileJSON.vector_layers || tileJSON.scheme === "tms") {
756
+ console.warn(
757
+ "[eodash] Unsupported TileJSON (only raster XYZ is supported)",
758
+ tilejsonLink.href,
759
+ );
760
+ continue;
761
+ }
762
+
763
+ const tilejsonProjection =
764
+ /** @type {number | string | {name: string, def: string} | undefined} */
765
+ (tilejsonLink?.["proj:epsg"] || tilejsonLink?.["eodash:proj4_def"]);
766
+ await registerProjection(tilejsonProjection);
767
+ const projectionCode = getProjectionCode(tilejsonProjection || "EPSG:3857");
768
+ const rasterForm = await fetchRasterForm(
769
+ /** @type {string|object|undefined} */ (
770
+ tilejsonLink?.["eodash:rasterform"] ||
771
+ item?.["eodash:rasterform"] ||
772
+ collection?.["eodash:rasterform"]
773
+ ),
774
+ );
775
+ const { layerConfig } = extractLayerConfig(
776
+ collectionId,
777
+ {},
778
+ rasterForm,
779
+ "tileUrl",
780
+ );
781
+ const linkId = createLayerID(
782
+ collectionId,
783
+ item.id,
784
+ tilejsonLink,
785
+ viewProjectionCode,
786
+ );
787
+
788
+ log.debug("TileJSON layer added", linkId);
789
+ /** @type {Record<string, any>} */
790
+ const json = {
791
+ type: "Tile",
792
+ properties: {
793
+ id: linkId,
794
+ title: tilejsonLink.title || title || item.id,
795
+ roles: tilejsonLink.roles,
796
+ layerDatetime,
797
+ layerConfig,
798
+ },
799
+ source: {
800
+ type: "XYZ",
801
+ ...(tileJSON.tiles.length > 1
802
+ ? { urls: tileJSON.tiles }
803
+ : { url: tileJSON.tiles[0] }),
804
+ projection: projectionCode,
805
+ // Link attribution wins; the TileJSON document's own is the fallback.
806
+ ...(tilejsonLink.attribution || tileJSON.attribution
807
+ ? {
808
+ attributions: tilejsonLink.attribution || tileJSON.attribution,
809
+ }
810
+ : {}),
811
+ },
812
+ };
813
+ if (Number.isFinite(tileJSON.minzoom)) json.minZoom = tileJSON.minzoom;
814
+ if (Number.isFinite(tileJSON.maxzoom)) json.maxZoom = tileJSON.maxzoom;
815
+
816
+ extractRoles(json.properties, tilejsonLink);
817
+ if (extraProperties !== null) {
818
+ json.properties = {
819
+ ...json.properties,
820
+ ...extraProperties,
821
+ ...extractEoxLegendLink(tilejsonLink),
822
+ };
823
+ }
824
+ jsonArray.push(json);
825
+ }
826
+
643
827
  for (const vectorTileLink of vectorTileArray ?? []) {
644
828
  const vectorTileLinkProjection =
645
829
  /** @type {number | string | {name: string, def: string} | undefined} */
@@ -659,7 +843,7 @@ export const createLayersFromLinks = async (
659
843
  const key =
660
844
  /** @type {string | undefined} */ (vectorTileLink["key"]) || undefined;
661
845
  // fetch styles and separate them by their mapping between links and assets
662
- const styles = await fetchStyle(item, key);
846
+ const styles = await resolveStyle(item, collection, key);
663
847
  // get the correct style which is not attached to a link
664
848
  let { layerConfig, style } = extractLayerConfig(collectionId, styles);
665
849
 
@@ -745,7 +929,7 @@ export const createLayersFromLinks = async (
745
929
  ),
746
930
  applyOptions,
747
931
  );
748
- applyOptions = /** @type { object } */ (optionsObject);
932
+ applyOptions = /** @type {object} */ (optionsObject);
749
933
  href = url;
750
934
  }
751
935
  const json = {
@@ -791,61 +975,85 @@ export const createLayerFromRender = async (
791
975
  item,
792
976
  extraProperties,
793
977
  ) => {
794
- if (!collection || !collection.renders || !item) {
978
+ // config renders > collection STAC renders > item renders
979
+ const renders = /** @type {Record<string,import("@/types").Render>} */ (
980
+ resolveRenders(collection, useEodash()?.options?.renders) ?? item?.renders
981
+ );
982
+ if (!collection || !item || !renders) {
795
983
  return [];
796
984
  }
797
985
 
798
- const rasterformURL = /** @type {string|undefined} */ (
799
- collection?.["eodash:rasterform"]
800
- );
801
- /** @type {import("@/types").EodashRasterJSONForm|undefined} */
802
- const rasterForm = rasterformURL
803
- ? await axios.get(rasterformURL).then((resp) => resp.data)
804
- : undefined;
805
- let { layerConfig } = extractLayerConfig(
806
- collection.id,
807
- await fetchStyle(item),
808
- rasterForm,
986
+ // Yield to a raster xyz/tilejson link — createLayersFromLinks renders it.
987
+ const hasMatchingTileLink = item.links?.some(
988
+ (link) =>
989
+ (link.rel === "xyz" || link.rel === "tilejson") &&
990
+ link.href?.includes(rasterURL),
809
991
  );
992
+ if (hasMatchingTileLink) {
993
+ return [];
994
+ }
810
995
 
811
- const renders = /** @type {Record<string,import("@/types").Render>} */ (
812
- collection.renders ?? item?.renders
996
+ const rasterForm = await fetchRasterForm(
997
+ /** @type {string|object|undefined} */ (
998
+ item?.["eodash:rasterform"] || collection?.["eodash:rasterform"]
999
+ ),
813
1000
  );
1001
+ let { layerConfig } = extractLayerConfig(collection.id, {}, rasterForm);
1002
+
1003
+ /**
1004
+ * Resolves the first defined value of a property across a render's assets,
1005
+ * checking item assets before falling back to collection assets.
1006
+ * @param {import("@/types").Render} render
1007
+ * @param {string} propertyName
1008
+ * @returns {any}
1009
+ */
1010
+ const getRenderAssetProperty = (render, propertyName) => {
1011
+ for (const assetKey of render.assets ?? []) {
1012
+ const asset = item?.assets?.[assetKey] ?? collection?.assets?.[assetKey];
1013
+ const value = asset?.[propertyName];
1014
+ if (value !== undefined) {
1015
+ return value;
1016
+ }
1017
+ }
1018
+ return undefined;
1019
+ };
1020
+
814
1021
  const layers = [];
815
- // special case for rescale
816
1022
  for (const key in renders) {
817
1023
  const title = renders[key].title;
818
1024
 
819
- const assetsCollection =
820
- renders[key].assets[0] in item["assets"] ? item : collection;
1025
+ const expression =
1026
+ renders[key].expression ??
1027
+ getRenderAssetProperty(renders[key], "expression");
821
1028
 
822
1029
  const paramsObject = {
823
- assets: renders[key].assets,
824
- expression:
825
- renders[key].expression ??
826
- assetsCollection["assets"]?.[renders[key].assets[0]]?.expression,
827
- nodata:
828
- renders[key].nodata ??
829
- assetsCollection["assets"]?.[renders[key].assets[0]]?.nodata,
1030
+ // TiTiler treats assets and expression as mutually exclusive band selection
1031
+ assets: expression ? undefined : renders[key].assets,
1032
+ expression,
1033
+ nodata: normalizeNodata(
1034
+ renders[key].nodata ?? getRenderAssetProperty(renders[key], "nodata"),
1035
+ ),
830
1036
  resampling:
831
1037
  renders[key].resampling ??
832
- assetsCollection["assets"]?.[renders[key].assets[0]]?.resampling,
1038
+ getRenderAssetProperty(renders[key], "resampling"),
833
1039
  color_formula:
834
1040
  renders[key].color_formula ??
835
- assetsCollection["assets"]?.[renders[key].assets[0]]?.color_formula,
1041
+ getRenderAssetProperty(renders[key], "color_formula"),
836
1042
  colormap:
837
1043
  renders[key].colormap ??
838
- assetsCollection["assets"]?.[renders[key].assets[0]]?.colormap,
1044
+ getRenderAssetProperty(renders[key], "colormap"),
839
1045
  colormap_name:
840
1046
  renders[key].colormap_name ??
841
- assetsCollection["assets"]?.[renders[key].assets[0]]?.colormap_name,
842
- rescale:
843
- renders[key].rescale ??
844
- assetsCollection["assets"]?.[renders[key].assets[0]]?.rescale,
1047
+ getRenderAssetProperty(renders[key], "colormap_name"),
1048
+ rescale: normalizeRescale(
1049
+ renders[key].rescale ?? getRenderAssetProperty(renders[key], "rescale"),
1050
+ ),
1051
+ bidx: renders[key].bidx,
1052
+ tilesize: renders[key].tilesize,
845
1053
  };
846
1054
  const paramsStr = encodeURLObject(paramsObject);
847
1055
  const url = `${rasterURL}/collections/${collection.id}/items/${item.id}/tiles/WebMercatorQuad/{z}/{x}/{y}?${paramsStr}`;
848
- layers.push({
1056
+ const json = {
849
1057
  /** @type {"Tile"} */
850
1058
  type: "Tile",
851
1059
  properties: {
@@ -868,57 +1076,15 @@ export const createLayerFromRender = async (
868
1076
  url,
869
1077
  projection: "EPSG:3857",
870
1078
  },
871
- });
1079
+ };
1080
+ if (renders[key].tilesize) {
1081
+ // @ts-expect-error tileGrid is added here and supported in eox-map layer definition
1082
+ json.source.tileGrid = {
1083
+ tileSize: [renders[key].tilesize, renders[key].tilesize],
1084
+ };
1085
+ }
1086
+ layers.push(json);
872
1087
  }
873
1088
 
874
1089
  return layers;
875
1090
  };
876
- /**
877
- *
878
- * @param {Record<string,any>} obj
879
- * @returns {string}
880
- */
881
- function encodeURLObject(obj) {
882
- let str = "";
883
- for (const key in obj) {
884
- const value = obj[key];
885
- if (value === null || value === undefined || value === "") {
886
- continue;
887
- }
888
-
889
- const valueType = Array.isArray(value) ? "array" : typeof value;
890
-
891
- switch (valueType) {
892
- case "array": {
893
- // Check if any element in the array is itself an array (multi-dimensional)
894
- const hasNestedArrays = value.some((/** @type {any} */ item) =>
895
- Array.isArray(item),
896
- );
897
-
898
- if (hasNestedArrays) {
899
- // For multi-dimensional arrays, repeat the key with different values
900
- for (const val of value) {
901
- if (Array.isArray(val)) {
902
- str += `${key}=${val.join(",")}&`;
903
- } else {
904
- str += `${key}=${val}&`;
905
- }
906
- }
907
- } else {
908
- // For simple arrays, join with commas
909
- str += `${key}=${value.join(",")}&`;
910
- }
911
- break;
912
- }
913
- case "object": {
914
- str += `${key}=${encodeURI(JSON.stringify(value))}&`;
915
- break;
916
- }
917
- default: {
918
- str += `${key}=${encodeURIComponent(value)}&`;
919
- break;
920
- }
921
- }
922
- }
923
- return str;
924
- }