@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.
- package/core/client/eodashSTAC/EodashCollection.js +14 -5
- package/core/client/eodashSTAC/createLayers.js +193 -80
- package/core/client/eodashSTAC/helpers.js +330 -39
- package/core/client/eodashSTAC/mosaic.js +10 -4
- package/core/client/store/stac.js +29 -0
- package/core/client/types.ts +5 -0
- package/dist/client/{DashboardLayout-ePbdvUTE.js → DashboardLayout-DEs4f_Vy.js} +2 -2
- package/dist/client/{DynamicWebComponent-g5SdRbjY.js → DynamicWebComponent-PvnV88BA.js} +1 -1
- package/dist/client/EodashChart-D-3AGKZY.js +260 -0
- package/dist/client/{EodashDatePicker-CY1TrsDo.js → EodashDatePicker-Dm_goZzs.js} +3 -3
- package/dist/client/{EodashItemFilter-DzshHDlS.js → EodashItemFilter-CJy3zYzD.js} +2 -2
- package/dist/client/{EodashLayerControl-DMCeMkSd.js → EodashLayerControl-rL8UnpkA.js} +11 -19
- package/dist/client/{EodashLayoutSwitcher-iJ8mX1rF.js → EodashLayoutSwitcher-ZRlESI9O.js} +3 -3
- package/dist/client/{EodashMapBtns-C7Cr7q5Y.js → EodashMapBtns-CG7u2Aq8.js} +34 -33
- package/dist/client/{EodashStacInfo-K7-iuXa7.js → EodashStacInfo-CJhhxthD.js} +2 -2
- package/dist/client/{EodashTools-BEK7e7JM.js → EodashTools-CWyK8QJT.js} +4 -4
- package/dist/client/{ExportState-CqIzfMYQ.js → ExportState-BXhy5TOU.js} +19 -11
- package/dist/client/{Footer-1P0cmBOZ.js → Footer-DZPvnhcT.js} +1 -1
- package/dist/client/{Header-CkkBaBNh.js → Header-CiGsDE_a.js} +2 -2
- package/dist/client/{MobileLayout-BPf49kDU.js → MobileLayout-BJHW7x5V.js} +2 -2
- package/dist/client/{PopUp-DPZq5SWI.js → PopUp-7_XnhvCc.js} +3 -3
- package/dist/client/{ProcessList-DuI6WpS1.js → ProcessList-LL_UCbvc.js} +4 -4
- package/dist/client/{VDivider-Bj9LG_JV.js → VDivider-DWYM0WUr.js} +2 -2
- package/dist/client/{VImg-Ddv5OyMz.js → VImg-B75dgw2K.js} +2 -2
- package/dist/client/{VMain-7yqk_qC3.js → VMain-DvMmOL4f.js} +1 -1
- package/dist/client/{VTooltip-2hAJyEoa.js → VTooltip-ClWHUmWL.js} +2 -2
- package/dist/client/{WidgetsContainer-2DOrlwCB.js → WidgetsContainer-BqaWs4ch.js} +1 -1
- package/dist/client/{asWebComponent-BlFN2lkB.js → asWebComponent-IZanwL-A.js} +239 -91
- package/dist/client/{async-B2H8ZwUK.js → async-CiNIOBVz.js} +31 -29
- package/dist/client/{create-layers-config-u_vn5hPk.js → create-layers-config-3S3y_kZg.js} +1 -1
- package/dist/client/{dialog-transition-BrMkMiNa.js → dialog-transition-c4ZSnzac.js} +2 -2
- package/dist/client/eo-dash.js +1 -1
- package/dist/client/{forwardRefs-DAlror6d.js → forwardRefs-B-c1_6yo.js} +2 -2
- package/dist/client/{handling-B6I1V5eG.js → handling-NvY8Csry.js} +3 -4
- package/dist/client/{helpers-CBqIUSXJ.js → helpers-BFuh19CP.js} +328 -40
- package/dist/client/{index-DhKU6KqM.js → index-1wMAyuNL.js} +17 -9
- package/dist/client/{index-DXLz4sFR.js → index-B8jGJSTA.js} +4 -4
- package/dist/client/{index-DjQlbWbJ.js → index-BKQQJkhy.js} +166 -61
- package/dist/client/{index-BWq1ZKaV.js → index-D0jUyol8.js} +10 -11
- package/dist/client/{index-CJ9UI3ln.js → index-KGxz5Egv.js} +2 -2
- package/dist/client/{mosaic-BFYLEwyx.js → mosaic-vHvSuih6.js} +10 -6
- package/dist/client/templates.js +6 -7
- package/dist/client/{transition-D_W6Wn9v.js → transition-BkyUsZh_.js} +1 -1
- package/dist/types/core/client/eodashSTAC/helpers.d.ts +47 -7
- package/dist/types/core/client/store/stac.d.ts +8 -2
- package/dist/types/core/client/types.d.ts +5 -0
- package/dist/types/templates/explore.d.ts +1 -0
- package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
- package/package.json +5 -5
- package/templates/baseConfig.js +2 -1
- package/templates/compare.js +0 -1
- package/templates/expert.js +2 -3
- package/templates/explore.js +1 -0
- package/templates/lite.js +0 -1
- package/widgets/EodashChart.vue +161 -21
- package/widgets/EodashItemCatalog/methods/map.js +0 -1
- package/widgets/EodashLayerControl.vue +10 -15
- package/widgets/EodashMap/index.vue +3 -1
- package/widgets/EodashMap/methods/btns.js +27 -26
- package/widgets/EodashMap/methods/use-map-loading.js +8 -2
- package/widgets/EodashProcess/index.vue +135 -16
- package/widgets/EodashProcess/methods/composables.js +13 -0
- package/widgets/EodashProcess/methods/outputs.js +0 -1
- package/widgets/EodashProcess/methods/utils.js +29 -27
- package/widgets/ExportState.vue +13 -4
- 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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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
|
|
1101
|
-
*
|
|
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
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
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
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
|
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({
|
|
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
|
});
|
package/core/client/types.ts
CHANGED
|
@@ -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-
|
|
4
|
-
import { V as VMain } from './VMain-
|
|
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-
|
|
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
|
|