@eodash/eodash 5.7.1 → 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 (66) hide show
  1. package/core/client/eodashSTAC/EodashCollection.js +14 -5
  2. package/core/client/eodashSTAC/createLayers.js +193 -80
  3. package/core/client/eodashSTAC/helpers.js +330 -39
  4. package/core/client/eodashSTAC/mosaic.js +10 -4
  5. package/core/client/store/stac.js +29 -0
  6. package/core/client/types.ts +5 -0
  7. package/dist/client/{DashboardLayout-ePbdvUTE.js → DashboardLayout-DEs4f_Vy.js} +2 -2
  8. package/dist/client/{DynamicWebComponent-g5SdRbjY.js → DynamicWebComponent-PvnV88BA.js} +1 -1
  9. package/dist/client/EodashChart-D-3AGKZY.js +260 -0
  10. package/dist/client/{EodashDatePicker-CY1TrsDo.js → EodashDatePicker-Dm_goZzs.js} +3 -3
  11. package/dist/client/{EodashItemFilter-DzshHDlS.js → EodashItemFilter-CJy3zYzD.js} +2 -2
  12. package/dist/client/{EodashLayerControl-DMCeMkSd.js → EodashLayerControl-rL8UnpkA.js} +11 -19
  13. package/dist/client/{EodashLayoutSwitcher-iJ8mX1rF.js → EodashLayoutSwitcher-ZRlESI9O.js} +3 -3
  14. package/dist/client/{EodashMapBtns-C7Cr7q5Y.js → EodashMapBtns-CG7u2Aq8.js} +34 -33
  15. package/dist/client/{EodashStacInfo-K7-iuXa7.js → EodashStacInfo-CJhhxthD.js} +2 -2
  16. package/dist/client/{EodashTools-BEK7e7JM.js → EodashTools-CWyK8QJT.js} +4 -4
  17. package/dist/client/{ExportState-CqIzfMYQ.js → ExportState-BXhy5TOU.js} +19 -11
  18. package/dist/client/{Footer-1P0cmBOZ.js → Footer-DZPvnhcT.js} +1 -1
  19. package/dist/client/{Header-CkkBaBNh.js → Header-CiGsDE_a.js} +2 -2
  20. package/dist/client/{MobileLayout-BPf49kDU.js → MobileLayout-BJHW7x5V.js} +2 -2
  21. package/dist/client/{PopUp-DPZq5SWI.js → PopUp-7_XnhvCc.js} +3 -3
  22. package/dist/client/{ProcessList-DuI6WpS1.js → ProcessList-LL_UCbvc.js} +4 -4
  23. package/dist/client/{VDivider-Bj9LG_JV.js → VDivider-DWYM0WUr.js} +2 -2
  24. package/dist/client/{VImg-Ddv5OyMz.js → VImg-B75dgw2K.js} +2 -2
  25. package/dist/client/{VMain-7yqk_qC3.js → VMain-DvMmOL4f.js} +1 -1
  26. package/dist/client/{VTooltip-2hAJyEoa.js → VTooltip-ClWHUmWL.js} +2 -2
  27. package/dist/client/{WidgetsContainer-2DOrlwCB.js → WidgetsContainer-BqaWs4ch.js} +1 -1
  28. package/dist/client/{asWebComponent-BlFN2lkB.js → asWebComponent-IZanwL-A.js} +239 -91
  29. package/dist/client/{async-B2H8ZwUK.js → async-CiNIOBVz.js} +31 -29
  30. package/dist/client/{create-layers-config-u_vn5hPk.js → create-layers-config-3S3y_kZg.js} +1 -1
  31. package/dist/client/{dialog-transition-BrMkMiNa.js → dialog-transition-c4ZSnzac.js} +2 -2
  32. package/dist/client/eo-dash.js +1 -1
  33. package/dist/client/{forwardRefs-DAlror6d.js → forwardRefs-B-c1_6yo.js} +2 -2
  34. package/dist/client/{handling-B6I1V5eG.js → handling-NvY8Csry.js} +3 -4
  35. package/dist/client/{helpers-CBqIUSXJ.js → helpers-BFuh19CP.js} +328 -40
  36. package/dist/client/{index-DhKU6KqM.js → index-1wMAyuNL.js} +17 -9
  37. package/dist/client/{index-DXLz4sFR.js → index-B8jGJSTA.js} +4 -4
  38. package/dist/client/{index-DjQlbWbJ.js → index-BKQQJkhy.js} +166 -61
  39. package/dist/client/{index-BWq1ZKaV.js → index-D0jUyol8.js} +10 -11
  40. package/dist/client/{index-CJ9UI3ln.js → index-KGxz5Egv.js} +2 -2
  41. package/dist/client/{mosaic-BFYLEwyx.js → mosaic-vHvSuih6.js} +10 -6
  42. package/dist/client/templates.js +6 -7
  43. package/dist/client/{transition-D_W6Wn9v.js → transition-BkyUsZh_.js} +1 -1
  44. package/dist/types/core/client/eodashSTAC/helpers.d.ts +47 -7
  45. package/dist/types/core/client/store/stac.d.ts +8 -2
  46. package/dist/types/core/client/types.d.ts +5 -0
  47. package/dist/types/templates/explore.d.ts +1 -0
  48. package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
  49. package/package.json +5 -5
  50. package/templates/baseConfig.js +2 -1
  51. package/templates/compare.js +0 -1
  52. package/templates/expert.js +2 -3
  53. package/templates/explore.js +1 -0
  54. package/templates/lite.js +0 -1
  55. package/widgets/EodashChart.vue +161 -21
  56. package/widgets/EodashItemCatalog/methods/map.js +0 -1
  57. package/widgets/EodashLayerControl.vue +10 -15
  58. package/widgets/EodashMap/index.vue +3 -1
  59. package/widgets/EodashMap/methods/btns.js +27 -26
  60. package/widgets/EodashMap/methods/use-map-loading.js +8 -2
  61. package/widgets/EodashProcess/index.vue +135 -16
  62. package/widgets/EodashProcess/methods/composables.js +13 -0
  63. package/widgets/EodashProcess/methods/outputs.js +0 -1
  64. package/widgets/EodashProcess/methods/utils.js +29 -27
  65. package/widgets/ExportState.vue +13 -4
  66. package/dist/client/EodashChart-ChrkA5oS.js +0 -145
@@ -1,6 +1,8 @@
1
1
  import { toAbsolute } from "stac-js/src/http.js";
2
2
  import axios from "@/plugins/axios";
3
3
  import log from "loglevel";
4
+ import mustache from "mustache";
5
+ import { updateVectorLayerStyle } from "@eox/layercontrol";
4
6
  import { getStyleVariablesState } from "./triggers.js";
5
7
  import { itemsCache, splitItemsCache } from "@/utils/states.js";
6
8
 
@@ -42,6 +44,26 @@ export function generateFeatures(links, extraProperties = {}, rel = "item") {
42
44
  return geojsonObject;
43
45
  }
44
46
 
47
+ /**
48
+ * Fetches or extracts the raster form configuration for a STAC object.
49
+ * Supports direct JSON objects, data URIs, and URL strings.
50
+ *
51
+ * @param {string|object|undefined} rasterform - The rasterform property from the STAC object.
52
+ * @returns {Promise<import("@/types").EodashRasterJSONForm|undefined>}
53
+ */
54
+ export async function fetchRasterForm(rasterform) {
55
+ if (!rasterform) {
56
+ return undefined;
57
+ }
58
+ if (typeof rasterform === "object") {
59
+ return /** @type {import("@/types").EodashRasterJSONForm} */ (rasterform);
60
+ }
61
+ if (typeof rasterform === "string") {
62
+ return await axios.get(rasterform).then((resp) => resp.data);
63
+ }
64
+ return undefined;
65
+ }
66
+
45
67
  /**
46
68
  * Spearates and extracts layerConfig (jsonform schema & legend) from a style json
47
69
  *
@@ -98,6 +120,39 @@ export function extractLayerConfig(
98
120
  return { layerConfig, style };
99
121
  }
100
122
 
123
+ /**
124
+ * Recursively extracts URL keys from a JSON Schema.
125
+ * Maps schema property names to their defined `url_key`.
126
+ * @param {Record<string, any> | null | undefined} schema
127
+ * @returns {Record<string, string>}
128
+ */
129
+ export function extractUrlKeys(schema) {
130
+ /** @type {Record<string, string>} */
131
+ const keys = {};
132
+ if (!schema || typeof schema !== "object") return keys;
133
+
134
+ if (schema.properties) {
135
+ for (const [key, propDef] of Object.entries(schema.properties)) {
136
+ if (propDef && typeof propDef === "object") {
137
+ if (typeof propDef.url_key === "string") {
138
+ keys[key] = propDef.url_key;
139
+ }
140
+ Object.assign(keys, extractUrlKeys(propDef));
141
+ }
142
+ }
143
+ }
144
+
145
+ for (const combinator of ["oneOf", "allOf", "anyOf"]) {
146
+ if (Array.isArray(schema[combinator])) {
147
+ for (const sub of schema[combinator]) {
148
+ Object.assign(keys, extractUrlKeys(sub));
149
+ }
150
+ }
151
+ }
152
+
153
+ return keys;
154
+ }
155
+
101
156
  /**
102
157
  *
103
158
  * @param {number[]} bbox
@@ -238,6 +293,7 @@ export const fetchAllStyles = async (stacObject) => {
238
293
  link.rel.includes("style"),
239
294
  );
240
295
  const fetchPromises = styleLinks.map(async (link) => {
296
+ /** @type {import("@/types").EodashStyleJson} */
241
297
  const styleJson = await axios.get(link.href).then((resp) => resp.data);
242
298
  log.debug("fetched styles JSON", JSON.parse(JSON.stringify(styleJson)));
243
299
  return styleJson;
@@ -560,20 +616,150 @@ export function assignProjID(item, linkOrAsset, id, layer) {
560
616
  *
561
617
  * @param {Record<string,any>[]} layers
562
618
  */
563
- export const removeUnneededProperties = (layers) => {
564
- const cloned = structuredClone(layers);
565
- cloned.forEach((layer) => {
566
- const id = layer.properties.id;
567
- const title = layer.properties.title;
568
- layer.properties = { id, title };
569
- if (layer["interactions"]) {
570
- delete layer["interactions"];
619
+ export const removeUnneededProperties = (layers, formValues = {}) => {
620
+ /**
621
+ * @param {Record<string,any>} layer
622
+ * @returns {Record<string,any>[]}
623
+ */
624
+ const processLayer = (layer) => {
625
+ // If the layer (or group) is explicitly marked as not visible, skip it and all children
626
+ if (layer.properties?.visible === false) {
627
+ return [];
571
628
  }
572
- if (layer.type === "Group") {
573
- layer.layers = removeUnneededProperties(layer.layers);
629
+
630
+ // If it's a Group, we just want its children
631
+ if (layer.type === "Group" && Array.isArray(layer.layers)) {
632
+ return layer.layers.flatMap(processLayer);
574
633
  }
575
- });
576
- return cloned;
634
+
635
+ // Break any Vue Proxies/OpenLayers getters by stringifying first
636
+ let clonedLayer;
637
+ try {
638
+ clonedLayer = JSON.parse(JSON.stringify(layer));
639
+ } catch (_e) {
640
+ clonedLayer = structuredClone(layer);
641
+ }
642
+
643
+ // Flatten formValues to handle nested properties (e.g., vminmax: { vmin, vmax })
644
+ /**
645
+ * @param {Record<string,any>} obj
646
+ * @returns {Record<string,any>}
647
+ */
648
+ const flattenFormValues = (obj) => {
649
+ /** @type {Record<string,any>} */
650
+ let result = {};
651
+ for (const key in obj) {
652
+ if (
653
+ obj[key] !== null &&
654
+ typeof obj[key] === "object" &&
655
+ !Array.isArray(obj[key])
656
+ ) {
657
+ Object.assign(result, flattenFormValues(obj[key]));
658
+ } else {
659
+ result[key] = obj[key];
660
+ }
661
+ }
662
+ return result;
663
+ };
664
+ const flatFormValues = flattenFormValues(formValues);
665
+
666
+ // Burn in style variables using Mustache if formValues are provided
667
+ if (Object.keys(flatFormValues).length > 0) {
668
+ // Stringify, render mustache, then parse back
669
+ try {
670
+ const renderedString = mustache.render(
671
+ JSON.stringify(clonedLayer),
672
+ flatFormValues,
673
+ );
674
+ clonedLayer = JSON.parse(renderedString);
675
+ } catch (e) {
676
+ log.warn(
677
+ "[eodash] Failed to apply mustache templating during export cleanup:",
678
+ e,
679
+ );
680
+ }
681
+ }
682
+
683
+ const {
684
+ id,
685
+ title,
686
+ mapboxStyle,
687
+ projection,
688
+ applyOptions,
689
+ layerConfig,
690
+ visible,
691
+ } = clonedLayer.properties || {};
692
+
693
+ // If style was not at root but in properties (layerConfig), move it to root early
694
+ if (!clonedLayer.style && layerConfig?.style) {
695
+ clonedLayer.style = layerConfig.style;
696
+ }
697
+
698
+ // Burn in OpenLayers ["var", "name"] variables using flatFormValues overriding style.variables
699
+ const styleVariables = {
700
+ ...(clonedLayer.style?.variables || {}),
701
+ ...flatFormValues,
702
+ };
703
+
704
+ if (clonedLayer.style && Object.keys(styleVariables).length > 0) {
705
+ clonedLayer.style = updateVectorLayerStyle({
706
+ ...clonedLayer.style,
707
+ variables: styleVariables,
708
+ });
709
+ }
710
+
711
+ clonedLayer.properties = {
712
+ id,
713
+ title,
714
+ ...(mapboxStyle && { mapboxStyle }),
715
+ ...(projection && { projection }),
716
+ ...(applyOptions && { applyOptions }),
717
+ ...(visible !== undefined && { visible }),
718
+ };
719
+
720
+ if (clonedLayer["interactions"]) {
721
+ delete clonedLayer["interactions"];
722
+ }
723
+
724
+ // Cleanup unnecessary properties
725
+ /**
726
+ * @param {any} obj
727
+ */
728
+ const cleanupProperties = (obj) => {
729
+ if (!obj || typeof obj !== "object") return;
730
+
731
+ for (const key in obj) {
732
+ if (obj[key] === null) {
733
+ delete obj[key];
734
+ } else if (Array.isArray(obj[key])) {
735
+ if (obj[key].length === 0) {
736
+ // Remove empty arrays like empty interactions
737
+ delete obj[key];
738
+ } else {
739
+ obj[key].forEach(cleanupProperties);
740
+ }
741
+ } else if (typeof obj[key] === "object") {
742
+ cleanupProperties(obj[key]);
743
+ if (Object.keys(obj[key]).length === 0) {
744
+ // we don't delete empty objects for now as it might break schema requirements
745
+ }
746
+ }
747
+ }
748
+ };
749
+ cleanupProperties(clonedLayer);
750
+
751
+ return [clonedLayer];
752
+ };
753
+
754
+ // ensure the top level array is also deeply un-proxied if needed
755
+ let rawLayers = layers;
756
+ try {
757
+ rawLayers = JSON.parse(JSON.stringify(layers));
758
+ } catch (_e) {
759
+ rawLayers = structuredClone(layers);
760
+ }
761
+
762
+ return rawLayers.flatMap(processLayer);
577
763
  };
578
764
 
579
765
  /**
@@ -764,6 +950,7 @@ export function getDatetimeProperty(linksOrItems) {
764
950
  return prop;
765
951
  }
766
952
  }
953
+
767
954
  /**
768
955
  *
769
956
  * @param {*} stacObject
@@ -1064,6 +1251,91 @@ export function updateGeoZarrBands(olLayer, jsonformValue) {
1064
1251
  return true;
1065
1252
  }
1066
1253
 
1254
+ /**
1255
+ * Safely appends query parameters to a URL string, preserving templates like {z}/{x}/{y}
1256
+ * @param {string} url
1257
+ * @param {Record<string, string>} params
1258
+ * @returns {string}
1259
+ */
1260
+ function appendQueryParams(url, params) {
1261
+ const [base, query] = url.split("?");
1262
+ const searchParams = new URLSearchParams(query || "");
1263
+
1264
+ for (const [key, val] of Object.entries(params)) {
1265
+ if (val !== undefined && val !== null && val !== "") {
1266
+ searchParams.set(key, val);
1267
+ } else {
1268
+ searchParams.delete(key);
1269
+ }
1270
+ }
1271
+
1272
+ const newQuery = searchParams.toString();
1273
+ return newQuery ? `${base}?${newQuery}` : base;
1274
+ }
1275
+
1276
+ /**
1277
+ * Checks whether a VectorTile layer's URL needs to be updated based on jsonform output.
1278
+ * If the style's jsonform schema has properties with `url_key` defined, their values
1279
+ * are injected as query parameters into the source URL.
1280
+ *
1281
+ * @param {import("ol/layer/Layer").default} olLayer - Layer from layerConfig:change event
1282
+ * @param {Record<string, any>} jsonformValue - Current jsonform output
1283
+ * @returns {boolean} true if the URL was updated
1284
+ */
1285
+ export function updateLayerUrl(olLayer, jsonformValue) {
1286
+ const jsonLayer = olLayer.get("_jsonDefinition");
1287
+ if (!jsonLayer || jsonLayer.type !== "VectorTile") {
1288
+ return false;
1289
+ }
1290
+
1291
+ const schema = jsonLayer.properties?.layerConfig?.schema;
1292
+ const urlKeys = extractUrlKeys(schema);
1293
+
1294
+ if (Object.keys(urlKeys).length === 0) {
1295
+ return false;
1296
+ }
1297
+
1298
+ let originalUrl = olLayer.get("originalUrl") || jsonLayer.source?.url;
1299
+
1300
+ if (!originalUrl || typeof originalUrl !== "string") {
1301
+ return false;
1302
+ }
1303
+
1304
+ if (!olLayer.get("originalUrl")) {
1305
+ olLayer.set("originalUrl", originalUrl);
1306
+ }
1307
+
1308
+ /** @type {Record<string, string>} */
1309
+ const queryParamsToInject = {};
1310
+ for (const [propName, urlKey] of Object.entries(urlKeys)) {
1311
+ queryParamsToInject[urlKey] = jsonformValue[propName];
1312
+ }
1313
+
1314
+ const newUrl = appendQueryParams(originalUrl, queryParamsToInject);
1315
+
1316
+ if (jsonLayer.source?.url) {
1317
+ if (jsonLayer.source.url === newUrl) {
1318
+ return false;
1319
+ }
1320
+ jsonLayer.source.url = newUrl;
1321
+ if (olLayer.get("injectedUrl") === newUrl) {
1322
+ return false;
1323
+ }
1324
+ const source = olLayer.getSource();
1325
+ olLayer.set("injectedUrl", newUrl);
1326
+ if (source && "setUrl" in source) {
1327
+ /** @type {any} */ (source).setUrl(newUrl);
1328
+ return true;
1329
+ }
1330
+ if (source && "setUrls" in source) {
1331
+ /** @type {any} */ (source).setUrls([newUrl]);
1332
+ return true;
1333
+ }
1334
+ }
1335
+
1336
+ return false;
1337
+ }
1338
+
1067
1339
  /**
1068
1340
  * Applies titiler upscaling to an XYZ tile URL based on the matched endpoint config.
1069
1341
  * - titiler v1: appends `@2x` to the `{y}` tile coordinate
@@ -1097,26 +1369,56 @@ export function applyTitilerUpscaling(url, upscalingEndpoints) {
1097
1369
  }
1098
1370
 
1099
1371
  /**
1100
- * Picks the render presets for a collection, preferring the collection's own
1101
- * STAC `renders` extension and falling back to client-provided config
1102
- *
1372
+ * Picks the render presets for a collection, preferring client-provided config
1373
+ * (`options.renders[collectionId]`) and falling back to the collection's own STAC
1374
+ * `renders` extension when no config entry exists.
1103
1375
  * @param {import("stac-ts").StacCollection | null | undefined} collection
1104
1376
  * @param {Record<string, Record<string, import("@/types").Render>> | undefined} [configRenders]
1105
1377
  * @returns {Record<string, import("@/types").Render> | undefined}
1106
1378
  */
1107
1379
  export function resolveRenders(collection, configRenders) {
1108
- if (collection?.renders) {
1109
- return /** @type {Record<string, import("@/types").Render>} */ (
1110
- collection.renders
1111
- );
1380
+ const config = collection?.id ? configRenders?.[collection.id] : undefined;
1381
+ if (config) return config;
1382
+ return /** @type {Record<string, import("@/types").Render>|undefined} */ (
1383
+ collection?.renders ?? undefined
1384
+ );
1385
+ }
1386
+
1387
+ /**
1388
+ * TiTiler expects rescale as [min,max] pairs; chunks a flat numeric list
1389
+ * into pairs (e.g. [0,0.4,0,0.1] -> [[0,0.4],[0,0.1]]). Nested input passes through.
1390
+ * @param {number[]|number[][]|undefined} rescale - flat or nested rescale values
1391
+ * @returns {number[][]|undefined} rescale as [min,max] pairs
1392
+ */
1393
+ export function normalizeRescale(rescale) {
1394
+ if (!rescale?.length || Array.isArray(rescale[0])) {
1395
+ return /** @type {number[][]|undefined} */ (rescale);
1112
1396
  }
1113
- return collection?.id ? configRenders?.[collection.id] : undefined;
1397
+ const pairs = [];
1398
+ for (let i = 0; i < rescale.length; i += 2) {
1399
+ pairs.push(/** @type {number[]} */ (rescale).slice(i, i + 2));
1400
+ }
1401
+ return pairs;
1114
1402
  }
1115
1403
 
1116
1404
  /**
1117
- * Serializes an object into a TiTiler query string. Arrays join with commas;
1118
- * nested arrays repeat the key (e.g. `rescale: [[0,1]]` -> `rescale=0,1`);
1119
- * objects are JSON-encoded. Shared by the render-extension and mosaic paths.
1405
+ * Drops NaN nodata values; NaN is already the implicit fill for float data,
1406
+ * so forwarding `nodata=nan` to TiTiler is redundant.
1407
+ * @param {string|number|undefined} nodata - nodata value from render/asset metadata
1408
+ * @returns {string|number|undefined} nodata, or undefined when it is NaN
1409
+ */
1410
+ export function normalizeNodata(nodata) {
1411
+ if (typeof nodata === "number" && Number.isNaN(nodata)) return undefined;
1412
+ if (typeof nodata === "string" && nodata.trim().toLowerCase() === "nan")
1413
+ return undefined;
1414
+ return nodata;
1415
+ }
1416
+
1417
+ /**
1418
+ * Serializes an object into a TiTiler query string. Arrays repeat the key per
1419
+ * element (TiTiler list params, e.g. `assets=a&assets=b`); nested elements
1420
+ * comma-join (`rescale: [[0,1],[0,2]]` -> `rescale=0,1&rescale=0,2`); objects
1421
+ * are JSON-encoded. Shared by the render-extension and mosaic paths.
1120
1422
  * @param {Record<string,any>} obj
1121
1423
  * @returns {string}
1122
1424
  */
@@ -1132,23 +1434,12 @@ export function encodeURLObject(obj) {
1132
1434
 
1133
1435
  switch (valueType) {
1134
1436
  case "array": {
1135
- // Check if any element in the array is itself an array (multi-dimensional)
1136
- const hasNestedArrays = value.some((/** @type {any} */ item) =>
1137
- Array.isArray(item),
1138
- );
1139
-
1140
- if (hasNestedArrays) {
1141
- // For multi-dimensional arrays, repeat the key with different values
1142
- for (const val of value) {
1143
- if (Array.isArray(val)) {
1144
- str += `${key}=${val.join(",")}&`;
1145
- } else {
1146
- str += `${key}=${val}&`;
1147
- }
1437
+ for (const val of value) {
1438
+ if (Array.isArray(val)) {
1439
+ str += `${key}=${val.join(",")}&`;
1440
+ } else {
1441
+ str += `${key}=${encodeURIComponent(val)}&`;
1148
1442
  }
1149
- } else {
1150
- // For simple arrays, join with commas
1151
- str += `${key}=${value.join(",")}&`;
1152
1443
  }
1153
1444
  break;
1154
1445
  }
@@ -25,6 +25,8 @@ import { useEodash, useOnLayersUpdate } from "@/composables";
25
25
  import {
26
26
  encodeURLObject,
27
27
  extractLayerTimeValues,
28
+ normalizeNodata,
29
+ normalizeRescale,
28
30
  resolveRenders,
29
31
  } from "./helpers";
30
32
  import { useSTAcStore } from "@/store/stac";
@@ -248,17 +250,21 @@ async function createMosaicLayers(mosaicEndpoint, params) {
248
250
  }
249
251
 
250
252
  const renderParamsStr = encodeURLObject({
251
- assets: preset.assets,
253
+ // TiTiler treats assets and expression as mutually exclusive band selection
254
+ assets: preset.expression ? undefined : preset.assets,
252
255
  expression: preset.expression,
253
- nodata: preset.nodata,
256
+ nodata: normalizeNodata(preset.nodata),
254
257
  resampling: preset.resampling,
255
258
  color_formula: preset.color_formula,
256
259
  colormap: preset.colormap,
257
260
  colormap_name: preset.colormap_name,
258
- rescale: preset.rescale,
261
+ rescale: normalizeRescale(preset.rescale),
259
262
  });
260
263
 
261
- const tileParams = new URLSearchParams({ tilesize: "512", ...params });
264
+ const tileParams = new URLSearchParams({
265
+ tilesize: `${preset.tilesize ?? "512"}`,
266
+ ...params,
267
+ });
262
268
  const tileJsonUrl = `${mosaicEndpoint}?${renderParamsStr}${tileParams.toString()}`;
263
269
 
264
270
  const tileJSON = await axios
@@ -35,6 +35,12 @@ export const useSTAcStore = defineStore("stac", () => {
35
35
  */
36
36
  const supportedUpscalingEndpoints = ref([]);
37
37
 
38
+ /**
39
+ * Registry of colormap ranges
40
+ * @type {import("vue").Ref<Record<string, string[]> | null>}
41
+ */
42
+ const colormapRegistry = ref(null);
43
+
38
44
  /**
39
45
  * Links of the root STAC catalog
40
46
  *
@@ -94,6 +100,26 @@ export const useSTAcStore = defineStore("stac", () => {
94
100
  rasterEndpoint.value = endpoint.rasterEndpoint ?? null;
95
101
  supportedUpscalingEndpoints.value =
96
102
  endpoint.supportedUpscalingEndpoints ?? [];
103
+ if (endpoint.colormapRegistry) {
104
+ loadColormapRegistry(endpoint.colormapRegistry);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Loads the colormap registry from a URL or object
110
+ * @param {string | Record<string, string[]>} registry
111
+ */
112
+ async function loadColormapRegistry(registry) {
113
+ if (typeof registry === "object") {
114
+ colormapRegistry.value = registry;
115
+ return;
116
+ }
117
+ try {
118
+ const resp = await axios.get(registry);
119
+ colormapRegistry.value = resp.data;
120
+ } catch (err) {
121
+ log.error("Error loading colormap registry", err);
122
+ }
97
123
  }
98
124
 
99
125
  /**
@@ -167,6 +193,7 @@ export const useSTAcStore = defineStore("stac", () => {
167
193
  await axios
168
194
  .get(absoluteUrl.value)
169
195
  .then(async (resp) => {
196
+ resp.data["eodash:layerExclusive"] = true;
170
197
  await updateEodashCollections(
171
198
  eodashCollections,
172
199
  resp.data,
@@ -279,5 +306,7 @@ export const useSTAcStore = defineStore("stac", () => {
279
306
  selectedItem,
280
307
  selectedCompareItem,
281
308
  supportedUpscalingEndpoints,
309
+ colormapRegistry,
310
+ loadColormapRegistry,
282
311
  };
283
312
  });
@@ -339,6 +339,7 @@ export type StacEndpoint =
339
339
  supportedUpscalingEndpoints?: Array<
340
340
  string | { url: string; titilerVersion?: 1 | 2 }
341
341
  >;
342
+ colormapRegistry?: string | Record<string, string[]>;
342
343
  };
343
344
 
344
345
  /** @group Eodash */
@@ -647,6 +648,10 @@ export interface Render {
647
648
  expression?: string;
648
649
  /** Zoom levels range applicable for the visualization */
649
650
  minmax_zoom?: number[];
651
+ /** Band indexes to apply the rendering to. */
652
+ bidx?: number[];
653
+ /** Tile size to request from the tile server. */
654
+ tilesize?: number;
650
655
  }
651
656
  /** @ignore */
652
657
  export interface TitilerSTACParameters {
@@ -1,7 +1,7 @@
1
1
  import { openBlock, createBlock, withCtx, createElementVNode, unref, createElementBlock, normalizeStyle, Suspense, resolveDynamicComponent, mergeProps, createCommentVNode, Fragment, renderList, Transition } from 'vue';
2
2
  import '@eox/layout';
3
- import { _ as _export_sfc, u as useDefineTemplate } from './asWebComponent-BlFN2lkB.js';
4
- import { V as VMain } from './VMain-7yqk_qC3.js';
3
+ import { _ as _export_sfc, u as useDefineTemplate } from './asWebComponent-IZanwL-A.js';
4
+ import { V as VMain } from './VMain-DvMmOL4f.js';
5
5
 
6
6
  const _style_0 = ".panel[data-v-f357e3a7]{position:relative;overflow:auto;z-index:1;pointer-events:none}.pointer[data-v-f357e3a7]{pointer-events:all}.bg-panel[data-v-f357e3a7]{z-index:0;border-radius:0!important}.fade-enter-active[data-v-f357e3a7],.fade-leave-active[data-v-f357e3a7]{transition:opacity .25s ease}.fade-enter-from[data-v-f357e3a7],.fade-leave-to[data-v-f357e3a7]{opacity:0}.bg-surface[data-v-f357e3a7],.bg-primary[data-v-f357e3a7]{backdrop-filter:blur(10px)!important;border-radius:8px;border:none;box-shadow:0 0 1px #18274b38,0 6px 12px -6px #18274b1f,0 8px 24px -4px #18274b14;max-height:100%;overflow:auto;scrollbar-color:rgba(var(--v-theme-on-surface),.2) transparent;scrollbar-width:thin}.bg-surface[data-v-f357e3a7]{background-color:rgba(var(--v-theme-surface),var(--v-surface-opacity, .8))!important}.bg-primary[data-v-f357e3a7]{background-color:rgba(var(--v-theme-primary),var(--v-primary-opacity, .8))!important}";
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { withAsyncContext, ref, onMounted, onUnmounted, openBlock, createElementBlock, createBlock, resolveDynamicComponent, mergeProps } from 'vue';
2
- import { H as useSTAcStore } from './asWebComponent-BlFN2lkB.js';
2
+ import { H as useSTAcStore } from './asWebComponent-IZanwL-A.js';
3
3
 
4
4
  const _hoisted_1 = { class: "d-flex flex-column fill-height overflow-auto" };
5
5