@eodash/eodash 5.7.0 → 5.7.1

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 (113) 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 +74 -20
  5. package/core/client/eodashSTAC/cql.js +64 -0
  6. package/core/client/eodashSTAC/createLayers.js +169 -116
  7. package/core/client/eodashSTAC/helpers.js +391 -98
  8. package/core/client/eodashSTAC/mosaic.js +416 -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 +25 -4
  12. package/core/client/store/states.js +20 -2
  13. package/core/client/types.ts +147 -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-ePbdvUTE.js} +2 -2
  17. package/dist/client/{DynamicWebComponent-CnKhYo0K.js → DynamicWebComponent-g5SdRbjY.js} +1 -1
  18. package/dist/client/{EodashChart-CLDqE93C.js → EodashChart-ChrkA5oS.js} +7 -6
  19. package/dist/client/{EodashDatePicker-CMPDT-ce.js → EodashDatePicker-CY1TrsDo.js} +3 -3
  20. package/dist/client/{EodashItemFilter-BpdA8QQa.js → EodashItemFilter-DzshHDlS.js} +2 -1
  21. package/dist/client/{EodashLayerControl-BHpDA4_0.js → EodashLayerControl-DMCeMkSd.js} +50 -20
  22. package/dist/client/{EodashLayoutSwitcher-B1fRUt7G.js → EodashLayoutSwitcher-iJ8mX1rF.js} +3 -3
  23. package/dist/client/{EodashMapBtns-Cpned0tc.js → EodashMapBtns-C7Cr7q5Y.js} +200 -86
  24. package/dist/client/EodashStacInfo-K7-iuXa7.js +169 -0
  25. package/dist/client/{EodashTools-1zHz26gv.js → EodashTools-BEK7e7JM.js} +4 -4
  26. package/dist/client/{ExportState-DbOm8hBI.js → ExportState-CqIzfMYQ.js} +6 -168
  27. package/dist/client/{Footer-BKg-VVkj.js → Footer-1P0cmBOZ.js} +1 -1
  28. package/dist/client/{Header-DCrwCvmY.js → Header-CkkBaBNh.js} +2 -2
  29. package/dist/client/{MobileLayout-vWSHpluo.js → MobileLayout-BPf49kDU.js} +2 -2
  30. package/dist/client/{PopUp-9uimfK49.js → PopUp-DPZq5SWI.js} +4 -168
  31. package/dist/client/{ProcessList-CTTIZa06.js → ProcessList-DuI6WpS1.js} +5 -5
  32. package/dist/client/VDivider-Bj9LG_JV.js +168 -0
  33. package/dist/client/{VImg-CrmB76ZY.js → VImg-Ddv5OyMz.js} +2 -2
  34. package/dist/client/{VMain-OKZNpcEG.js → VMain-7yqk_qC3.js} +1 -1
  35. package/dist/client/{VTooltip-CJw2FyWk.js → VTooltip-2hAJyEoa.js} +2 -2
  36. package/dist/client/{WidgetsContainer-BaWOnE5R.js → WidgetsContainer-2DOrlwCB.js} +1 -1
  37. package/dist/client/{asWebComponent-BHWpMuE3.js → asWebComponent-BlFN2lkB.js} +456 -306
  38. package/dist/client/{async-DSKj6g1f.js → async-B2H8ZwUK.js} +2 -2
  39. package/dist/client/{create-layers-config-CBoYedqB.js → create-layers-config-u_vn5hPk.js} +17 -17
  40. package/dist/client/dialog-transition-BrMkMiNa.js +170 -0
  41. package/dist/client/eo-dash.js +1 -1
  42. package/dist/client/{forwardRefs-DH51zPUr.js → forwardRefs-DAlror6d.js} +8 -87
  43. package/dist/client/{handling-BrcDDlgS.js → handling-B6I1V5eG.js} +3 -3
  44. package/dist/client/{helpers-DjZVl_WF.js → helpers-CBqIUSXJ.js} +794 -182
  45. package/dist/client/index-BWq1ZKaV.js +3168 -0
  46. package/dist/client/{index-CUjl-ZsI.js → index-CJ9UI3ln.js} +2 -2
  47. package/dist/client/{index-CSG_JoOA.js → index-DXLz4sFR.js} +62 -56
  48. package/dist/client/{index-1JKE68n3.js → index-DhKU6KqM.js} +211 -67
  49. package/dist/client/{index-Cu3-13Xg.js → index-DjQlbWbJ.js} +7 -6
  50. package/dist/client/mosaic-BFYLEwyx.js +455 -0
  51. package/dist/client/templates.js +250 -119
  52. package/dist/client/{transition-OHcz5Qq1.js → transition-D_W6Wn9v.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 +77 -18
  60. package/dist/types/core/client/eodashSTAC/mosaic.d.ts +56 -0
  61. package/dist/types/core/client/store/stac.d.ts +31 -7
  62. package/dist/types/core/client/store/states.d.ts +24 -2
  63. package/dist/types/core/client/types.d.ts +123 -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 +9 -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/states.d.ts +1 -1
  83. package/dist/types/widgets/EodashStacInfo.vue.d.ts +20 -0
  84. package/dist/types/widgets/EodashTimeSlider/index.vue.d.ts +20 -0
  85. package/dist/types/widgets/EodashTimeSlider/methods/index.d.ts +3 -8
  86. package/dist/types/widgets/EodashTimeSlider/types.d.ts +6 -16
  87. package/dist/types/widgets/EodashTools.vue.d.ts +5 -5
  88. package/package.json +33 -33
  89. package/templates/baseConfig.js +36 -6
  90. package/templates/compare.js +62 -7
  91. package/templates/expert.js +23 -0
  92. package/templates/explore.js +11 -5
  93. package/templates/lite.js +14 -0
  94. package/widgets/EodashChart.vue +1 -0
  95. package/widgets/EodashItemCatalog/index.vue +363 -35
  96. package/widgets/EodashItemCatalog/methods/filters.js +134 -70
  97. package/widgets/EodashItemCatalog/methods/handlers.js +113 -19
  98. package/widgets/EodashItemCatalog/methods/map.js +142 -28
  99. package/widgets/EodashLayerControl.vue +42 -11
  100. package/widgets/EodashMap/EodashMapBtns.vue +109 -48
  101. package/widgets/EodashMap/index.vue +89 -105
  102. package/widgets/EodashMap/methods/btns.js +37 -5
  103. package/widgets/EodashMap/methods/create-layers-config.js +16 -16
  104. package/widgets/EodashMap/methods/index.js +71 -8
  105. package/widgets/EodashMap/methods/use-map-loading.js +52 -0
  106. package/widgets/EodashProcess/index.vue +1 -0
  107. package/widgets/EodashProcess/states.js +1 -1
  108. package/widgets/EodashStacInfo.vue +64 -2
  109. package/widgets/EodashTimeSlider/index.vue +58 -2
  110. package/widgets/EodashTimeSlider/methods/index.js +5 -48
  111. package/widgets/EodashTimeSlider/types.ts +7 -17
  112. package/dist/client/EodashStacInfo-BQspAHKH.js +0 -107
  113. package/dist/client/index-5Jhsa8Xe.js +0 -570
@@ -1,7 +1,8 @@
1
1
  import { toAbsolute } from 'stac-js/src/http.js';
2
2
  import { setupCache } from 'axios-cache-interceptor';
3
3
  import log from 'loglevel';
4
- import { ref, shallowRef } from 'vue';
4
+ import { ref, shallowRef, reactive, shallowReactive } from 'vue';
5
+ import { mdiWrench, mdiSnowflake, mdiVolleyball, mdiHiking, mdiLightningBolt, mdiLeaf, mdiAccountMultiple, mdiCurrencyEur, mdiWeatherWindy, mdiSetCenter, mdiHospitalBoxOutline, mdiImageFilterHdr, mdiWater, mdiBarley } from '@mdi/js';
5
6
 
6
7
  /**
7
8
  * Create a bound version of a function with a specified `this` context
@@ -426,7 +427,9 @@ function merge(...objs) {
426
427
  return;
427
428
  }
428
429
 
429
- const targetKey = (caseless && findKey(result, key)) || key;
430
+ // findKey lowercases the key, so caseless lookup only applies to strings —
431
+ // symbol keys are identity-matched.
432
+ const targetKey = (caseless && typeof key === 'string' && findKey(result, key)) || key;
430
433
  // Read via own-prop only — a bare `result[targetKey]` walks the prototype
431
434
  // chain, so a polluted Object.prototype value could surface here and get
432
435
  // copied into the merged result.
@@ -443,7 +446,24 @@ function merge(...objs) {
443
446
  };
444
447
 
445
448
  for (let i = 0, l = objs.length; i < l; i++) {
446
- objs[i] && forEach(objs[i], assignValue);
449
+ const source = objs[i];
450
+ if (!source || isBuffer(source)) {
451
+ continue;
452
+ }
453
+
454
+ forEach(source, assignValue);
455
+
456
+ if (typeof source !== 'object' || isArray(source)) {
457
+ continue;
458
+ }
459
+
460
+ const symbols = Object.getOwnPropertySymbols(source);
461
+ for (let j = 0; j < symbols.length; j++) {
462
+ const symbol = symbols[j];
463
+ if (propertyIsEnumerable.call(source, symbol)) {
464
+ assignValue(source[symbol], symbol);
465
+ }
466
+ }
447
467
  }
448
468
  return result;
449
469
  }
@@ -672,6 +692,8 @@ const hasOwnProperty = (
672
692
  hasOwnProperty.call(obj, prop)
673
693
  )(Object.prototype);
674
694
 
695
+ const { propertyIsEnumerable } = Object.prototype;
696
+
675
697
  /**
676
698
  * Determine if a value is a RegExp object
677
699
  *
@@ -777,11 +799,11 @@ function isSpecCompliantForm(thing) {
777
799
  * @returns {Object} The JSON-compatible object.
778
800
  */
779
801
  const toJSONObject = (obj) => {
780
- const stack = new Array(10);
802
+ const visited = new WeakSet();
781
803
 
782
- const visit = (source, i) => {
804
+ const visit = (source) => {
783
805
  if (isObject(source)) {
784
- if (stack.indexOf(source) >= 0) {
806
+ if (visited.has(source)) {
785
807
  return;
786
808
  }
787
809
 
@@ -791,15 +813,16 @@ const toJSONObject = (obj) => {
791
813
  }
792
814
 
793
815
  if (!('toJSON' in source)) {
794
- stack[i] = source;
816
+ // add-on descent / delete-on-ascent: preserves path semantics, so DAG nodes serialise at every occurrence (see #7230).
817
+ visited.add(source);
795
818
  const target = isArray(source) ? [] : {};
796
819
 
797
820
  forEach(source, (value, key) => {
798
- const reducedValue = visit(value, i + 1);
821
+ const reducedValue = visit(value);
799
822
  !isUndefined(reducedValue) && (target[key] = reducedValue);
800
823
  });
801
824
 
802
- stack[i] = undefined;
825
+ visited.delete(source);
803
826
 
804
827
  return target;
805
828
  }
@@ -808,7 +831,7 @@ const toJSONObject = (obj) => {
808
831
  return source;
809
832
  };
810
833
 
811
- return visit(obj, 0);
834
+ return visit(obj);
812
835
  };
813
836
 
814
837
  /**
@@ -1010,10 +1033,6 @@ const parseHeaders = (rawHeaders) => {
1010
1033
  return parsed;
1011
1034
  };
1012
1035
 
1013
- const $internals = Symbol('internals');
1014
-
1015
- const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
1016
-
1017
1036
  function trimSPorHTAB(str) {
1018
1037
  let start = 0;
1019
1038
  let end = str.length;
@@ -1041,12 +1060,40 @@ function trimSPorHTAB(str) {
1041
1060
  return start === 0 && end === str.length ? str : str.slice(start, end);
1042
1061
  }
1043
1062
 
1044
- function normalizeHeader(header) {
1045
- return header && String(header).trim().toLowerCase();
1063
+ // The control-code ranges are intentional: header sanitization strips C0/DEL bytes.
1064
+ // eslint-disable-next-line no-control-regex
1065
+ const INVALID_UNICODE_HEADER_VALUE_CHARS = new RegExp('[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+', 'g');
1066
+ // eslint-disable-next-line no-control-regex
1067
+ const INVALID_BYTE_STRING_HEADER_VALUE_CHARS = new RegExp('[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+', 'g');
1068
+
1069
+ function sanitizeValue(value, invalidChars) {
1070
+ if (utils$1.isArray(value)) {
1071
+ return value.map((item) => sanitizeValue(item, invalidChars));
1072
+ }
1073
+
1074
+ return trimSPorHTAB(String(value).replace(invalidChars, ''));
1075
+ }
1076
+
1077
+ const sanitizeHeaderValue = (value) =>
1078
+ sanitizeValue(value, INVALID_UNICODE_HEADER_VALUE_CHARS);
1079
+
1080
+ const sanitizeByteStringHeaderValue = (value) =>
1081
+ sanitizeValue(value, INVALID_BYTE_STRING_HEADER_VALUE_CHARS);
1082
+
1083
+ function toByteStringHeaderObject(headers) {
1084
+ const byteStringHeaders = Object.create(null);
1085
+
1086
+ utils$1.forEach(headers.toJSON(), (value, header) => {
1087
+ byteStringHeaders[header] = sanitizeByteStringHeaderValue(value);
1088
+ });
1089
+
1090
+ return byteStringHeaders;
1046
1091
  }
1047
1092
 
1048
- function sanitizeHeaderValue(str) {
1049
- return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, ''));
1093
+ const $internals = Symbol('internals');
1094
+
1095
+ function normalizeHeader(header) {
1096
+ return header && String(header).trim().toLowerCase();
1050
1097
  }
1051
1098
 
1052
1099
  function normalizeValue(value) {
@@ -1128,7 +1175,7 @@ class AxiosHeaders {
1128
1175
  const lHeader = normalizeHeader(_header);
1129
1176
 
1130
1177
  if (!lHeader) {
1131
- throw new Error('header name must be a non-empty string');
1178
+ return;
1132
1179
  }
1133
1180
 
1134
1181
  const key = utils$1.findKey(self, lHeader);
@@ -1156,7 +1203,7 @@ class AxiosHeaders {
1156
1203
  key;
1157
1204
  for (const entry of header) {
1158
1205
  if (!utils$1.isArray(entry)) {
1159
- throw TypeError('Object iterator must return a key-value pair');
1206
+ throw new TypeError('Object iterator must return a key-value pair');
1160
1207
  }
1161
1208
 
1162
1209
  obj[(key = entry[0])] = (dest = obj[key])
@@ -1771,7 +1818,7 @@ function toFormData(obj, formData, options) {
1771
1818
  }
1772
1819
 
1773
1820
  if (stack.indexOf(value) !== -1) {
1774
- throw Error('Circular reference detected in ' + path.join('.'));
1821
+ throw new Error('Circular reference detected in ' + path.join('.'));
1775
1822
  }
1776
1823
 
1777
1824
  stack.push(value);
@@ -1988,6 +2035,7 @@ const transitionalDefaults = {
1988
2035
  forcedJSONParsing: true,
1989
2036
  clarifyTimeoutError: false,
1990
2037
  legacyInterceptorReqResOrdering: true,
2038
+ advertiseZstdAcceptEncoding: false,
1991
2039
  };
1992
2040
 
1993
2041
  const URLSearchParams$1 = typeof URLSearchParams !== 'undefined' ? URLSearchParams : AxiosURLSearchParams;
@@ -2145,7 +2193,7 @@ function formDataToJSON(formData) {
2145
2193
  return !isNumericKey;
2146
2194
  }
2147
2195
 
2148
- if (!target[name] || !utils$1.isObject(target[name])) {
2196
+ if (!utils$1.hasOwnProp(target, name) || !utils$1.isObject(target[name])) {
2149
2197
  target[name] = [];
2150
2198
  }
2151
2199
 
@@ -2510,6 +2558,9 @@ const progressEventReducer = (listener, isDownloadStream, freq = 3) => {
2510
2558
  const _speedometer = speedometer(50, 250);
2511
2559
 
2512
2560
  return throttle((e) => {
2561
+ if (!e || typeof e.loaded !== 'number') {
2562
+ return;
2563
+ }
2513
2564
  const rawLoaded = e.loaded;
2514
2565
  const total = e.lengthComputable ? e.total : undefined;
2515
2566
  const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded;
@@ -2819,12 +2870,12 @@ function setFormDataHeaders(headers, formHeaders, policy) {
2819
2870
  *
2820
2871
  * @returns {string} UTF-8 bytes as a Latin-1 string
2821
2872
  */
2822
- const encodeUTF8 = (str) =>
2873
+ const encodeUTF8$1 = (str) =>
2823
2874
  encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) =>
2824
2875
  String.fromCharCode(parseInt(hex, 16))
2825
2876
  );
2826
2877
 
2827
- const resolveConfig = (config) => {
2878
+ function resolveConfig(config) {
2828
2879
  const newConfig = mergeConfig({}, config);
2829
2880
 
2830
2881
  // Read only own properties to prevent prototype pollution gadgets
@@ -2845,8 +2896,8 @@ const resolveConfig = (config) => {
2845
2896
 
2846
2897
  newConfig.url = buildURL(
2847
2898
  buildFullPath(baseURL, url, allowAbsoluteUrls),
2848
- config.params,
2849
- config.paramsSerializer
2899
+ own('params'),
2900
+ own('paramsSerializer')
2850
2901
  );
2851
2902
 
2852
2903
  // HTTP basic authentication
@@ -2854,13 +2905,17 @@ const resolveConfig = (config) => {
2854
2905
  headers.set(
2855
2906
  'Authorization',
2856
2907
  'Basic ' +
2857
- btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))
2908
+ btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8$1(auth.password) : ''))
2858
2909
  );
2859
2910
  }
2860
2911
 
2861
2912
  if (utils$1.isFormData(data)) {
2862
- if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
2863
- headers.setContentType(undefined); // browser handles it
2913
+ if (
2914
+ platform.hasStandardBrowserEnv ||
2915
+ platform.hasStandardBrowserWebWorkerEnv ||
2916
+ utils$1.isReactNative(data)
2917
+ ) {
2918
+ headers.setContentType(undefined); // browser/web worker/RN handles it
2864
2919
  } else if (utils$1.isFunction(data.getHeaders)) {
2865
2920
  // Node.js FormData (like form-data package)
2866
2921
  setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
@@ -2892,7 +2947,7 @@ const resolveConfig = (config) => {
2892
2947
  }
2893
2948
 
2894
2949
  return newConfig;
2895
- };
2950
+ }
2896
2951
 
2897
2952
  const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
2898
2953
 
@@ -3041,7 +3096,7 @@ const xhrAdapter = isXHRAdapterSupported &&
3041
3096
 
3042
3097
  // Add headers to the request
3043
3098
  if ('setRequestHeader' in request) {
3044
- utils$1.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
3099
+ utils$1.forEach(toByteStringHeaderObject(requestHeaders), function setRequestHeader(val, key) {
3045
3100
  request.setRequestHeader(key, val);
3046
3101
  });
3047
3102
  }
@@ -3111,54 +3166,55 @@ const xhrAdapter = isXHRAdapterSupported &&
3111
3166
  };
3112
3167
 
3113
3168
  const composeSignals = (signals, timeout) => {
3114
- const { length } = (signals = signals ? signals.filter(Boolean) : []);
3115
-
3116
- if (timeout || length) {
3117
- let controller = new AbortController();
3118
-
3119
- let aborted;
3120
-
3121
- const onabort = function (reason) {
3122
- if (!aborted) {
3123
- aborted = true;
3124
- unsubscribe();
3125
- const err = reason instanceof Error ? reason : this.reason;
3126
- controller.abort(
3127
- err instanceof AxiosError
3128
- ? err
3129
- : new CanceledError(err instanceof Error ? err.message : err)
3130
- );
3131
- }
3132
- };
3169
+ signals = signals ? signals.filter(Boolean) : [];
3133
3170
 
3134
- let timer =
3135
- timeout &&
3136
- setTimeout(() => {
3137
- timer = null;
3138
- onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT));
3139
- }, timeout);
3140
-
3141
- const unsubscribe = () => {
3142
- if (signals) {
3143
- timer && clearTimeout(timer);
3144
- timer = null;
3145
- signals.forEach((signal) => {
3146
- signal.unsubscribe
3147
- ? signal.unsubscribe(onabort)
3148
- : signal.removeEventListener('abort', onabort);
3149
- });
3150
- signals = null;
3151
- }
3152
- };
3171
+ if (!timeout && !signals.length) {
3172
+ return;
3173
+ }
3153
3174
 
3154
- signals.forEach((signal) => signal.addEventListener('abort', onabort));
3175
+ const controller = new AbortController();
3155
3176
 
3156
- const { signal } = controller;
3177
+ let aborted = false;
3157
3178
 
3158
- signal.unsubscribe = () => utils$1.asap(unsubscribe);
3179
+ const onabort = function (reason) {
3180
+ if (!aborted) {
3181
+ aborted = true;
3182
+ unsubscribe();
3183
+ const err = reason instanceof Error ? reason : this.reason;
3184
+ controller.abort(
3185
+ err instanceof AxiosError
3186
+ ? err
3187
+ : new CanceledError(err instanceof Error ? err.message : err)
3188
+ );
3189
+ }
3190
+ };
3159
3191
 
3160
- return signal;
3161
- }
3192
+ let timer =
3193
+ timeout &&
3194
+ setTimeout(() => {
3195
+ timer = null;
3196
+ onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT));
3197
+ }, timeout);
3198
+
3199
+ const unsubscribe = () => {
3200
+ if (!signals) { return; }
3201
+ timer && clearTimeout(timer);
3202
+ timer = null;
3203
+ signals.forEach((signal) => {
3204
+ signal.unsubscribe
3205
+ ? signal.unsubscribe(onabort)
3206
+ : signal.removeEventListener('abort', onabort);
3207
+ });
3208
+ signals = null;
3209
+ };
3210
+
3211
+ signals.forEach((signal) => signal.addEventListener('abort', onabort));
3212
+
3213
+ const { signal } = controller;
3214
+
3215
+ signal.unsubscribe = () => utils$1.asap(unsubscribe);
3216
+
3217
+ return signal;
3162
3218
  };
3163
3219
 
3164
3220
  const streamChunk = function* (chunk, chunkSize) {
@@ -3352,12 +3408,41 @@ function estimateDataURLDecodedBytes(url) {
3352
3408
  return bytes;
3353
3409
  }
3354
3410
 
3355
- const VERSION = "1.16.0";
3411
+ const VERSION = "1.17.0";
3356
3412
 
3357
3413
  const DEFAULT_CHUNK_SIZE = 64 * 1024;
3358
3414
 
3359
3415
  const { isFunction } = utils$1;
3360
3416
 
3417
+ /**
3418
+ * Encode a UTF-8 string to a Latin-1 byte string for use with btoa().
3419
+ * This is a modern replacement for the deprecated unescape(encodeURIComponent(str)) pattern.
3420
+ *
3421
+ * @param {string} str The string to encode
3422
+ *
3423
+ * @returns {string} UTF-8 bytes as a Latin-1 string
3424
+ */
3425
+ const encodeUTF8 = (str) =>
3426
+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) =>
3427
+ String.fromCharCode(parseInt(hex, 16))
3428
+ );
3429
+
3430
+ // Node's WHATWG URL parser returns `username` and `password` percent-encoded.
3431
+ // Decode before composing the `auth` option so credentials such as
3432
+ // `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
3433
+ // original value for malformed input so a bad encoding never throws.
3434
+ const decodeURIComponentSafe = (value) => {
3435
+ if (!utils$1.isString(value)) {
3436
+ return value;
3437
+ }
3438
+
3439
+ try {
3440
+ return decodeURIComponent(value);
3441
+ } catch (error) {
3442
+ return value;
3443
+ }
3444
+ };
3445
+
3361
3446
  const test = (fn, ...args) => {
3362
3447
  try {
3363
3448
  return !!fn(...args);
@@ -3366,8 +3451,20 @@ const test = (fn, ...args) => {
3366
3451
  }
3367
3452
  };
3368
3453
 
3454
+ const maybeWithAuthCredentials = (url) => {
3455
+ const protocolIndex = url.indexOf('://');
3456
+ let urlToCheck = url;
3457
+ if (protocolIndex !== -1) {
3458
+ urlToCheck = urlToCheck.slice(protocolIndex + 3);
3459
+ }
3460
+ return urlToCheck.includes('@') || urlToCheck.includes(':');
3461
+ };
3462
+
3369
3463
  const factory = (env) => {
3370
- const globalObject = utils$1.global ?? globalThis;
3464
+ const globalObject =
3465
+ utils$1.global !== undefined && utils$1.global !== null
3466
+ ? utils$1.global
3467
+ : globalThis;
3371
3468
  const { ReadableStream, TextEncoder } = globalObject;
3372
3469
 
3373
3470
  env = utils$1.merge.call(
@@ -3510,6 +3607,7 @@ const factory = (env) => {
3510
3607
 
3511
3608
  const hasMaxContentLength = utils$1.isNumber(maxContentLength) && maxContentLength > -1;
3512
3609
  const hasMaxBodyLength = utils$1.isNumber(maxBodyLength) && maxBodyLength > -1;
3610
+ const own = (key) => (utils$1.hasOwnProp(config, key) ? config[key] : undefined);
3513
3611
 
3514
3612
  let _fetch = envFetch || fetch;
3515
3613
 
@@ -3532,6 +3630,46 @@ const factory = (env) => {
3532
3630
  let requestContentLength;
3533
3631
 
3534
3632
  try {
3633
+ // HTTP basic authentication
3634
+ let auth = undefined;
3635
+ const configAuth = own('auth');
3636
+
3637
+ if (configAuth) {
3638
+ const username = configAuth.username || '';
3639
+ const password = configAuth.password || '';
3640
+ auth = {
3641
+ username,
3642
+ password
3643
+ };
3644
+ }
3645
+
3646
+ if (maybeWithAuthCredentials(url)) {
3647
+ const parsedURL = new URL(url, platform.origin);
3648
+
3649
+ if (!auth && (parsedURL.username || parsedURL.password)) {
3650
+ const urlUsername = decodeURIComponentSafe(parsedURL.username);
3651
+ const urlPassword = decodeURIComponentSafe(parsedURL.password);
3652
+ auth = {
3653
+ username: urlUsername,
3654
+ password: urlPassword
3655
+ };
3656
+ }
3657
+
3658
+ if (parsedURL.username || parsedURL.password) {
3659
+ parsedURL.username = '';
3660
+ parsedURL.password = '';
3661
+ url = parsedURL.href;
3662
+ }
3663
+ }
3664
+
3665
+ if (auth) {
3666
+ headers.delete('authorization');
3667
+ headers.set(
3668
+ 'Authorization',
3669
+ 'Basic ' + btoa(encodeUTF8((auth.username || '') + ':' + (auth.password || '')))
3670
+ );
3671
+ }
3672
+
3535
3673
  // Enforce maxContentLength for data: URLs up-front so we never materialize
3536
3674
  // an oversized payload. The HTTP adapter applies the same check (see http.js
3537
3675
  // "if (protocol === 'data:')" branch).
@@ -3624,7 +3762,7 @@ const factory = (env) => {
3624
3762
  ...fetchOptions,
3625
3763
  signal: composedSignal,
3626
3764
  method: method.toUpperCase(),
3627
- headers: headers.normalize().toJSON(),
3765
+ headers: toByteStringHeaderObject(headers.normalize()),
3628
3766
  body: data,
3629
3767
  duplex: 'half',
3630
3768
  credentials: isCredentialsSupported ? withCredentials : undefined,
@@ -4213,6 +4351,7 @@ class Axios {
4213
4351
  forcedJSONParsing: validators.transitional(validators.boolean),
4214
4352
  clarifyTimeoutError: validators.transitional(validators.boolean),
4215
4353
  legacyInterceptorReqResOrdering: validators.transitional(validators.boolean),
4354
+ advertiseZstdAcceptEncoding: validators.transitional(validators.boolean),
4216
4355
  },
4217
4356
  false
4218
4357
  );
@@ -4699,10 +4838,6 @@ axios$1.HttpStatusCode = HttpStatusCode;
4699
4838
 
4700
4839
  axios$1.default = axios$1;
4701
4840
 
4702
- const instance = axios$1.create();
4703
-
4704
- const axios = setupCache(instance, { cacheTakeover: false });
4705
-
4706
4841
  /** setting default log level globally to warning */
4707
4842
  log.setLevel(log.levels.WARN, true);
4708
4843
 
@@ -4732,7 +4867,7 @@ const registeredProjections = ["EPSG:4326", "EPSG:3857"];
4732
4867
  /** available projection to be rendered by `EodashMap` */
4733
4868
  const availableMapProjection = ref("EPSG:3857");
4734
4869
 
4735
- /** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
4870
+ /** @type {import("vue").Ref<import("@eox/map").EOxMap & { mapUpdateId?: number } | null>} */
4736
4871
  const mapEl = shallowRef(null);
4737
4872
 
4738
4873
  /** @type {import("vue").Ref<import("@eox/map").EOxMap | null>} */
@@ -4776,6 +4911,24 @@ const chartSpec = ref(null);
4776
4911
  */
4777
4912
  const compareChartSpec = ref(null);
4778
4913
 
4914
+ /**
4915
+ * Global loading state.
4916
+ * - `loading.activeLoads` — increment/decrement to track concurrent loads
4917
+ * - `loading.value` — derived boolean, true when `activeLoads > 0`
4918
+ */
4919
+ const loading = reactive({
4920
+ activeLoads: 0,
4921
+ get value() {
4922
+ return this.activeLoads > 0;
4923
+ },
4924
+ });
4925
+
4926
+ /**
4927
+ * Adapter allows external widgets to hook into tooltip property transformation
4928
+ * @type {import("vue").Ref<((param: {key: string, value: any}, map?: string) => {key: string, value: any} | undefined) | null>}
4929
+ */
4930
+ const tooltipAdapter = shallowRef(null);
4931
+
4779
4932
  const states = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
4780
4933
  __proto__: null,
4781
4934
  activeTemplate,
@@ -4792,13 +4945,180 @@ const states = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
4792
4945
  datetime,
4793
4946
  indicator,
4794
4947
  isGlobe,
4948
+ loading,
4795
4949
  mapCompareEl,
4796
4950
  mapEl,
4797
4951
  mapPosition,
4798
4952
  poi,
4799
- registeredProjections
4953
+ registeredProjections,
4954
+ tooltipAdapter
4800
4955
  }, Symbol.toStringTag, { value: 'Module' }));
4801
4956
 
4957
+ const instance = axios$1.create();
4958
+ const axios = setupCache(instance, { cacheTakeover: false });
4959
+ function installLoadingInterceptors() {
4960
+ const reqId = axios.interceptors.request.use((config) => {
4961
+ loading.activeLoads++;
4962
+ return config;
4963
+ });
4964
+ const resId = axios.interceptors.response.use(
4965
+ (response) => {
4966
+ loading.activeLoads = Math.max(0, loading.activeLoads - 1);
4967
+ return response;
4968
+ },
4969
+ (error) => {
4970
+ loading.activeLoads = Math.max(0, loading.activeLoads - 1);
4971
+ return Promise.reject(error);
4972
+ }
4973
+ );
4974
+ return () => {
4975
+ axios.interceptors.request.eject(reqId);
4976
+ axios.interceptors.response.eject(resId);
4977
+ };
4978
+ }
4979
+ installLoadingInterceptors();
4980
+
4981
+ /**
4982
+ * Array of eodash STAC Collections extracted from the current selected indicator.
4983
+ * Updated in {@link file://./../store/stac.js `loadSelectedSTAC`} widget
4984
+ *
4985
+ * @type {import('../eodashSTAC/EodashCollection').EodashCollection[]}
4986
+ * @private
4987
+ */
4988
+ const eodashCollections = shallowReactive([]);
4989
+
4990
+ /**
4991
+ * Array of eodash STAC Collections extracted from the current selected COMPARE indicator.
4992
+ * Updated in {@link file://./../store/stac.js ` loadSelectedCompareSTAC`} widget
4993
+ *
4994
+ * @type {import('../eodashSTAC/EodashCollection').EodashCollection[]}
4995
+ * @private
4996
+ */
4997
+ const eodashCompareCollections = shallowReactive([]);
4998
+
4999
+ /** whether it's the first load of the app, used to track if there are params in the url */
5000
+ const isFirstLoad = ref(false);
5001
+
5002
+ /**
5003
+ * Current value of the layer control JSON form for the latest layer the user interacted with.
5004
+ * @type {import("vue").Ref<Record<string, any> | undefined>}
5005
+ */
5006
+ const layerControlFormValue = ref({});
5007
+
5008
+ /**
5009
+ * Current value of the layer control JSON form for the latest layer the user interacted with.
5010
+ * @type {import("vue").Ref<Record<string, any> | undefined>}
5011
+ */
5012
+ const layerControlFormValueCompare = ref({});
5013
+
5014
+ /**
5015
+ * STAC indicators color palette, defaults to Bank-Wong palette
5016
+ * @type {string[]} */
5017
+ const collectionsPalette = reactive([
5018
+ "#009E73",
5019
+ "#0072B2",
5020
+ "#E69F00",
5021
+ "#CC79A7",
5022
+ "#56B4E9",
5023
+ "#D55E00",
5024
+ ]);
5025
+
5026
+ /**
5027
+ * Object containing data themes and their associated brand properties (icon and color).
5028
+ */
5029
+ const dataThemesBrands = {
5030
+ agriculture: {
5031
+ icon: mdiBarley,
5032
+ color: "#F2AF25",
5033
+ },
5034
+ water: {
5035
+ icon: mdiWater,
5036
+ color: "#73A6C7",
5037
+ },
5038
+ oceans: {
5039
+ icon: mdiWater,
5040
+ color: "#6DA2C5",
5041
+ },
5042
+ land: {
5043
+ icon: mdiImageFilterHdr,
5044
+ color: "#019E73",
5045
+ },
5046
+ health: {
5047
+ icon: mdiHospitalBoxOutline,
5048
+ color: "#32322C",
5049
+ },
5050
+ "covid-19": {
5051
+ icon: mdiHospitalBoxOutline,
5052
+ color: "#32322C",
5053
+ },
5054
+ combined: {
5055
+ icon: mdiSetCenter,
5056
+ color: "#56B4E9",
5057
+ },
5058
+ air: {
5059
+ icon: mdiWeatherWindy,
5060
+ color: "#475faf",
5061
+ },
5062
+ atmosphere: {
5063
+ icon: mdiWeatherWindy,
5064
+ color: "#475faf",
5065
+ },
5066
+ climate: {
5067
+ icon: mdiWeatherWindy,
5068
+ color: "#475faf",
5069
+ },
5070
+ economy: {
5071
+ icon: mdiCurrencyEur,
5072
+ color: "#8E81AF",
5073
+ },
5074
+ commerce: {
5075
+ icon: mdiCurrencyEur,
5076
+ color: "#8E81AF",
5077
+ },
5078
+ society: {
5079
+ icon: mdiAccountMultiple,
5080
+ color: "#8ac501",
5081
+ },
5082
+ biomass: {
5083
+ icon: mdiLeaf,
5084
+ color: "#009E73",
5085
+ },
5086
+ extremes: {
5087
+ icon: mdiLightningBolt,
5088
+ color: "#a1280a",
5089
+ },
5090
+ energy: {
5091
+ icon: mdiLightningBolt,
5092
+ color: "#475faf",
5093
+ },
5094
+ tourism: {
5095
+ icon: mdiHiking,
5096
+ color: "#80510aff",
5097
+ },
5098
+ sport: {
5099
+ icon: mdiVolleyball,
5100
+ color: "#e98e65ff",
5101
+ },
5102
+ cryosphere: {
5103
+ icon: mdiSnowflake,
5104
+ color: "#42C7B8",
5105
+ },
5106
+ industry: {
5107
+ icon: mdiWrench,
5108
+ color: "#8d845cff",
5109
+ },
5110
+ };
5111
+
5112
+ /** @type {Map<string, import("stac-ts").StacItem[]>} */
5113
+ const itemsCache = new Map();
5114
+ /**
5115
+ * Split items cache stores items with their time range metadata
5116
+ * @type {Map<string, {items: import("stac-ts").StacItem[], minTime: number, maxTime: number}>}
5117
+ */
5118
+ const splitItemsCache = new Map();
5119
+
5120
+ ref(0);
5121
+
4802
5122
  /**
4803
5123
  * Returns the current layers of {@link mapEl}
4804
5124
  * @returns {import("@eox/map").EoxLayer[]}
@@ -4807,7 +5127,7 @@ const getLayers = () => mapEl.value?.layers ?? [];
4807
5127
 
4808
5128
  /**
4809
5129
  * Returns the current layers of {@link mapCompareEl}
4810
- * * @returns {import("@eox/map").EoxLayer[]}
5130
+ * @returns {import("@eox/map").EoxLayer[]}
4811
5131
  */
4812
5132
  const getCompareLayers = () => mapCompareEl.value?.layers ?? [];
4813
5133
 
@@ -5173,7 +5493,7 @@ const extractRoles = (properties, linkOrAsset) => {
5173
5493
 
5174
5494
  /**
5175
5495
  * Extracts a single non-link style JSON from a STAC Item optionally for a selected key mapping
5176
- * @param { import("stac-ts").StacItem | import("stac-ts").StacCollection } stacObject
5496
+ * @param { import("stac-ts").StacItem | import("stac-ts").StacCollection | null | undefined} stacObject
5177
5497
  * @param {string | undefined} linkKey
5178
5498
  * @param {string | undefined} assetKey
5179
5499
  * @returns
@@ -5183,6 +5503,7 @@ const fetchStyle = async (
5183
5503
  linkKey = undefined,
5184
5504
  assetKey = undefined,
5185
5505
  ) => {
5506
+ if (!stacObject) return undefined;
5186
5507
  let styleLink = null;
5187
5508
  if (linkKey) {
5188
5509
  styleLink = stacObject.links.find(
@@ -5214,6 +5535,20 @@ const fetchStyle = async (
5214
5535
  }
5215
5536
  };
5216
5537
 
5538
+ /**
5539
+ * Resolves a style by preferring the item's own `style` link and falling back
5540
+ * to the collection's. Takes the same key arguments as `fetchStyle`.
5541
+ *
5542
+ * @param {import("stac-ts").StacItem | import("stac-ts").StacCollection} item
5543
+ * @param {import("stac-ts").StacCollection | null | undefined} collection
5544
+ * @param {string} [linkKey]
5545
+ * @param {string} [assetKey]
5546
+ * @returns {Promise<import("@/types").EodashStyleJson | undefined>}
5547
+ */
5548
+ const resolveStyle = async (item, collection, linkKey, assetKey) =>
5549
+ (await fetchStyle(item, linkKey, assetKey)) ??
5550
+ (await fetchStyle(collection, linkKey, assetKey));
5551
+
5217
5552
  /**
5218
5553
  * Fetches all style JSONs from a STAC Item and returns an array with style objects
5219
5554
  * @param {import("stac-ts").StacItem | import("stac-ts").StacCollection} stacObject
@@ -5253,94 +5588,74 @@ const getProjectionCode = (projection) => {
5253
5588
  };
5254
5589
 
5255
5590
  /**
5256
- * Extracts layercontrol LayerDatetime property from STAC Links
5257
- * @param {import("stac-ts").StacLink[] | import("stac-ts").StacItem[] | undefined} [items]
5258
- * @param {string|null} [currentStep]
5259
- **/
5260
- const extractLayerTimeValues = (items, currentStep) => {
5261
- if (!currentStep || !items?.length) {
5262
- return { layerDatetime: undefined, timeControlValues: undefined };
5263
- }
5264
-
5265
- // check if items has a datetime value
5266
- const dateProperty = getDatetimeProperty(items);
5267
-
5268
- if (!dateProperty) {
5591
+ * Builds layercontrol LayerDatetime + timeControlValues from a list of dates.
5592
+ *
5593
+ * @param {Date[] | undefined} dates
5594
+ * @param {string | null} [currentStep] - target datetime; snapped to the closest available date
5595
+ * @returns {{ layerDatetime: Record<string, any> | undefined, timeControlValues: { date: string }[] | undefined }}
5596
+ */
5597
+ const extractLayerTimeValues = (dates, currentStep) => {
5598
+ if (!currentStep || !dates?.length || dates.length <= 1) {
5269
5599
  return { layerDatetime: undefined, timeControlValues: undefined };
5270
5600
  }
5271
- /** @type {{date:string;itemId:string}[]} */
5272
- const timeValues = [];
5273
- try {
5274
- /**
5275
- * @param {typeof timeValues} vals
5276
- * @param {import("stac-ts").StacLink} link
5277
- */
5278
- const reduceLinks = (vals, link) => {
5279
- if (link[dateProperty] && link.rel === "item") {
5280
- vals.push({
5281
- itemId: /** @type {string} */ (link.id),
5282
- date: new Date(
5283
- /** @type {string} */ (link[dateProperty]),
5284
- ).toISOString(),
5285
- });
5286
- }
5287
- return vals;
5288
- };
5289
5601
 
5290
- /**
5291
- *
5292
- * @param {typeof timeValues} vals
5293
- * @param {import("stac-ts").StacItem} item
5294
- */
5295
- const reduceItems = (vals, item) => {
5296
- const date = item.properties?.[dateProperty];
5297
- if (date) {
5298
- vals.push({
5299
- itemId: /** @type {string} */ (item.id),
5300
- date: new Date(/** @type {string} */ (date)).toISOString(),
5301
- ...item.properties,
5302
- });
5303
- }
5304
- return vals;
5305
- };
5306
- currentStep = new Date(currentStep).toISOString();
5307
- //@ts-expect-error TODO
5308
- items.reduce(isSTACItem(items[0]) ? reduceItems : reduceLinks, timeValues);
5309
- } catch (e) {
5310
- console.warn("[eodash] not supported datetime format was provided", e);
5311
- return { layerDatetime: undefined, timeControlValues: undefined };
5312
- }
5313
- // not enough timeValues
5314
- if (timeValues.length <= 1) {
5315
- return { layerDatetime: undefined, timeControlValues: undefined };
5316
- }
5602
+ const controlValues = dates.map((d) => d.toISOString()).sort();
5603
+ const timeControlValues = controlValues.map((date) => ({ date }));
5317
5604
 
5318
- // item datetime is not included in the item links datetime
5319
- if (!timeValues.some((val) => val.date === currentStep)) {
5320
- const currentStepTime = new Date(currentStep).getTime();
5321
- currentStep = timeValues.reduce((time, step) => {
5322
- const aDiff = Math.abs(new Date(time).getTime() - currentStepTime);
5323
- const bDiff = Math.abs(new Date(step.date).getTime() - currentStepTime);
5324
- return bDiff < aDiff ? step.date : time;
5325
- }, timeValues[0].date);
5605
+ currentStep = new Date(currentStep).toISOString();
5606
+ if (!controlValues.includes(currentStep)) {
5607
+ const target = new Date(currentStep).getTime();
5608
+ currentStep = controlValues.reduce((best, d) =>
5609
+ Math.abs(new Date(d).getTime() - target) <
5610
+ Math.abs(new Date(best).getTime() - target)
5611
+ ? d
5612
+ : best,
5613
+ );
5326
5614
  }
5327
5615
 
5328
5616
  const layerDatetime = {
5329
- controlValues: timeValues.map((d) => d.date).sort(),
5617
+ controlValues,
5330
5618
  currentStep,
5331
5619
  slider: true,
5332
5620
  navigation: true,
5333
5621
  play: false,
5334
5622
  displayFormat: "DD.MM.YYYY HH:mm",
5335
5623
  animateOnClickInterval: false,
5624
+ showUTC: true,
5336
5625
  };
5337
5626
 
5338
- return {
5339
- layerDatetime,
5340
- timeControlValues: timeValues,
5341
- };
5627
+ return { layerDatetime, timeControlValues };
5342
5628
  };
5343
5629
 
5630
+ /**
5631
+ * Fetches the daily pre-aggregation data for a STAC collection if available.
5632
+ * Returns the raw AggregationCollection,
5633
+ * or null if no daily pre-aggregation link exists, or the fetch fails.
5634
+ *
5635
+ * @param {import("stac-ts").StacCollection | undefined} stacCollection
5636
+ * @param {string} fallbackBaseUrl - base for relative href resolution when no `self` link is present
5637
+ * @returns {Promise<any | null>}
5638
+ */
5639
+ async function fetchPreAggregations(stacCollection, fallbackBaseUrl) {
5640
+ if (!stacCollection) return null;
5641
+ const preAggregationLink = stacCollection.links?.find(
5642
+ (l) => l.rel === "pre-aggregation" && l["aggregation:interval"] === "daily",
5643
+ );
5644
+ if (!preAggregationLink) return null;
5645
+
5646
+ try {
5647
+ const selfLink = stacCollection.links?.find((l) => l.rel === "self")?.href;
5648
+ const url = toAbsolute(
5649
+ preAggregationLink.href,
5650
+ selfLink || fallbackBaseUrl,
5651
+ );
5652
+ return await axios.get(url).then((resp) => resp.data);
5653
+ } catch (e) {
5654
+ console.warn("[eodash] Failed to fetch pre-aggregation", e);
5655
+ return null;
5656
+ }
5657
+ }
5658
+
5344
5659
  /**
5345
5660
  * Recursively find all layers whose ID up to the first ; is same as given layer
5346
5661
  *
@@ -5349,6 +5664,9 @@ const extractLayerTimeValues = (items, currentStep) => {
5349
5664
  * @returns {import("@eox/map").EoxLayer[]} Matching layer objects.
5350
5665
  */
5351
5666
  const findLayersByLayerPrefix = (layers, referenceLayer) => {
5667
+ if (!layers || !referenceLayer) {
5668
+ return [];
5669
+ }
5352
5670
  const refId = referenceLayer?.properties?.id;
5353
5671
 
5354
5672
  if (typeof refId !== "string" || !refId.includes(";:;")) {
@@ -5708,7 +6026,12 @@ function getDatetimeProperty(linksOrItems) {
5708
6026
  }
5709
6027
 
5710
6028
  // TODO: consider other properties for datetime ranges
5711
- const datetimeProperties = ["datetime", "start_datetime", "end_datetime"];
6029
+
6030
+ const datetimeProperties = /** @type {const} */ ([
6031
+ "datetime",
6032
+ "start_datetime",
6033
+ "end_datetime",
6034
+ ]);
5712
6035
  if (checkProperties) {
5713
6036
  for (const prop of datetimeProperties) {
5714
6037
  const propExists = linksOrItems.some(
@@ -5750,6 +6073,65 @@ function isSTACItem(stacObject) {
5750
6073
  );
5751
6074
  }
5752
6075
 
6076
+ /**
6077
+ * Fetches items using a split strategy (past/future) around a center date
6078
+ * @param {string} itemsUrl
6079
+ * @param {string} query
6080
+ * @param {string | Date} centerDatetime
6081
+ * @param {number} maxNumber
6082
+ * @returns {Promise<import("stac-ts").StacItem[]>}
6083
+ */
6084
+ const fetchSplitItems = async (
6085
+ itemsUrl,
6086
+ query,
6087
+ centerDatetime,
6088
+ maxNumber,
6089
+ ) => {
6090
+ const center = new Date(centerDatetime).toISOString();
6091
+ const limit = Math.ceil(maxNumber / 2);
6092
+
6093
+ /** @param {"past"|"future"} direction */
6094
+ const fetchSide = async (direction) => {
6095
+ const isPast = direction === "past";
6096
+ const datetimeRange = isPast ? `../${center}` : `${center}/..`;
6097
+ const sortOrder = isPast ? "-datetime" : "+datetime";
6098
+ const queryParams = new URLSearchParams(query);
6099
+
6100
+ queryParams.set("limit", limit.toString());
6101
+ queryParams.set("datetime", datetimeRange);
6102
+ queryParams.set("sortby", sortOrder);
6103
+
6104
+ const splitUrl = itemsUrl.split("/");
6105
+ const collectionId = /** @type {string} */ (splitUrl.at(-2));
6106
+ queryParams.set("collection", collectionId);
6107
+
6108
+ const searchEndpoint = `${splitUrl.slice(0, -3).join("/")}/search`;
6109
+ const items = await axios
6110
+ .get(searchEndpoint, { params: queryParams })
6111
+ .then((res) => res.data.features);
6112
+ return items;
6113
+ };
6114
+
6115
+ const [pastItems, futureItems] = await Promise.all([
6116
+ fetchSide("past"),
6117
+ fetchSide("future"),
6118
+ ]);
6119
+
6120
+ const allItems = [...pastItems, ...futureItems];
6121
+
6122
+ // check for duplicates by ids
6123
+ const seen = new Set();
6124
+ const uniqueItems = [];
6125
+ for (const item of allItems) {
6126
+ if (!seen.has(item.id)) {
6127
+ seen.add(item.id);
6128
+ uniqueItems.push(item);
6129
+ }
6130
+ }
6131
+
6132
+ return uniqueItems;
6133
+ };
6134
+
5753
6135
  /**
5754
6136
  * Fetch all STAC items from a STAC API endpoint.
5755
6137
  * @param {string} itemsUrl
@@ -5757,6 +6139,8 @@ function isSTACItem(stacObject) {
5757
6139
  * @param {number} [limit=100] - The maximum number of items to fetch per request.
5758
6140
  * @param {boolean} [returnFirst] - If true, only the first page of results will be returned.
5759
6141
  * @param {number} [maxNumber=1000] - if the matched number of items exceed this, only the first page will be returned.
6142
+ * @param {string | Date} [centerDatetime] - Date to center the search around if items exceed maxNumber
6143
+ * @returns {Promise<import("stac-ts").StacItem[]>}
5760
6144
  */
5761
6145
  async function fetchApiItems(
5762
6146
  itemsUrl,
@@ -5764,23 +6148,42 @@ async function fetchApiItems(
5764
6148
  limit = 100,
5765
6149
  returnFirst = false,
5766
6150
  maxNumber = 1000,
6151
+ centerDatetime,
5767
6152
  ) {
5768
- itemsUrl = itemsUrl.includes("?") ? itemsUrl.split("?")[0] : itemsUrl;
5769
- itemsUrl += query ? `?limit=${limit}&${query}` : `?limit=${limit}`;
6153
+ // Exclude centerDatetime from cache key - it's only used for split search fallback
6154
+ const cacheKey = JSON.stringify({
6155
+ itemsUrl,
6156
+ query,
6157
+ limit,
6158
+ returnFirst,
6159
+ maxNumber,
6160
+ });
6161
+
6162
+ if (itemsCache.has(cacheKey)) {
6163
+ return itemsCache.get(cacheKey) ?? [];
6164
+ }
6165
+
6166
+ const urlQuery = new URLSearchParams(query);
6167
+ urlQuery.set("limit", limit.toString());
6168
+
6169
+ let finalItemsUrl = itemsUrl.includes("?")
6170
+ ? itemsUrl.split("?")[0]
6171
+ : itemsUrl;
6172
+ finalItemsUrl += urlQuery.keys().toArray().length
6173
+ ? `?${urlQuery.toString()}`
6174
+ : "";
5770
6175
 
5771
6176
  const itemsFeatureCollection = await axios
5772
- .get(itemsUrl)
6177
+ .get(finalItemsUrl)
5773
6178
  .then((resp) => resp.data);
6179
+
5774
6180
  /** @type {import("stac-ts").StacItem[]} */
5775
6181
  const items = itemsFeatureCollection.features;
5776
- const nextLink = itemsFeatureCollection.links?.find(
5777
- //@ts-expect-error TODO: itemsFeatureCollection is not typed
5778
- (link) => link.rel === "next",
5779
- );
5780
-
5781
- if (!nextLink || returnFirst) {
6182
+ if (returnFirst) {
6183
+ itemsCache.set(cacheKey, items);
5782
6184
  return items;
5783
6185
  }
6186
+
5784
6187
  /** @type {number} */
5785
6188
  const matchedItems = itemsFeatureCollection.numberMatched;
5786
6189
  // Avoid fetching too many items
@@ -5788,26 +6191,70 @@ async function fetchApiItems(
5788
6191
  console.warn(
5789
6192
  `[eodash] The number of items matched (${matchedItems}) exceeds the maximum allowed (${maxNumber})`,
5790
6193
  );
5791
- return items;
5792
- }
6194
+ // we try to narrow down the search around the center datetime
6195
+ if (centerDatetime) {
6196
+ // Check if we have a cached split result that covers this centerDatetime
6197
+ const splitCacheKey = JSON.stringify({ itemsUrl, query, maxNumber });
6198
+ const cachedSplit = splitItemsCache.get(splitCacheKey);
6199
+ const centerTime = new Date(centerDatetime).getTime();
5793
6200
 
5794
- let [nextLinkURL, nextLinkQuery] = nextLink.href.split("?");
5795
- nextLinkQuery = nextLinkQuery.replace(/limit=\d+/, "");
5796
- if (query) {
5797
- const queryParams = new URLSearchParams(query);
5798
- const nextLinkParams = new URLSearchParams(nextLinkQuery);
6201
+ if (
6202
+ cachedSplit &&
6203
+ centerTime >= cachedSplit.minTime &&
6204
+ centerTime <= cachedSplit.maxTime
6205
+ ) {
6206
+ return cachedSplit.items;
6207
+ }
5799
6208
 
5800
- for (const key of nextLinkParams.keys()) {
5801
- queryParams.delete(key);
5802
- }
5803
- const remainingQuery = queryParams.toString();
5804
- if (remainingQuery) {
5805
- nextLinkQuery += `&${remainingQuery}`;
6209
+ const narrowedItems = await fetchSplitItems(
6210
+ itemsUrl,
6211
+ query ?? "",
6212
+ centerDatetime,
6213
+ maxNumber,
6214
+ );
6215
+
6216
+ if (!narrowedItems.length) {
6217
+ return narrowedItems;
6218
+ }
6219
+ const datetimeProperty = getDatetimeProperty(narrowedItems);
6220
+ if (!datetimeProperty) {
6221
+ return narrowedItems;
6222
+ }
6223
+
6224
+ const times = narrowedItems
6225
+ .map((i) =>
6226
+ i.properties[datetimeProperty]
6227
+ ? new Date(i.properties[datetimeProperty]).getTime()
6228
+ : null,
6229
+ )
6230
+ .filter((t) => t !== null);
6231
+ if (!times.length) {
6232
+ return narrowedItems;
6233
+ }
6234
+ splitItemsCache.set(splitCacheKey, {
6235
+ items: narrowedItems,
6236
+ minTime: Math.min(...times),
6237
+ maxTime: Math.max(...times),
6238
+ });
6239
+
6240
+ return narrowedItems;
5806
6241
  }
6242
+
6243
+ itemsCache.set(cacheKey, items);
6244
+ return items;
5807
6245
  }
5808
6246
 
5809
- const nextPage = await fetchApiItems(nextLinkURL, nextLinkQuery);
5810
- items.push(...nextPage);
6247
+ urlQuery.set("limit", maxNumber.toString());
6248
+
6249
+ const allItems = await axios
6250
+ .get(itemsUrl + "?" + urlQuery.toString())
6251
+ .then((resp) => resp.data.features)
6252
+ .catch((err) => {
6253
+ console.error(err);
6254
+ return [];
6255
+ });
6256
+ items.splice(0, items.length, ...allItems);
6257
+ itemsCache.set(cacheKey, items);
5811
6258
  return items;
5812
6259
  }
5813
6260
  /**
@@ -5846,4 +6293,169 @@ function extractEoxLegendLink(link) {
5846
6293
  return extraProperties;
5847
6294
  }
5848
6295
 
5849
- export { axios$1 as $, datetime as A, poi as B, activeTemplate as C, mapPosition as D, indicator as E, currentUrl as F, currentCompareUrl as G, compareIndicator as H, comparePoi as I, states as J, actions as K, areChartsSeparateLayout as L, chartSpec as M, compareChartSpec as N, includesProcess as O, shouldShowChartWidget as P, sanitizeBbox as Q, mapCompareEl as R, getColFromLayer as S, setMapProjFromCol as T, isGlobe as U, setActiveTemplate as V, availableMapProjection as W, changeMapProjection as X, getCompareLayers as Y, compareChartData as Z, chartData as _, extractEoxLegendLink as a, removeLayers as a0, removeUnneededProperties as a1, extractLayerConfig as b, createLayerID as c, axios as d, extractRoles as e, fetchStyle as f, addTooltipInteraction as g, createAssetID as h, mapEl as i, getProjectionCode as j, isSTACItem as k, extractLayerTimeValues as l, mergeGeojsons as m, fetchApiItems as n, getDatetimeProperty as o, fetchAllStyles as p, findLayer as q, registerProjection as r, findLayersByLayerPrefix as s, replaceLayer as t, generateFeatures as u, getLayers as v, extractLayerLegend as w, extractCollectionUrls as x, generateLinksFromItems as y, revokeCollectionBlobUrls as z };
6296
+ /**
6297
+ * Locate the first sub-schema whose `format` matches by walking `properties`
6298
+ * and the `oneOf` / `allOf` / `anyOf` combinators. Returns the schema path
6299
+ * (array of keys/indices) from the root schema to the matched node, or
6300
+ * undefined if not found. Empty array means the input schema itself matched.
6301
+ *
6302
+ * @param {Record<string, any> | null | undefined} schema
6303
+ * @param {string} [format="bands"]
6304
+ * @returns {(string | number)[] | undefined}
6305
+ */
6306
+ function getBandsProperty(schema, format = "bands") {
6307
+ if (!schema || typeof schema !== "object") return undefined;
6308
+ if (schema.format === format) return [];
6309
+
6310
+ if (schema.properties) {
6311
+ for (const key of Object.keys(schema.properties)) {
6312
+ const sub = getBandsProperty(schema.properties[key], format);
6313
+ if (sub) return ["properties", key, ...sub];
6314
+ }
6315
+ }
6316
+
6317
+ for (const combinator of ["oneOf", "allOf", "anyOf"]) {
6318
+ if (!Array.isArray(schema[combinator])) continue;
6319
+ for (let i = 0; i < schema[combinator].length; i++) {
6320
+ const sub = getBandsProperty(schema[combinator][i], format);
6321
+ if (sub) return [combinator, i, ...sub];
6322
+ }
6323
+ }
6324
+
6325
+ return undefined;
6326
+ }
6327
+
6328
+ /**
6329
+ * Checks whether a GeoZarr layer's bands changed in the jsonform output and,
6330
+ * if so, rebuilds the source with the new 3 selected bands.
6331
+ * Gamma and rescale are handled automatically by `applyUpdatedStyles` via
6332
+ * `updateStyleVariables` — this function only manages source reconstruction.
6333
+ * Uses the existing source constructor to avoid import-version mismatches.
6334
+ *
6335
+ * @param {import("ol/layer/Layer").default} olLayer - Layer from layerConfig:change event
6336
+ * @param {Record<string, any>} jsonformValue - Current jsonform output
6337
+ * @returns {boolean} true if the source was rebuilt
6338
+ */
6339
+ function updateGeoZarrBands(olLayer, jsonformValue) {
6340
+ /** @type {import("@eox/map/src/layers").EOxLayerType<"WebGLTile","GeoZarr">} */
6341
+ const jsonLayer = olLayer.get("_jsonDefinition");
6342
+ const updatedBands = jsonformValue.bands;
6343
+ const isGeoZarr =
6344
+ jsonLayer?.type === "WebGLTile" && jsonLayer?.source?.type === "GeoZarr";
6345
+ if (!jsonLayer || !jsonLayer.source || !isGeoZarr || !updatedBands) {
6346
+ return false;
6347
+ }
6348
+
6349
+ const oldBands = jsonLayer.source?.bands;
6350
+ if (JSON.stringify(updatedBands) === JSON.stringify(oldBands)) {
6351
+ return false;
6352
+ }
6353
+ jsonLayer.source.bands = [...updatedBands];
6354
+ olLayer.setSource(
6355
+ new window.eoxMapAdvancedOlSources.GeoZarr(jsonLayer.source),
6356
+ );
6357
+ return true;
6358
+ }
6359
+
6360
+ /**
6361
+ * Applies titiler upscaling to an XYZ tile URL based on the matched endpoint config.
6362
+ * - titiler v1: appends `@2x` to the `{y}` tile coordinate
6363
+ * - titiler v2: adds `tilesize=512` query parameter (v2 removed the `@2x` suffix)
6364
+ * Plain strings in the config default to v1 behavior for backward compatibility.
6365
+ *
6366
+ * @param {string} url - The XYZ tile URL template
6367
+ * @param {Array<string | { url: string; titilerVersion?: 1 | 2 }>} upscalingEndpoints
6368
+ * @returns {{ url: string; tileSize: [number, number] } | null} null if no endpoint matches
6369
+ */
6370
+ function applyTitilerUpscaling(url, upscalingEndpoints) {
6371
+ const match = upscalingEndpoints.find((entry) => {
6372
+ const endpointUrl = typeof entry === "string" ? entry : entry.url;
6373
+ return url.includes(endpointUrl);
6374
+ });
6375
+
6376
+ if (!match) {
6377
+ return null;
6378
+ }
6379
+
6380
+ const version = typeof match === "string" ? 1 : (match.titilerVersion ?? 1);
6381
+
6382
+ if (version === 2) {
6383
+ const [base, query] = url.split("?");
6384
+ const params = new URLSearchParams(query);
6385
+ params.set("tilesize", "512");
6386
+ return { url: `${base}?${params.toString()}`, tileSize: [512, 512] };
6387
+ }
6388
+
6389
+ return { url: url.replace("{y}", "{y}@2x"), tileSize: [512, 512] };
6390
+ }
6391
+
6392
+ /**
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
+ *
6396
+ * @param {import("stac-ts").StacCollection | null | undefined} collection
6397
+ * @param {Record<string, Record<string, import("@/types").Render>> | undefined} [configRenders]
6398
+ * @returns {Record<string, import("@/types").Render> | undefined}
6399
+ */
6400
+ function resolveRenders(collection, configRenders) {
6401
+ if (collection?.renders) {
6402
+ return /** @type {Record<string, import("@/types").Render>} */ (
6403
+ collection.renders
6404
+ );
6405
+ }
6406
+ return collection?.id ? configRenders?.[collection.id] : undefined;
6407
+ }
6408
+
6409
+ /**
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.
6413
+ * @param {Record<string,any>} obj
6414
+ * @returns {string}
6415
+ */
6416
+ function encodeURLObject(obj) {
6417
+ let str = "";
6418
+ for (const key in obj) {
6419
+ const value = obj[key];
6420
+ if (value === null || value === undefined || value === "") {
6421
+ continue;
6422
+ }
6423
+
6424
+ const valueType = Array.isArray(value) ? "array" : typeof value;
6425
+
6426
+ switch (valueType) {
6427
+ 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
+ }
6441
+ }
6442
+ } else {
6443
+ // For simple arrays, join with commas
6444
+ str += `${key}=${value.join(",")}&`;
6445
+ }
6446
+ break;
6447
+ }
6448
+ case "object": {
6449
+ str += `${key}=${encodeURI(JSON.stringify(value))}&`;
6450
+ break;
6451
+ }
6452
+ default: {
6453
+ str += `${key}=${encodeURIComponent(value)}&`;
6454
+ break;
6455
+ }
6456
+ }
6457
+ }
6458
+ return str;
6459
+ }
6460
+
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 };