@eodash/eodash 5.0.0-alpha.2 → 5.0.0-alpha.2.10
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/LICENSE +21 -0
- package/README.md +36 -3
- package/core/client/App.vue +26 -0
- package/core/client/SuspensedDashboard.ce.vue +105 -0
- package/core/client/asWebComponent.d.ts +23 -0
- package/core/client/asWebComponent.js +12 -0
- package/core/client/components/DashboardLayout.vue +77 -0
- package/core/client/components/DynamicWebComponent.vue +68 -0
- package/core/client/components/ErrorAlert.vue +36 -0
- package/core/{components → client/components}/Footer.vue +34 -15
- package/core/client/components/Header.vue +19 -0
- package/core/client/components/IframeWrapper.vue +11 -0
- package/core/client/components/Loading.vue +37 -0
- package/core/client/components/MobileLayout.vue +109 -0
- package/core/client/composables/DefineEodash.js +67 -0
- package/core/client/composables/DefineWidgets.js +161 -0
- package/core/client/composables/index.js +136 -0
- package/core/client/eodash.js +136 -0
- package/core/client/main.js +2 -0
- package/core/client/plugins/index.js +20 -0
- package/core/client/plugins/vuetify.js +35 -0
- package/core/client/render.js +12 -0
- package/core/client/store/Actions.js +62 -0
- package/core/client/store/States.js +21 -0
- package/core/client/store/index.js +20 -0
- package/core/client/store/stac.js +81 -0
- package/core/client/types.d.ts +287 -0
- package/core/client/utils/eodashSTAC.js +337 -0
- package/core/client/utils/helpers.js +172 -0
- package/core/client/utils/index.js +51 -0
- package/core/{utils → client/utils}/keys.js +2 -2
- package/core/client/views/Dashboard.vue +116 -0
- package/core/client/vite-env.d.ts +39 -0
- package/dist/client/DashboardLayout-C_3q8Y10.js +156 -0
- package/dist/{DynamicWebComponent-BDOQsEcj.js → client/DynamicWebComponent-IiTTLBt-.js} +11 -11
- package/dist/client/EodashDatePicker-DgC1lccp.js +252 -0
- package/dist/{eox-itemfilter-DcQkRD2l.js → client/EodashItemFilter-DoWZGe6r.js} +1515 -1381
- package/dist/client/EodashMap-C96D3LLv.js +114352 -0
- package/dist/client/EodashMapBtns-DDtzV94u.js +50 -0
- package/dist/client/ExportState-CduS2mFs.js +558 -0
- package/dist/client/Footer-DHQcutCv.js +115 -0
- package/dist/client/Header-DC0y7rLK.js +350 -0
- package/dist/{IframeWrapper-DYHQQRUh.js → client/IframeWrapper-BK-10FbS.js} +4 -4
- package/dist/client/MobileLayout-BpLoMY4i.js +945 -0
- package/dist/client/PopUp-CNMCWL0z.js +300 -0
- package/dist/client/VImg-DT7eb8V-.js +291 -0
- package/dist/client/VMain-OenWyy46.js +39 -0
- package/dist/client/VOverlay-x3UXpKhr.js +972 -0
- package/dist/{WidgetsContainer-D_PLP1qf.js → client/WidgetsContainer-qMUwtN0W.js} +26 -19
- package/dist/client/asWebComponent-DQrnLsI2.js +20404 -0
- package/dist/{basedecoder-Qm25PwVp-CHo5Pomv.js → client/basedecoder-DHcBySSe-BmCFNFnw.js} +5 -6
- package/dist/{decoder-kAoyGIq9-HAkjSTCt.js → client/decoder-Cth6J7EK-CtUoHA6r.js} +1 -1
- package/dist/client/deflate-BXt-9JA_-CWfClgpK.js +10 -0
- package/dist/client/eo-dash.js +6 -0
- package/dist/client/eox-jsonform-DCErgjhw.js +17515 -0
- package/dist/client/eox-layercontrol-D0Ht7Mag.js +21298 -0
- package/dist/client/eox-stacinfo-l7ALSV90.js +13969 -0
- package/dist/client/forwardRefs-CpNjL95t.js +185 -0
- package/dist/client/helpers-KCSgmfim.js +1417 -0
- package/dist/{index-8ch8xKfa.js → client/index-DopVrUzM.js} +21 -20
- package/dist/{jpeg-DNfUpLwy-Fjan-04T.js → client/jpeg-BAgeD1d3-oeHbFPUL.js} +5 -6
- package/dist/{lerc-C9VL9kri-DyVxBvsf.js → client/lerc-BgbQqdFI-D986ErVw.js} +37 -39
- package/dist/{lzw-BOMhmEDy-Dboc93VO.js → client/lzw-LAGDNbSC-DkP96qO9.js} +1 -1
- package/dist/{packbits-DaUD6MLm-Bu1PoTGa.js → client/packbits-BlDR4Kj5-C66n1-zr.js} +1 -1
- package/dist/{pako.esm-C3kYPGGQ-BMki8cQY.js → client/pako.esm-CB1uQYY0-DB0PYm1P.js} +6 -12
- package/dist/{raw-CcGKjn8q-DFOt-i8n.js → client/raw-CMGvRjfu-BRi6E4i1.js} +1 -1
- package/dist/{ssrBoot-Cm5pWM14.js → client/ssrBoot-DkjdOWvj.js} +1 -1
- package/dist/client/style.css +5 -0
- package/dist/client/transition-BtJHh7JK.js +34 -0
- package/dist/{webfontloader-CyOFAuFB.js → client/webfontloader-qotgY98I.js} +56 -92
- package/dist/{webimage-D2c098k3-DLj1LQxB.js → client/webimage-BM_pbLN3-L2cGWK5l.js} +1 -1
- package/dist/node/cli.js +20 -0
- package/dist/node/main.js +2 -0
- package/dist/node/types.d.ts +47 -0
- package/package.json +54 -51
- package/widgets/EodashDatePicker.vue +145 -40
- package/widgets/EodashItemFilter.vue +73 -38
- package/widgets/EodashMap.vue +210 -37
- package/widgets/EodashMapBtns.vue +50 -0
- package/widgets/ExportState.vue +112 -0
- package/widgets/PopUp.vue +40 -0
- package/widgets/WidgetsContainer.vue +47 -27
- package/bin/app.js +0 -22
- package/bin/cli.js +0 -62
- package/bin/main.js +0 -9
- package/bin/serverConfig.js +0 -165
- package/bin/types.d.ts +0 -52
- package/bin/utils.js +0 -103
- package/core/App.vue +0 -19
- package/core/SuspensedDashboard.ce.vue +0 -59
- package/core/asWebComponent.d.ts +0 -18
- package/core/asWebComponent.js +0 -12
- package/core/components/DashboardLayout.vue +0 -54
- package/core/components/DynamicWebComponent.vue +0 -68
- package/core/components/Header.vue +0 -14
- package/core/components/IframeWrapper.vue +0 -11
- package/core/components/Loading.vue +0 -27
- package/core/components/MobileLayout.vue +0 -49
- package/core/composables/DefineEodash.js +0 -57
- package/core/composables/DefineWidgets.js +0 -129
- package/core/composables/index.js +0 -219
- package/core/eodash.js +0 -100
- package/core/main.js +0 -2
- package/core/plugins/index.js +0 -18
- package/core/plugins/vuetify.js +0 -30
- package/core/render.js +0 -13
- package/core/store/Actions.js +0 -0
- package/core/store/States.js +0 -21
- package/core/store/index.js +0 -17
- package/core/store/stac.js +0 -63
- package/core/types.d.ts +0 -355
- package/core/utils/eodashSTAC.js +0 -164
- package/core/utils/helpers.js +0 -40
- package/core/utils/index.js +0 -28
- package/core/views/Dashboard.vue +0 -59
- package/core/vite-env.d.ts +0 -31
- package/dist/DashboardLayout-YLfAa8wH.js +0 -168
- package/dist/EodashDatePicker-DlehqSgz.js +0 -1645
- package/dist/EodashItemFilter-TSp7XRGy.js +0 -51
- package/dist/EodashMap-DInb9cfS.js +0 -53405
- package/dist/Footer-CIzHgJbK.js +0 -118
- package/dist/Header-C5PSB8Uc.js +0 -609
- package/dist/MobileLayout-DZmqrpub.js +0 -523
- package/dist/VBtn-CFPhsIX8.js +0 -1106
- package/dist/VMain-deI9jyIf.js +0 -35
- package/dist/asWebComponent-BKe6Spni.js +0 -11323
- package/dist/color-D72nEBIR.js +0 -115
- package/dist/deflate-Be2Arps5-hDqMz3RA.js +0 -10
- package/dist/dimensions-BZkmIizV.js +0 -53
- package/dist/eo-dash.js +0 -6
- package/dist/eox-map-BJ9SIixs.js +0 -36205
- package/dist/eox-stacinfo-B-YrT7Ug.js +0 -13698
- package/dist/forwardRefs-Bxeu9Obx.js +0 -142
- package/dist/index-DIt8GjSR.js +0 -65
- package/dist/style.css +0 -5
- /package/dist/{.gitkeep → client/.gitkeep} +0 -0
- /package/dist/{_commonjsHelpers-DaMA6jEr.js → client/_commonjsHelpers-DaMA6jEr.js} +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { Collection, Item } from "stac-js";
|
|
2
|
+
import { toAbsolute } from "stac-js/src/http.js";
|
|
3
|
+
import {
|
|
4
|
+
createLayersFromDataAssets,
|
|
5
|
+
extractLayerConfig,
|
|
6
|
+
generateFeatures,
|
|
7
|
+
setMapProjFromCol,
|
|
8
|
+
} from "./helpers";
|
|
9
|
+
import axios from "axios";
|
|
10
|
+
import { registerProjection } from "@/store/Actions";
|
|
11
|
+
|
|
12
|
+
export class EodashCollection {
|
|
13
|
+
/** @type {string} */
|
|
14
|
+
#collectionUrl = "";
|
|
15
|
+
/** @type {import("stac-ts").StacCollection | undefined} */
|
|
16
|
+
#collectionStac;
|
|
17
|
+
/**
|
|
18
|
+
* @type {import("stac-ts").StacLink
|
|
19
|
+
* | import("stac-ts").StacItem
|
|
20
|
+
* | undefined}
|
|
21
|
+
*/
|
|
22
|
+
selectedItem;
|
|
23
|
+
|
|
24
|
+
/** @param {string} collectionUrl */
|
|
25
|
+
constructor(collectionUrl) {
|
|
26
|
+
this.#collectionUrl = collectionUrl;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @async
|
|
30
|
+
* @param {import('stac-ts').StacLink | Date} [itemLinkOrDate]
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
createLayersJson = async (itemLinkOrDate) => {
|
|
34
|
+
/** @type {import("stac-ts").StacLink | undefined} */
|
|
35
|
+
let stacItem,
|
|
36
|
+
/** @type {import("stac-ts").StacCollection | undefined} */
|
|
37
|
+
stac;
|
|
38
|
+
// TODO get auxiliary layers from collection
|
|
39
|
+
/** @type {object[]} */
|
|
40
|
+
let layersJson = [];
|
|
41
|
+
|
|
42
|
+
// Load collectionstac if not yet initialized
|
|
43
|
+
if (!this.#collectionStac) {
|
|
44
|
+
const response = await axios.get(this.#collectionUrl);
|
|
45
|
+
stac = await response.data;
|
|
46
|
+
this.#collectionStac = new Collection(stac);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// set availabe map projection
|
|
50
|
+
setMapProjFromCol(this.#collectionStac);
|
|
51
|
+
|
|
52
|
+
if (stac && stac.endpointtype === "GeoDB") {
|
|
53
|
+
// Special handling of point based data
|
|
54
|
+
const allFeatures = generateFeatures(stac.links);
|
|
55
|
+
layersJson.unshift({
|
|
56
|
+
type: "Vector",
|
|
57
|
+
properties: {
|
|
58
|
+
id: stac.id,
|
|
59
|
+
title: stac.title || stac.id,
|
|
60
|
+
},
|
|
61
|
+
source: {
|
|
62
|
+
type: "Vector",
|
|
63
|
+
url: "data:," + encodeURIComponent(JSON.stringify(allFeatures)),
|
|
64
|
+
format: "GeoJSON",
|
|
65
|
+
},
|
|
66
|
+
style: {
|
|
67
|
+
"circle-radius": 5,
|
|
68
|
+
"circle-fill-color": "#00417077",
|
|
69
|
+
"circle-stroke-color": "#004170",
|
|
70
|
+
"fill-color": "#00417077",
|
|
71
|
+
"stroke-color": "#004170",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return layersJson;
|
|
76
|
+
} else {
|
|
77
|
+
if (itemLinkOrDate instanceof Date) {
|
|
78
|
+
// if collectionStac not yet initialized we do it here
|
|
79
|
+
stacItem = this.getItems()?.sort((a, b) => {
|
|
80
|
+
const distanceA = Math.abs(
|
|
81
|
+
new Date(/** @type {number} */ (a.datetime)).getTime() -
|
|
82
|
+
itemLinkOrDate.getTime(),
|
|
83
|
+
);
|
|
84
|
+
const distanceB = Math.abs(
|
|
85
|
+
new Date(/** @type {number} */ (b.datetime)).getTime() -
|
|
86
|
+
itemLinkOrDate.getTime(),
|
|
87
|
+
);
|
|
88
|
+
return distanceA - distanceB;
|
|
89
|
+
})[0];
|
|
90
|
+
this.selectedItem = stacItem;
|
|
91
|
+
} else {
|
|
92
|
+
stacItem = itemLinkOrDate;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stacItemUrl = stacItem
|
|
96
|
+
? toAbsolute(stacItem.href, this.#collectionUrl)
|
|
97
|
+
: this.#collectionUrl;
|
|
98
|
+
stac = await axios.get(stacItemUrl).then((resp) => resp.data);
|
|
99
|
+
|
|
100
|
+
if (!stacItem) {
|
|
101
|
+
// no specific item was requested; render last item
|
|
102
|
+
this.#collectionStac = new Collection(stac);
|
|
103
|
+
const items = this.getItems();
|
|
104
|
+
this.selectedItem = items?.[items.length - 1];
|
|
105
|
+
if (this.selectedItem) {
|
|
106
|
+
layersJson = await this.createLayersJson(this.selectedItem);
|
|
107
|
+
} else {
|
|
108
|
+
if (import.meta.env.DEV) {
|
|
109
|
+
console.warn(
|
|
110
|
+
"[eodash] the selected collection does not include any items",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
} else {
|
|
116
|
+
// specific item was requested
|
|
117
|
+
const item = new Item(stac);
|
|
118
|
+
this.selectedItem = item;
|
|
119
|
+
const title = this.#collectionStac.title || this.#collectionStac.id;
|
|
120
|
+
layersJson.unshift(
|
|
121
|
+
...(await this.buildJsonArray(item, stacItemUrl, title)),
|
|
122
|
+
);
|
|
123
|
+
return layersJson;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
async getExtent() {
|
|
129
|
+
if (!this.#collectionStac) {
|
|
130
|
+
const response = await axios.get(this.#collectionUrl);
|
|
131
|
+
const stac = await response.data;
|
|
132
|
+
this.#collectionStac = new Collection(stac);
|
|
133
|
+
}
|
|
134
|
+
return this.#collectionStac?.extent;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @param {object} properties
|
|
138
|
+
* @param {[string]} roles
|
|
139
|
+
* */
|
|
140
|
+
extractRoles(properties, roles) {
|
|
141
|
+
roles?.forEach((role) => {
|
|
142
|
+
if (role === "visible") {
|
|
143
|
+
// @ts-expect-error visible does not need to exist in properties
|
|
144
|
+
properties.visible = true;
|
|
145
|
+
}
|
|
146
|
+
if (role === "overlay" || role === "baselayer") {
|
|
147
|
+
// @ts-expect-error group not expected to exist in properties
|
|
148
|
+
properties.group = role;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* @param {import("stac-ts").StacItem} item
|
|
154
|
+
* @param {string} itemUrl
|
|
155
|
+
* @param {string} title
|
|
156
|
+
* */
|
|
157
|
+
async buildJsonArray(item, itemUrl, title) {
|
|
158
|
+
const jsonArray = [];
|
|
159
|
+
// TODO: this currently assumes only one layer will be extracted
|
|
160
|
+
// from an item, although it think this is currently true
|
|
161
|
+
// potentially this could return multiple layers
|
|
162
|
+
// TODO: implement other types, such as COG
|
|
163
|
+
|
|
164
|
+
// I propose following approach, we "manually" create configurations
|
|
165
|
+
// for the rendering options we know and expect.
|
|
166
|
+
// If we don't find any we fallback to using the STAC ol item that
|
|
167
|
+
// will try to extract anything it supports but for which we have
|
|
168
|
+
// less control.
|
|
169
|
+
let dataAssets = /** @type {Record<string,import('stac-ts').StacAsset>} */ ({});
|
|
170
|
+
if (item.assets) {
|
|
171
|
+
dataAssets = Object.keys(item.assets).reduce((data, ast) => {
|
|
172
|
+
if (item.assets[ast].roles?.includes("data")) {
|
|
173
|
+
data[ast] = item.assets[ast];
|
|
174
|
+
}
|
|
175
|
+
return data;
|
|
176
|
+
}, /** @type {Record<string,import('stac-ts').StacAsset>} */ ({}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { layerConfig, style } = extractLayerConfig(
|
|
180
|
+
await this.fetchStyle(item, itemUrl),
|
|
181
|
+
);
|
|
182
|
+
const wmsArray = item.links.filter((l) => l.rel === "wms");
|
|
183
|
+
const xyzArray = item.links.filter((l) => l.rel === "xyz");
|
|
184
|
+
const fallbackToStac = item.links.find((l) => l.rel === "wmts");
|
|
185
|
+
|
|
186
|
+
// TODO: add capability to find projection in item
|
|
187
|
+
await registerProjection(
|
|
188
|
+
/** @type {number | undefined} */ (item?.["proj:epsg"]),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (wmsArray.length > 0) {
|
|
192
|
+
wmsArray.forEach((link) => {
|
|
193
|
+
let json = {
|
|
194
|
+
type: "Tile",
|
|
195
|
+
properties: {
|
|
196
|
+
id: link.id || item.id,
|
|
197
|
+
title: title || link.title || item.id,
|
|
198
|
+
},
|
|
199
|
+
source: {
|
|
200
|
+
// TODO: if no projection information is provided we should
|
|
201
|
+
// assume one, else for WMS requests it will try to get
|
|
202
|
+
// the map projection that might not be supported
|
|
203
|
+
// projection: projDef ? projDef : "EPSG:4326",
|
|
204
|
+
type: "TileWMS",
|
|
205
|
+
url: link.href,
|
|
206
|
+
params: {
|
|
207
|
+
LAYERS: link["wms:layers"],
|
|
208
|
+
TILED: true,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
this.extractRoles(
|
|
213
|
+
json.properties,
|
|
214
|
+
/** @type {[string]} */ (link.roles),
|
|
215
|
+
);
|
|
216
|
+
if ("wms:dimensions" in link) {
|
|
217
|
+
// Expand all dimensions into the params attribute
|
|
218
|
+
json.source.params = Object.assign(
|
|
219
|
+
json.source.params,
|
|
220
|
+
link["wms:dimensions"],
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
jsonArray.push(json);
|
|
224
|
+
});
|
|
225
|
+
} else if (xyzArray.length > 0) {
|
|
226
|
+
xyzArray.forEach((link) => {
|
|
227
|
+
let json = {
|
|
228
|
+
type: "Tile",
|
|
229
|
+
properties: {
|
|
230
|
+
id: link.id || item.id,
|
|
231
|
+
title: title || link.title || item.id,
|
|
232
|
+
roles: link.roles,
|
|
233
|
+
},
|
|
234
|
+
source: {
|
|
235
|
+
type: "XYZ",
|
|
236
|
+
url: link.href,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
this.extractRoles(
|
|
240
|
+
json.properties,
|
|
241
|
+
/** @type {[string]} */ (link.roles),
|
|
242
|
+
);
|
|
243
|
+
jsonArray.push(json);
|
|
244
|
+
});
|
|
245
|
+
} else if (fallbackToStac) {
|
|
246
|
+
jsonArray.push({
|
|
247
|
+
type: "STAC",
|
|
248
|
+
displayWebMapLink: true,
|
|
249
|
+
displayFootprint: false,
|
|
250
|
+
data: item,
|
|
251
|
+
properties: {
|
|
252
|
+
id: item.id,
|
|
253
|
+
title: title || item.id,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
} else if (Object.keys(dataAssets).length) {
|
|
257
|
+
jsonArray.push(
|
|
258
|
+
...(await createLayersFromDataAssets(
|
|
259
|
+
this.#collectionStac?.title || item.id,
|
|
260
|
+
this.#collectionStac?.title || item.id,
|
|
261
|
+
dataAssets,
|
|
262
|
+
style,
|
|
263
|
+
layerConfig,
|
|
264
|
+
)),
|
|
265
|
+
);
|
|
266
|
+
} else if (item.geometry) {
|
|
267
|
+
// fall back to rendering the feature
|
|
268
|
+
jsonArray.push({
|
|
269
|
+
type: "Vector",
|
|
270
|
+
source: {
|
|
271
|
+
type: "Vector",
|
|
272
|
+
url: "data:," + encodeURIComponent(JSON.stringify(item.geometry)),
|
|
273
|
+
format: "GeoJSON",
|
|
274
|
+
},
|
|
275
|
+
properties: {
|
|
276
|
+
id: item.id,
|
|
277
|
+
title: this.#collectionStac?.title || item.id,
|
|
278
|
+
layerConfig: {
|
|
279
|
+
...layerConfig,
|
|
280
|
+
style,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return jsonArray;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
getItems() {
|
|
290
|
+
return (
|
|
291
|
+
this.#collectionStac?.links
|
|
292
|
+
.filter((i) => i.rel === "item")
|
|
293
|
+
// sort by `datetime`, where oldest is first in array
|
|
294
|
+
.sort((a, b) =>
|
|
295
|
+
/** @type {number} */ (a.datetime) <
|
|
296
|
+
/** @type {number} */ (b.datetime)
|
|
297
|
+
? -1
|
|
298
|
+
: 1,
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @param {import("stac-ts").StacItem} item
|
|
305
|
+
* @param {string} itemUrl
|
|
306
|
+
**/
|
|
307
|
+
async fetchStyle(item, itemUrl) {
|
|
308
|
+
const styleLink = item.links.find((link) => link.rel.includes("style"));
|
|
309
|
+
if (styleLink) {
|
|
310
|
+
let url = "";
|
|
311
|
+
if (styleLink.href.startsWith("http")) {
|
|
312
|
+
url = styleLink.href;
|
|
313
|
+
} else {
|
|
314
|
+
url = toAbsolute(styleLink.href, itemUrl);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** @type {import("ol/layer/WebGLTile").Style & {jsonform?:object}} */
|
|
318
|
+
const styleJson = await axios.get(url).then((resp) => resp.data);
|
|
319
|
+
return styleJson;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getDates() {
|
|
324
|
+
return (
|
|
325
|
+
this.#collectionStac?.links
|
|
326
|
+
.filter((i) => i.rel === "item")
|
|
327
|
+
// sort by `datetime`, where oldest is first in array
|
|
328
|
+
.sort((a, b) =>
|
|
329
|
+
/** @type {number} */ (a.datetime) <
|
|
330
|
+
/** @type {number} */ (b.datetime)
|
|
331
|
+
? -1
|
|
332
|
+
: 1,
|
|
333
|
+
)
|
|
334
|
+
.map((i) => new Date(/** @type {number} */ (i.datetime)))
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { changeMapProjection, registerProjection } from "@/store/Actions";
|
|
2
|
+
import { availableMapProjection } from "@/store/States";
|
|
3
|
+
import { toAbsolute } from "stac-js/src/http.js";
|
|
4
|
+
|
|
5
|
+
/** @param {import("stac-ts").StacLink[]} links */
|
|
6
|
+
export function generateFeatures(links) {
|
|
7
|
+
/**
|
|
8
|
+
* @type {{
|
|
9
|
+
* type: string;
|
|
10
|
+
* geometry: {
|
|
11
|
+
* type: string;
|
|
12
|
+
* coordinates: [number, number];
|
|
13
|
+
* };
|
|
14
|
+
* }[]}
|
|
15
|
+
*/
|
|
16
|
+
const features = [];
|
|
17
|
+
links.forEach((element) => {
|
|
18
|
+
if (element.rel === "item" && "latlng" in element) {
|
|
19
|
+
const [lat, lon] = /** @type {string} */ (element.latlng)
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((it) => Number(it));
|
|
22
|
+
features.push({
|
|
23
|
+
type: "Feature",
|
|
24
|
+
geometry: {
|
|
25
|
+
type: "Point",
|
|
26
|
+
coordinates: [lon, lat],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const geojsonObject = {
|
|
32
|
+
type: "FeatureCollection",
|
|
33
|
+
crs: {
|
|
34
|
+
type: "name",
|
|
35
|
+
properties: {
|
|
36
|
+
name: "EPSG:4326",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
features,
|
|
40
|
+
};
|
|
41
|
+
return geojsonObject;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param { import("ol/layer/WebGLTile").Style & { jsonform?: object } } [style] */
|
|
45
|
+
export function extractLayerConfig(style) {
|
|
46
|
+
/** @type {Record<string,unknown>} */
|
|
47
|
+
let layerConfig = {};
|
|
48
|
+
if (style?.jsonform) {
|
|
49
|
+
layerConfig = { schema: style.jsonform, type: "style" };
|
|
50
|
+
delete style?.jsonform;
|
|
51
|
+
}
|
|
52
|
+
return { layerConfig, style };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} id
|
|
56
|
+
* @param {string} title
|
|
57
|
+
* @param {Record<string,import("stac-ts").StacAsset>} assets
|
|
58
|
+
* @param {import("ol/layer/WebGLTile").Style} [style]
|
|
59
|
+
* @param {Record<string, unknown>} [layerConfig]
|
|
60
|
+
**/
|
|
61
|
+
export async function createLayersFromDataAssets(
|
|
62
|
+
id,
|
|
63
|
+
title,
|
|
64
|
+
assets,
|
|
65
|
+
style,
|
|
66
|
+
layerConfig,
|
|
67
|
+
) {
|
|
68
|
+
let jsonArray = [];
|
|
69
|
+
let geoTIFFSources = [];
|
|
70
|
+
for (const ast in assets) {
|
|
71
|
+
// register projection if exists
|
|
72
|
+
await registerProjection(
|
|
73
|
+
/** @type {number | undefined} */ (assets[ast]?.["proj:epsg"]),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (assets[ast]?.type === "application/geo+json") {
|
|
77
|
+
jsonArray.push({
|
|
78
|
+
type: "Vector",
|
|
79
|
+
source: {
|
|
80
|
+
type: "Vector",
|
|
81
|
+
url: assets[ast].href,
|
|
82
|
+
format: "GeoJSON",
|
|
83
|
+
},
|
|
84
|
+
properties: {
|
|
85
|
+
id,
|
|
86
|
+
title,
|
|
87
|
+
layerConfig: {
|
|
88
|
+
...layerConfig,
|
|
89
|
+
style,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
} else if (assets[ast]?.type === "image/tiff") {
|
|
94
|
+
geoTIFFSources.push({ url: assets[ast].href });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (geoTIFFSources.length) {
|
|
98
|
+
jsonArray.push({
|
|
99
|
+
type: "WebGLTile",
|
|
100
|
+
source: {
|
|
101
|
+
type: "GeoTIFF",
|
|
102
|
+
normalize: style?.variables ? false : true,
|
|
103
|
+
sources: geoTIFFSources,
|
|
104
|
+
},
|
|
105
|
+
properties: {
|
|
106
|
+
id,
|
|
107
|
+
title,
|
|
108
|
+
layerConfig,
|
|
109
|
+
},
|
|
110
|
+
style,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return jsonArray;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* checks if there's a projection on the Collection and
|
|
118
|
+
* updates {@link availableMapProjection}
|
|
119
|
+
* @param {import('stac-ts').StacCollection} STAcCollection
|
|
120
|
+
*/
|
|
121
|
+
export const setMapProjFromCol = (STAcCollection) => {
|
|
122
|
+
// if a projection exists on the collection level
|
|
123
|
+
if (STAcCollection?.["proj:epsg"]) {
|
|
124
|
+
if (
|
|
125
|
+
availableMapProjection.value &&
|
|
126
|
+
availableMapProjection.value !== STAcCollection?.["proj:epsg"]
|
|
127
|
+
) {
|
|
128
|
+
changeMapProjection(
|
|
129
|
+
/** @type {number} */
|
|
130
|
+
(STAcCollection["proj:epsg"]),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
// set it for `EodashMapBtns`
|
|
134
|
+
availableMapProjection.value = /** @type {string} */ (
|
|
135
|
+
STAcCollection["proj:epsg"]
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
// reset to default projection
|
|
139
|
+
changeMapProjection((availableMapProjection.value = ""));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Function to extract collection urls from an indicator
|
|
145
|
+
* @param {import("stac-ts").StacCatalog
|
|
146
|
+
* | import("stac-ts").StacCollection
|
|
147
|
+
* | import("stac-ts").StacItem
|
|
148
|
+
* | null
|
|
149
|
+
* } stacObject
|
|
150
|
+
* @param {string} basepath
|
|
151
|
+
* @returns {string[]}
|
|
152
|
+
*/
|
|
153
|
+
export function extractCollectionUrls(stacObject, basepath) {
|
|
154
|
+
const collectionUrls = [];
|
|
155
|
+
// Support for two structure types, flat and indicator, simplified here:
|
|
156
|
+
// Flat assumes Catalog-Collection-Item
|
|
157
|
+
// Indicator assumes Catalog-Collection-Collection-Item
|
|
158
|
+
// TODO: this is not the most stable test approach,
|
|
159
|
+
// we should discuss potential other approaches
|
|
160
|
+
|
|
161
|
+
if (stacObject?.links && stacObject?.links[1].rel === "item") {
|
|
162
|
+
collectionUrls.push(basepath);
|
|
163
|
+
} else if (stacObject?.links[1].rel === "child") {
|
|
164
|
+
// TODO: Iterate through all children to create collections
|
|
165
|
+
stacObject.links.forEach((link) => {
|
|
166
|
+
if (link.rel === "child") {
|
|
167
|
+
collectionUrls.push(toAbsolute(link.href, basepath));
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return collectionUrls;
|
|
172
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads font in the app using `webfontloader`
|
|
3
|
+
*
|
|
4
|
+
* @param {string} [family]
|
|
5
|
+
* @param {string} [link]
|
|
6
|
+
* @param {boolean} [isWebComponent]
|
|
7
|
+
* @returns {Promise<string>} - Font family name
|
|
8
|
+
* @see {@link "https://github.com/typekit/webfontloader"}
|
|
9
|
+
*/
|
|
10
|
+
export const loadFont = async (
|
|
11
|
+
family = "",
|
|
12
|
+
link = "",
|
|
13
|
+
isWebComponent = false,
|
|
14
|
+
) => {
|
|
15
|
+
if (family && link) {
|
|
16
|
+
const WebFontLoader = (await import("webfontloader")).default;
|
|
17
|
+
WebFontLoader.load({
|
|
18
|
+
classes: false,
|
|
19
|
+
custom: {
|
|
20
|
+
// Use FVD notation to include families https://github.com/typekit/fvd
|
|
21
|
+
families: [family],
|
|
22
|
+
// Path to stylesheet that defines font-face
|
|
23
|
+
urls: [link],
|
|
24
|
+
},
|
|
25
|
+
fontactive(familyName, _fvd) {
|
|
26
|
+
const stylesheet = new CSSStyleSheet();
|
|
27
|
+
const styles = isWebComponent
|
|
28
|
+
? `eo-dash {font-family: ${familyName};}`
|
|
29
|
+
: `* {font-family: ${familyName};}`;
|
|
30
|
+
stylesheet.replaceSync(styles);
|
|
31
|
+
document.adoptedStyleSheets.push(stylesheet);
|
|
32
|
+
},
|
|
33
|
+
fontinactive(familyName, _fvd) {
|
|
34
|
+
throw new Error(`error loading font: ${familyName}`);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return family;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} text
|
|
43
|
+
* @param {import("vue").Ref<boolean>} showIcon
|
|
44
|
+
**/
|
|
45
|
+
export const copyToClipBoard = async (text, showIcon) => {
|
|
46
|
+
await navigator.clipboard.writeText(text);
|
|
47
|
+
showIcon.value = true;
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
showIcon.value = false;
|
|
50
|
+
}, 2000);
|
|
51
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<HeaderComponent ref="headerRef" v-if="!eodash.brand.noLayout" />
|
|
3
|
+
<ErrorAlert v-model="error" />
|
|
4
|
+
<Suspense>
|
|
5
|
+
<TemplateComponent
|
|
6
|
+
@vue:mounted="onTemplateMount?.(hiddenElements)"
|
|
7
|
+
class="template"
|
|
8
|
+
/>
|
|
9
|
+
<template #fallback>
|
|
10
|
+
<div class="loading-container">
|
|
11
|
+
<Loading />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
</Suspense>
|
|
15
|
+
<FooterComponent ref="footerRef" v-if="!eodash.brand.noLayout" />
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup>
|
|
19
|
+
import { useEodashRuntime } from "@/composables/DefineEodash";
|
|
20
|
+
import { useURLSearchParametersSync, useUpdateTheme } from "@/composables";
|
|
21
|
+
import { useSTAcStore } from "@/store/stac";
|
|
22
|
+
import { defineAsyncComponent, onErrorCaptured, onMounted, ref } from "vue";
|
|
23
|
+
import { useDisplay, useLayout } from "vuetify/lib/framework.mjs";
|
|
24
|
+
import { loadFont } from "@/utils";
|
|
25
|
+
import Loading from "@/components/Loading.vue";
|
|
26
|
+
import ErrorAlert from "@/components/ErrorAlert.vue";
|
|
27
|
+
|
|
28
|
+
const props = defineProps({
|
|
29
|
+
config: {
|
|
30
|
+
type: String,
|
|
31
|
+
},
|
|
32
|
+
onTemplateMount: {
|
|
33
|
+
type: Function,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const eodash = await useEodashRuntime(props.config);
|
|
37
|
+
|
|
38
|
+
useURLSearchParametersSync();
|
|
39
|
+
|
|
40
|
+
const theme = useUpdateTheme("dashboardTheme", eodash.brand?.theme);
|
|
41
|
+
theme.global.name.value = "dashboardTheme";
|
|
42
|
+
|
|
43
|
+
await loadFont(
|
|
44
|
+
eodash.brand?.font?.family,
|
|
45
|
+
eodash.brand?.font?.link,
|
|
46
|
+
!!props.onTemplateMount,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const { loadSTAC } = useSTAcStore();
|
|
50
|
+
await loadSTAC();
|
|
51
|
+
|
|
52
|
+
const { smAndDown } = useDisplay();
|
|
53
|
+
const TemplateComponent = smAndDown.value
|
|
54
|
+
? defineAsyncComponent(() => import(`@/components/MobileLayout.vue`))
|
|
55
|
+
: defineAsyncComponent(() => import(`@/components/DashboardLayout.vue`));
|
|
56
|
+
|
|
57
|
+
const HeaderComponent = defineAsyncComponent(
|
|
58
|
+
() => import(`@/components/Header.vue`),
|
|
59
|
+
);
|
|
60
|
+
const FooterComponent = defineAsyncComponent(
|
|
61
|
+
() => import(`@/components/Footer.vue`),
|
|
62
|
+
);
|
|
63
|
+
const { mainRect } = useLayout();
|
|
64
|
+
const templateHeight = eodash.brand.noLayout
|
|
65
|
+
? props.onTemplateMount
|
|
66
|
+
? "100%"
|
|
67
|
+
: "90dvh"
|
|
68
|
+
: `calc(100dvh - ${mainRect.value["top"] + mainRect.value["bottom"]}px)`;
|
|
69
|
+
|
|
70
|
+
const headerRef = ref(null);
|
|
71
|
+
/**
|
|
72
|
+
* @type {import("vue").Ref<InstanceType<
|
|
73
|
+
* typeof import("@/components/Footer.vue").default
|
|
74
|
+
* > | null>}
|
|
75
|
+
*/
|
|
76
|
+
const footerRef = ref(null);
|
|
77
|
+
|
|
78
|
+
const hiddenElements = [headerRef, footerRef];
|
|
79
|
+
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
if (props.onTemplateMount && !eodash.brand.noLayout) {
|
|
82
|
+
hiddenElements.forEach((element) => {
|
|
83
|
+
/** @type {HTMLElement} */
|
|
84
|
+
(
|
|
85
|
+
/** @type {import("vue").ComponentPublicInstance} */
|
|
86
|
+
(element.value).$el
|
|
87
|
+
).style.opacity = "0";
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const error = ref("");
|
|
93
|
+
onErrorCaptured((e, comp, info) => {
|
|
94
|
+
error.value = `
|
|
95
|
+
${e}.
|
|
96
|
+
component: ${comp?.$.type.name}.
|
|
97
|
+
info: ${info}.
|
|
98
|
+
`;
|
|
99
|
+
});
|
|
100
|
+
</script>
|
|
101
|
+
<style>
|
|
102
|
+
html {
|
|
103
|
+
overflow: hidden !important;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.template {
|
|
107
|
+
height: v-bind("templateHeight");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.loading-container {
|
|
111
|
+
height: 100dvh;
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
}
|
|
116
|
+
</style>
|