@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,5 +1,5 @@
1
- import { y as replaceLayer, d as extractLayerConfig, C as extractLayerLegend, m as mergeGeojsons, f as axios, ab as getCompareLayers, B as getLayers, ae as axios$1, Q as compareIndicator, M as indicator } from './helpers-CBqIUSXJ.js';
2
- import { aa as useEmitLayersUpdate } from './asWebComponent-BlFN2lkB.js';
1
+ import { C as replaceLayer, e as extractLayerConfig, G as extractLayerLegend, m as mergeGeojsons, g as axios, ag as getCompareLayers, F as getLayers, aj as axios$1, U as compareIndicator, Q as indicator } from './helpers-BFuh19CP.js';
2
+ import { aa as useEmitLayersUpdate } from './asWebComponent-IZanwL-A.js';
3
3
  import { isMulti } from '@eox/jsonform/src/custom-inputs/spatial/utils';
4
4
  import log from 'loglevel';
5
5
 
@@ -83,10 +83,10 @@ async function createTiffLayerDefinition(
83
83
  }
84
84
  // We want to make sure the urls are alphabetically sorted
85
85
  urls = urls.sort();
86
- /** @type {import("@eox/map/src/layers").EOxLayerType<"WebGLTile","GeoTIFF"> | undefined} */
86
+
87
87
  const layerdef =
88
88
  urls.length > 0
89
- ? {
89
+ ? /** @type {import("@eox/map/src/layers").EOxLayerType<"WebGLTile","GeoTIFF">} */ ({
90
90
  type: "WebGLTile",
91
91
  source: {
92
92
  type: "GeoTIFF",
@@ -100,7 +100,7 @@ async function createTiffLayerDefinition(
100
100
  layerControlToolsExpand: true,
101
101
  },
102
102
  ...(style && { style: style }),
103
- }
103
+ })
104
104
  : undefined;
105
105
 
106
106
  // We want to see if the currently selected indicator uses a
@@ -303,31 +303,33 @@ async function creatAsyncProcessLayerDefinitions(
303
303
 
304
304
  switch (resultItem.type) {
305
305
  case "image/tiff": {
306
- layers.push({
307
- type: "WebGLTile",
308
- properties: {
309
- id: endpointLink.id + "_process" + resultItem.id + postfixId,
310
- title:
311
- "Results " +
312
- (selectedStac?.id ?? "") +
313
- " " +
314
- (resultItem.id ?? ""),
315
- layerControlToolsExpand: true,
316
- ...(layerConfig && { layerConfig }),
317
- ...extraProperties,
318
- },
319
- source: {
320
- type: "GeoTIFF",
321
- normalize: !style,
322
- sources: resultItem.urls.map((url) => ({ url })),
323
- //@ts-expect-error TODO
324
- ...(selectedStac["eodash:mapProjection"]?.["name"] && {
306
+ layers.push(
307
+ /** @type {import("@eox/map/src/layers").EOxLayerType<"WebGLTile","GeoTIFF">} */ ({
308
+ type: "WebGLTile",
309
+ properties: {
310
+ id: endpointLink.id + "_process" + resultItem.id + postfixId,
311
+ title:
312
+ "Results " +
313
+ (selectedStac?.id ?? "") +
314
+ " " +
315
+ (resultItem.id ?? ""),
316
+ layerControlToolsExpand: true,
317
+ ...(layerConfig && { layerConfig }),
318
+ ...extraProperties,
319
+ },
320
+ source: {
321
+ type: "GeoTIFF",
322
+ normalize: !style,
323
+ sources: resultItem.urls.map((url) => ({ url })),
325
324
  //@ts-expect-error TODO
326
- projection: selectedStac["eodash:mapProjection"]["name"],
327
- }),
328
- },
329
- ...(style && { style }),
330
- });
325
+ ...(selectedStac["eodash:mapProjection"]?.["name"] && {
326
+ //@ts-expect-error TODO
327
+ projection: selectedStac["eodash:mapProjection"]["name"],
328
+ }),
329
+ },
330
+ ...(style && { style }),
331
+ }),
332
+ );
331
333
  break;
332
334
  }
333
335
  case "application/geo+json": {
@@ -1,4 +1,4 @@
1
- import { ah as EodashCollection } from './asWebComponent-BlFN2lkB.js';
1
+ import { ah as EodashCollection } from './asWebComponent-IZanwL-A.js';
2
2
  import log from 'loglevel';
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { createVNode, Transition, mergeProps } from 'vue';
2
- import { g as genericComponent, ar as PREFERS_REDUCED_MOTION, p as propsFactory, at as getTargetBox, au as acceleratedEasing, av as deceleratedEasing, aw as standardEasing } from './asWebComponent-BlFN2lkB.js';
3
- import { b as animate, n as nullifyTransforms } from './forwardRefs-DAlror6d.js';
2
+ import { g as genericComponent, ar as PREFERS_REDUCED_MOTION, p as propsFactory, at as getTargetBox, au as acceleratedEasing, av as deceleratedEasing, aw as standardEasing } from './asWebComponent-IZanwL-A.js';
3
+ import { b as animate, n as nullifyTransforms } from './forwardRefs-B-c1_6yo.js';
4
4
 
5
5
  // Utilities
6
6
  const makeVDialogTransitionProps = propsFactory({
@@ -1,2 +1,2 @@
1
- export { aR as EodashConstructor, aS as register, aT as store } from './asWebComponent-BlFN2lkB.js';
1
+ export { aR as EodashConstructor, aS as register, aT as store } from './asWebComponent-IZanwL-A.js';
2
2
  import 'vue';
@@ -1,6 +1,6 @@
1
1
  import { computed, watch, onScopeDispose, nextTick, ref, watchEffect, effectScope, inject, mergeProps, toRef, toValue, shallowRef, onMounted, reactive, provide, toRaw, readonly, warn, onBeforeUnmount, createElementVNode, Fragment, createVNode, Teleport, withDirectives, vShow, Transition } from 'vue';
2
- import { ax as Box, ay as isOn, az as eventName, p as propsFactory, aA as destructComputed, aB as parseAnchor, aC as flipSide, aD as flipAlign, aE as flipCorner, aF as CircularBuffer, aG as deepEqual, at as getTargetBox, aH as getElementBox, O as consoleError, aI as getAxis, q as convertToUnit, aJ as getOverflow, x as clamp, aK as IN_BROWSER, A as useToggleScope, aL as defer, N as getCurrentInstance, aM as templateRef, aN as matchesSelector, a8 as focusableChildren, G as useDisplay, r as makeThemeProps, d as makeDimensionProps, e as makeComponentProps, g as genericComponent, z as useProxiedModel, l as provideTheme, n as useRtl, f as useBackgroundColor, b as useDimension, aO as useRouter, aP as useBackButton, aw as standardEasing, c as useRender, C as omit } from './asWebComponent-BlFN2lkB.js';
3
- import { m as makeTransitionProps, M as MaybeTransition } from './transition-D_W6Wn9v.js';
2
+ import { ax as Box, ay as isOn, az as eventName, p as propsFactory, aA as destructComputed, aB as parseAnchor, aC as flipSide, aD as flipAlign, aE as flipCorner, aF as CircularBuffer, aG as deepEqual, at as getTargetBox, aH as getElementBox, O as consoleError, aI as getAxis, q as convertToUnit, aJ as getOverflow, x as clamp, aK as IN_BROWSER, A as useToggleScope, aL as defer, N as getCurrentInstance, aM as templateRef, aN as matchesSelector, a8 as focusableChildren, G as useDisplay, r as makeThemeProps, d as makeDimensionProps, e as makeComponentProps, g as genericComponent, z as useProxiedModel, l as provideTheme, n as useRtl, f as useBackgroundColor, b as useDimension, aO as useRouter, aP as useBackButton, aw as standardEasing, c as useRender, C as omit } from './asWebComponent-IZanwL-A.js';
3
+ import { m as makeTransitionProps, M as MaybeTransition } from './transition-BkyUsZh_.js';
4
4
 
5
5
  // Utilities
6
6
  /** @see https://stackoverflow.com/a/57876601/2074736 */
@@ -1,8 +1,8 @@
1
1
  import log from 'loglevel';
2
- import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as extractGeometries, i as applyProcessLayersToMap, g as getDrawToolsProperties } from './async-B2H8ZwUK.js';
3
- import { f as axios, L as isFirstLoad, d as extractLayerConfig, Q as compareIndicator, M as indicator, D as extractCollectionUrls, O as currentCompareUrl, N as currentUrl, E as generateLinksFromItems, t as getDatetimeProperty, R as comparePoi, H as datetime, ac as compareChartData, ad as chartData, X as compareChartSpec, W as chartSpec, I as poi, ab as getCompareLayers, B as getLayers } from './helpers-CBqIUSXJ.js';
2
+ import { s as separateEndpointLinks, c as createTiffLayerDefinition, p as pollProcessStatus, e as extractAsyncResults, u as updateJobsStatus, a as creatAsyncProcessLayerDefinitions, b as getBboxProperty, f as generateTimePairs, h as extractGeometries, i as applyProcessLayersToMap, g as getDrawToolsProperties } from './async-CiNIOBVz.js';
3
+ import { g as axios, P as isFirstLoad, e as extractLayerConfig, U as compareIndicator, Q as indicator, H as extractCollectionUrls, S as currentCompareUrl, R as currentUrl, I as generateLinksFromItems, x as getDatetimeProperty, V as comparePoi, L as datetime, ah as compareChartData, ai as chartData, $ as compareChartSpec, _ as chartSpec, M as poi, ag as getCompareLayers, F as getLayers } from './helpers-BFuh19CP.js';
4
4
  import mustache from 'mustache';
5
- import { H as useSTAcStore, ae as readParquetItems, af as useGetSubCodeId } from './asWebComponent-BlFN2lkB.js';
5
+ import { H as useSTAcStore, ae as readParquetItems, af as useGetSubCodeId } from './asWebComponent-IZanwL-A.js';
6
6
  import { toAbsolute } from 'stac-js/src/http.js';
7
7
 
8
8
  /**
@@ -643,7 +643,6 @@ async function processVector(links, jsonformValue, layerId) {
643
643
  type: "Vector",
644
644
  source: {
645
645
  type: "Vector",
646
- //@ts-expect-error TODO
647
646
  url: mustache.render(link.href, {
648
647
  ...(jsonformValue ?? {}),
649
648
  }),
@@ -2,6 +2,8 @@ import { toAbsolute } from 'stac-js/src/http.js';
2
2
  import { setupCache } from 'axios-cache-interceptor';
3
3
  import log from 'loglevel';
4
4
  import { ref, shallowRef, reactive, shallowReactive } from 'vue';
5
+ import mustache from 'mustache';
6
+ import { updateVectorLayerStyle } from '@eox/layercontrol';
5
7
  import { mdiWrench, mdiSnowflake, mdiVolleyball, mdiHiking, mdiLightningBolt, mdiLeaf, mdiAccountMultiple, mdiCurrencyEur, mdiWeatherWindy, mdiSetCenter, mdiHospitalBoxOutline, mdiImageFilterHdr, mdiWater, mdiBarley } from '@mdi/js';
6
8
 
7
9
  /**
@@ -5363,6 +5365,26 @@ function generateFeatures(links, extraProperties = {}, rel = "item") {
5363
5365
  return geojsonObject;
5364
5366
  }
5365
5367
 
5368
+ /**
5369
+ * Fetches or extracts the raster form configuration for a STAC object.
5370
+ * Supports direct JSON objects, data URIs, and URL strings.
5371
+ *
5372
+ * @param {string|object|undefined} rasterform - The rasterform property from the STAC object.
5373
+ * @returns {Promise<import("@/types").EodashRasterJSONForm|undefined>}
5374
+ */
5375
+ async function fetchRasterForm(rasterform) {
5376
+ if (!rasterform) {
5377
+ return undefined;
5378
+ }
5379
+ if (typeof rasterform === "object") {
5380
+ return /** @type {import("@/types").EodashRasterJSONForm} */ (rasterform);
5381
+ }
5382
+ if (typeof rasterform === "string") {
5383
+ return await axios.get(rasterform).then((resp) => resp.data);
5384
+ }
5385
+ return undefined;
5386
+ }
5387
+
5366
5388
  /**
5367
5389
  * Spearates and extracts layerConfig (jsonform schema & legend) from a style json
5368
5390
  *
@@ -5419,6 +5441,39 @@ function extractLayerConfig(
5419
5441
  return { layerConfig, style };
5420
5442
  }
5421
5443
 
5444
+ /**
5445
+ * Recursively extracts URL keys from a JSON Schema.
5446
+ * Maps schema property names to their defined `url_key`.
5447
+ * @param {Record<string, any> | null | undefined} schema
5448
+ * @returns {Record<string, string>}
5449
+ */
5450
+ function extractUrlKeys(schema) {
5451
+ /** @type {Record<string, string>} */
5452
+ const keys = {};
5453
+ if (!schema || typeof schema !== "object") return keys;
5454
+
5455
+ if (schema.properties) {
5456
+ for (const [key, propDef] of Object.entries(schema.properties)) {
5457
+ if (propDef && typeof propDef === "object") {
5458
+ if (typeof propDef.url_key === "string") {
5459
+ keys[key] = propDef.url_key;
5460
+ }
5461
+ Object.assign(keys, extractUrlKeys(propDef));
5462
+ }
5463
+ }
5464
+ }
5465
+
5466
+ for (const combinator of ["oneOf", "allOf", "anyOf"]) {
5467
+ if (Array.isArray(schema[combinator])) {
5468
+ for (const sub of schema[combinator]) {
5469
+ Object.assign(keys, extractUrlKeys(sub));
5470
+ }
5471
+ }
5472
+ }
5473
+
5474
+ return keys;
5475
+ }
5476
+
5422
5477
  /**
5423
5478
  *
5424
5479
  * @param {number[]} bbox
@@ -5559,6 +5614,7 @@ const fetchAllStyles = async (stacObject) => {
5559
5614
  link.rel.includes("style"),
5560
5615
  );
5561
5616
  const fetchPromises = styleLinks.map(async (link) => {
5617
+ /** @type {import("@/types").EodashStyleJson} */
5562
5618
  const styleJson = await axios.get(link.href).then((resp) => resp.data);
5563
5619
  log.debug("fetched styles JSON", JSON.parse(JSON.stringify(styleJson)));
5564
5620
  return styleJson;
@@ -5853,20 +5909,147 @@ const createAssetID = (collectionId, itemId, index) => {
5853
5909
  *
5854
5910
  * @param {Record<string,any>[]} layers
5855
5911
  */
5856
- const removeUnneededProperties = (layers) => {
5857
- const cloned = structuredClone(layers);
5858
- cloned.forEach((layer) => {
5859
- const id = layer.properties.id;
5860
- const title = layer.properties.title;
5861
- layer.properties = { id, title };
5862
- if (layer["interactions"]) {
5863
- delete layer["interactions"];
5912
+ const removeUnneededProperties = (layers, formValues = {}) => {
5913
+ /**
5914
+ * @param {Record<string,any>} layer
5915
+ * @returns {Record<string,any>[]}
5916
+ */
5917
+ const processLayer = (layer) => {
5918
+ // If the layer (or group) is explicitly marked as not visible, skip it and all children
5919
+ if (layer.properties?.visible === false) {
5920
+ return [];
5864
5921
  }
5865
- if (layer.type === "Group") {
5866
- layer.layers = removeUnneededProperties(layer.layers);
5922
+
5923
+ // If it's a Group, we just want its children
5924
+ if (layer.type === "Group" && Array.isArray(layer.layers)) {
5925
+ return layer.layers.flatMap(processLayer);
5867
5926
  }
5868
- });
5869
- return cloned;
5927
+
5928
+ // Break any Vue Proxies/OpenLayers getters by stringifying first
5929
+ let clonedLayer;
5930
+ try {
5931
+ clonedLayer = JSON.parse(JSON.stringify(layer));
5932
+ } catch (_e) {
5933
+ clonedLayer = structuredClone(layer);
5934
+ }
5935
+
5936
+ // Flatten formValues to handle nested properties (e.g., vminmax: { vmin, vmax })
5937
+ /**
5938
+ * @param {Record<string,any>} obj
5939
+ * @returns {Record<string,any>}
5940
+ */
5941
+ const flattenFormValues = (obj) => {
5942
+ /** @type {Record<string,any>} */
5943
+ let result = {};
5944
+ for (const key in obj) {
5945
+ if (
5946
+ obj[key] !== null &&
5947
+ typeof obj[key] === "object" &&
5948
+ !Array.isArray(obj[key])
5949
+ ) {
5950
+ Object.assign(result, flattenFormValues(obj[key]));
5951
+ } else {
5952
+ result[key] = obj[key];
5953
+ }
5954
+ }
5955
+ return result;
5956
+ };
5957
+ const flatFormValues = flattenFormValues(formValues);
5958
+
5959
+ // Burn in style variables using Mustache if formValues are provided
5960
+ if (Object.keys(flatFormValues).length > 0) {
5961
+ // Stringify, render mustache, then parse back
5962
+ try {
5963
+ const renderedString = mustache.render(
5964
+ JSON.stringify(clonedLayer),
5965
+ flatFormValues,
5966
+ );
5967
+ clonedLayer = JSON.parse(renderedString);
5968
+ } catch (e) {
5969
+ log.warn(
5970
+ "[eodash] Failed to apply mustache templating during export cleanup:",
5971
+ e,
5972
+ );
5973
+ }
5974
+ }
5975
+
5976
+ const {
5977
+ id,
5978
+ title,
5979
+ mapboxStyle,
5980
+ projection,
5981
+ applyOptions,
5982
+ layerConfig,
5983
+ visible,
5984
+ } = clonedLayer.properties || {};
5985
+
5986
+ // If style was not at root but in properties (layerConfig), move it to root early
5987
+ if (!clonedLayer.style && layerConfig?.style) {
5988
+ clonedLayer.style = layerConfig.style;
5989
+ }
5990
+
5991
+ // Burn in OpenLayers ["var", "name"] variables using flatFormValues overriding style.variables
5992
+ const styleVariables = {
5993
+ ...(clonedLayer.style?.variables || {}),
5994
+ ...flatFormValues,
5995
+ };
5996
+
5997
+ if (clonedLayer.style && Object.keys(styleVariables).length > 0) {
5998
+ clonedLayer.style = updateVectorLayerStyle({
5999
+ ...clonedLayer.style,
6000
+ variables: styleVariables,
6001
+ });
6002
+ }
6003
+
6004
+ clonedLayer.properties = {
6005
+ id,
6006
+ title,
6007
+ ...(mapboxStyle && { mapboxStyle }),
6008
+ ...(projection && { projection }),
6009
+ ...(applyOptions && { applyOptions }),
6010
+ ...(visible !== undefined && { visible }),
6011
+ };
6012
+
6013
+ if (clonedLayer["interactions"]) {
6014
+ delete clonedLayer["interactions"];
6015
+ }
6016
+
6017
+ // Cleanup unnecessary properties
6018
+ /**
6019
+ * @param {any} obj
6020
+ */
6021
+ const cleanupProperties = (obj) => {
6022
+ if (!obj || typeof obj !== "object") return;
6023
+
6024
+ for (const key in obj) {
6025
+ if (obj[key] === null) {
6026
+ delete obj[key];
6027
+ } else if (Array.isArray(obj[key])) {
6028
+ if (obj[key].length === 0) {
6029
+ // Remove empty arrays like empty interactions
6030
+ delete obj[key];
6031
+ } else {
6032
+ obj[key].forEach(cleanupProperties);
6033
+ }
6034
+ } else if (typeof obj[key] === "object") {
6035
+ cleanupProperties(obj[key]);
6036
+ }
6037
+ }
6038
+ };
6039
+ cleanupProperties(clonedLayer);
6040
+
6041
+ return [clonedLayer];
6042
+ };
6043
+
6044
+ // ensure the top level array is also deeply un-proxied if needed
6045
+ let rawLayers = layers;
6046
+ try {
6047
+ rawLayers = JSON.parse(JSON.stringify(layers));
6048
+ } catch (_e) {
6049
+ rawLayers = structuredClone(layers);
6050
+ }
6051
+
6052
+ return rawLayers.flatMap(processLayer);
5870
6053
  };
5871
6054
 
5872
6055
  /**
@@ -6057,6 +6240,7 @@ function getDatetimeProperty(linksOrItems) {
6057
6240
  return prop;
6058
6241
  }
6059
6242
  }
6243
+
6060
6244
  /**
6061
6245
  *
6062
6246
  * @param {*} stacObject
@@ -6357,6 +6541,91 @@ function updateGeoZarrBands(olLayer, jsonformValue) {
6357
6541
  return true;
6358
6542
  }
6359
6543
 
6544
+ /**
6545
+ * Safely appends query parameters to a URL string, preserving templates like {z}/{x}/{y}
6546
+ * @param {string} url
6547
+ * @param {Record<string, string>} params
6548
+ * @returns {string}
6549
+ */
6550
+ function appendQueryParams(url, params) {
6551
+ const [base, query] = url.split("?");
6552
+ const searchParams = new URLSearchParams(query || "");
6553
+
6554
+ for (const [key, val] of Object.entries(params)) {
6555
+ if (val !== undefined && val !== null && val !== "") {
6556
+ searchParams.set(key, val);
6557
+ } else {
6558
+ searchParams.delete(key);
6559
+ }
6560
+ }
6561
+
6562
+ const newQuery = searchParams.toString();
6563
+ return newQuery ? `${base}?${newQuery}` : base;
6564
+ }
6565
+
6566
+ /**
6567
+ * Checks whether a VectorTile layer's URL needs to be updated based on jsonform output.
6568
+ * If the style's jsonform schema has properties with `url_key` defined, their values
6569
+ * are injected as query parameters into the source URL.
6570
+ *
6571
+ * @param {import("ol/layer/Layer").default} olLayer - Layer from layerConfig:change event
6572
+ * @param {Record<string, any>} jsonformValue - Current jsonform output
6573
+ * @returns {boolean} true if the URL was updated
6574
+ */
6575
+ function updateLayerUrl(olLayer, jsonformValue) {
6576
+ const jsonLayer = olLayer.get("_jsonDefinition");
6577
+ if (!jsonLayer || jsonLayer.type !== "VectorTile") {
6578
+ return false;
6579
+ }
6580
+
6581
+ const schema = jsonLayer.properties?.layerConfig?.schema;
6582
+ const urlKeys = extractUrlKeys(schema);
6583
+
6584
+ if (Object.keys(urlKeys).length === 0) {
6585
+ return false;
6586
+ }
6587
+
6588
+ let originalUrl = olLayer.get("originalUrl") || jsonLayer.source?.url;
6589
+
6590
+ if (!originalUrl || typeof originalUrl !== "string") {
6591
+ return false;
6592
+ }
6593
+
6594
+ if (!olLayer.get("originalUrl")) {
6595
+ olLayer.set("originalUrl", originalUrl);
6596
+ }
6597
+
6598
+ /** @type {Record<string, string>} */
6599
+ const queryParamsToInject = {};
6600
+ for (const [propName, urlKey] of Object.entries(urlKeys)) {
6601
+ queryParamsToInject[urlKey] = jsonformValue[propName];
6602
+ }
6603
+
6604
+ const newUrl = appendQueryParams(originalUrl, queryParamsToInject);
6605
+
6606
+ if (jsonLayer.source?.url) {
6607
+ if (jsonLayer.source.url === newUrl) {
6608
+ return false;
6609
+ }
6610
+ jsonLayer.source.url = newUrl;
6611
+ if (olLayer.get("injectedUrl") === newUrl) {
6612
+ return false;
6613
+ }
6614
+ const source = olLayer.getSource();
6615
+ olLayer.set("injectedUrl", newUrl);
6616
+ if (source && "setUrl" in source) {
6617
+ /** @type {any} */ (source).setUrl(newUrl);
6618
+ return true;
6619
+ }
6620
+ if (source && "setUrls" in source) {
6621
+ /** @type {any} */ (source).setUrls([newUrl]);
6622
+ return true;
6623
+ }
6624
+ }
6625
+
6626
+ return false;
6627
+ }
6628
+
6360
6629
  /**
6361
6630
  * Applies titiler upscaling to an XYZ tile URL based on the matched endpoint config.
6362
6631
  * - titiler v1: appends `@2x` to the `{y}` tile coordinate
@@ -6390,26 +6659,56 @@ function applyTitilerUpscaling(url, upscalingEndpoints) {
6390
6659
  }
6391
6660
 
6392
6661
  /**
6393
- * Picks the render presets for a collection, preferring the collection's own
6394
- * STAC `renders` extension and falling back to client-provided config
6395
- *
6662
+ * Picks the render presets for a collection, preferring client-provided config
6663
+ * (`options.renders[collectionId]`) and falling back to the collection's own STAC
6664
+ * `renders` extension when no config entry exists.
6396
6665
  * @param {import("stac-ts").StacCollection | null | undefined} collection
6397
6666
  * @param {Record<string, Record<string, import("@/types").Render>> | undefined} [configRenders]
6398
6667
  * @returns {Record<string, import("@/types").Render> | undefined}
6399
6668
  */
6400
6669
  function resolveRenders(collection, configRenders) {
6401
- if (collection?.renders) {
6402
- return /** @type {Record<string, import("@/types").Render>} */ (
6403
- collection.renders
6404
- );
6670
+ const config = collection?.id ? configRenders?.[collection.id] : undefined;
6671
+ if (config) return config;
6672
+ return /** @type {Record<string, import("@/types").Render>|undefined} */ (
6673
+ collection?.renders ?? undefined
6674
+ );
6675
+ }
6676
+
6677
+ /**
6678
+ * TiTiler expects rescale as [min,max] pairs; chunks a flat numeric list
6679
+ * into pairs (e.g. [0,0.4,0,0.1] -> [[0,0.4],[0,0.1]]). Nested input passes through.
6680
+ * @param {number[]|number[][]|undefined} rescale - flat or nested rescale values
6681
+ * @returns {number[][]|undefined} rescale as [min,max] pairs
6682
+ */
6683
+ function normalizeRescale(rescale) {
6684
+ if (!rescale?.length || Array.isArray(rescale[0])) {
6685
+ return /** @type {number[][]|undefined} */ (rescale);
6405
6686
  }
6406
- return collection?.id ? configRenders?.[collection.id] : undefined;
6687
+ const pairs = [];
6688
+ for (let i = 0; i < rescale.length; i += 2) {
6689
+ pairs.push(/** @type {number[]} */ (rescale).slice(i, i + 2));
6690
+ }
6691
+ return pairs;
6692
+ }
6693
+
6694
+ /**
6695
+ * Drops NaN nodata values; NaN is already the implicit fill for float data,
6696
+ * so forwarding `nodata=nan` to TiTiler is redundant.
6697
+ * @param {string|number|undefined} nodata - nodata value from render/asset metadata
6698
+ * @returns {string|number|undefined} nodata, or undefined when it is NaN
6699
+ */
6700
+ function normalizeNodata(nodata) {
6701
+ if (typeof nodata === "number" && Number.isNaN(nodata)) return undefined;
6702
+ if (typeof nodata === "string" && nodata.trim().toLowerCase() === "nan")
6703
+ return undefined;
6704
+ return nodata;
6407
6705
  }
6408
6706
 
6409
6707
  /**
6410
- * Serializes an object into a TiTiler query string. Arrays join with commas;
6411
- * nested arrays repeat the key (e.g. `rescale: [[0,1]]` -> `rescale=0,1`);
6412
- * objects are JSON-encoded. Shared by the render-extension and mosaic paths.
6708
+ * Serializes an object into a TiTiler query string. Arrays repeat the key per
6709
+ * element (TiTiler list params, e.g. `assets=a&assets=b`); nested elements
6710
+ * comma-join (`rescale: [[0,1],[0,2]]` -> `rescale=0,1&rescale=0,2`); objects
6711
+ * are JSON-encoded. Shared by the render-extension and mosaic paths.
6413
6712
  * @param {Record<string,any>} obj
6414
6713
  * @returns {string}
6415
6714
  */
@@ -6425,23 +6724,12 @@ function encodeURLObject(obj) {
6425
6724
 
6426
6725
  switch (valueType) {
6427
6726
  case "array": {
6428
- // Check if any element in the array is itself an array (multi-dimensional)
6429
- const hasNestedArrays = value.some((/** @type {any} */ item) =>
6430
- Array.isArray(item),
6431
- );
6432
-
6433
- if (hasNestedArrays) {
6434
- // For multi-dimensional arrays, repeat the key with different values
6435
- for (const val of value) {
6436
- if (Array.isArray(val)) {
6437
- str += `${key}=${val.join(",")}&`;
6438
- } else {
6439
- str += `${key}=${val}&`;
6440
- }
6727
+ for (const val of value) {
6728
+ if (Array.isArray(val)) {
6729
+ str += `${key}=${val.join(",")}&`;
6730
+ } else {
6731
+ str += `${key}=${encodeURIComponent(val)}&`;
6441
6732
  }
6442
- } else {
6443
- // For simple arrays, join with commas
6444
- str += `${key}=${value.join(",")}&`;
6445
6733
  }
6446
6734
  break;
6447
6735
  }
@@ -6458,4 +6746,4 @@ function encodeURLObject(obj) {
6458
6746
  return str;
6459
6747
  }
6460
6748
 
6461
- export { sanitizeBbox as $, dataThemesBrands as A, getLayers as B, extractLayerLegend as C, extractCollectionUrls as D, generateLinksFromItems as E, revokeCollectionBlobUrls as F, collectionsPalette as G, datetime as H, poi as I, activeTemplate as J, mapPosition as K, isFirstLoad as L, indicator as M, currentUrl as N, currentCompareUrl as O, eodashCompareCollections as P, compareIndicator as Q, comparePoi as R, eodashCollections as S, states as T, actions as U, areChartsSeparateLayout as V, chartSpec as W, compareChartSpec as X, loading as Y, includesProcess as Z, shouldShowChartWidget as _, extractEoxLegendLink as a, tooltipAdapter as a0, mapCompareEl as a1, updateGeoZarrBands as a2, layerControlFormValueCompare as a3, layerControlFormValue as a4, getColFromLayer as a5, isGlobe as a6, setMapProjFromCol as a7, setActiveTemplate as a8, availableMapProjection as a9, changeMapProjection as aa, getCompareLayers as ab, compareChartData as ac, chartData as ad, axios$1 as ae, removeLayers as af, resolveRenders as ag, removeUnneededProperties as ah, resolveStyle as b, createLayerID as c, extractLayerConfig as d, extractRoles as e, axios as f, applyTitilerUpscaling as g, addTooltipInteraction as h, createAssetID as i, getBandsProperty as j, fetchStyle as k, encodeURLObject as l, mergeGeojsons as m, mapEl as n, getProjectionCode as o, isSTACItem as p, extractLayerTimeValues as q, registerProjection as r, fetchApiItems as s, getDatetimeProperty as t, fetchPreAggregations as u, fetchAllStyles as v, findLayer as w, findLayersByLayerPrefix as x, replaceLayer as y, generateFeatures as z };
6749
+ export { compareChartSpec as $, findLayer as A, findLayersByLayerPrefix as B, replaceLayer as C, generateFeatures as D, dataThemesBrands as E, getLayers as F, extractLayerLegend as G, extractCollectionUrls as H, generateLinksFromItems as I, revokeCollectionBlobUrls as J, collectionsPalette as K, datetime as L, poi as M, activeTemplate as N, mapPosition as O, isFirstLoad as P, indicator as Q, currentUrl as R, currentCompareUrl as S, eodashCompareCollections as T, compareIndicator as U, comparePoi as V, eodashCollections as W, states as X, actions as Y, areChartsSeparateLayout as Z, chartSpec as _, extractRoles as a, loading as a0, includesProcess as a1, shouldShowChartWidget as a2, sanitizeBbox as a3, tooltipAdapter as a4, mapCompareEl as a5, updateGeoZarrBands as a6, updateLayerUrl as a7, layerControlFormValueCompare as a8, layerControlFormValue as a9, getColFromLayer as aa, isGlobe as ab, setMapProjFromCol as ac, setActiveTemplate as ad, availableMapProjection as ae, changeMapProjection as af, getCompareLayers as ag, compareChartData as ah, chartData as ai, axios$1 as aj, removeLayers as ak, removeUnneededProperties as al, extractEoxLegendLink as b, createLayerID as c, applyTitilerUpscaling as d, extractLayerConfig as e, fetchRasterForm as f, axios as g, resolveStyle as h, addTooltipInteraction as i, createAssetID as j, getBandsProperty as k, resolveRenders as l, mergeGeojsons as m, normalizeRescale as n, normalizeNodata as o, encodeURLObject as p, mapEl as q, registerProjection as r, getProjectionCode as s, isSTACItem as t, extractLayerTimeValues as u, fetchStyle as v, fetchApiItems as w, getDatetimeProperty as x, fetchPreAggregations as y, fetchAllStyles as z };