@eodash/eodash 5.0.0-alpha.2.1 → 5.0.0-alpha.2.11

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 (138) hide show
  1. package/README.md +36 -3
  2. package/core/client/App.vue +26 -0
  3. package/core/client/SuspensedDashboard.ce.vue +105 -0
  4. package/core/client/asWebComponent.d.ts +23 -0
  5. package/core/client/asWebComponent.js +12 -0
  6. package/core/client/components/DashboardLayout.vue +77 -0
  7. package/core/client/components/DynamicWebComponent.vue +68 -0
  8. package/core/client/components/ErrorAlert.vue +36 -0
  9. package/core/{components → client/components}/Footer.vue +34 -15
  10. package/core/client/components/Header.vue +19 -0
  11. package/core/client/components/IframeWrapper.vue +11 -0
  12. package/core/client/components/Loading.vue +37 -0
  13. package/core/client/components/MobileLayout.vue +109 -0
  14. package/core/client/composables/DefineEodash.js +67 -0
  15. package/core/client/composables/DefineWidgets.js +161 -0
  16. package/core/client/composables/index.js +155 -0
  17. package/core/client/eodash.js +129 -0
  18. package/core/client/main.js +2 -0
  19. package/core/client/plugins/index.js +20 -0
  20. package/core/client/plugins/vuetify.js +35 -0
  21. package/core/client/render.js +12 -0
  22. package/core/client/store/Actions.js +38 -0
  23. package/core/client/store/States.js +24 -0
  24. package/core/client/store/index.js +20 -0
  25. package/core/client/store/stac.js +81 -0
  26. package/core/client/types.d.ts +287 -0
  27. package/core/client/utils/createLayers.js +131 -0
  28. package/core/client/utils/eodashSTAC.js +259 -0
  29. package/core/client/utils/helpers.js +152 -0
  30. package/core/client/utils/index.js +51 -0
  31. package/core/{utils → client/utils}/keys.js +2 -2
  32. package/core/client/views/Dashboard.vue +116 -0
  33. package/core/client/vite-env.d.ts +39 -0
  34. package/dist/client/DashboardLayout-D5c2wAGC.js +156 -0
  35. package/dist/{DynamicWebComponent-BrHVTesn.js → client/DynamicWebComponent-CBuMCUIj.js} +11 -11
  36. package/dist/client/EodashDatePicker-D0qdMMTE.js +252 -0
  37. package/dist/client/EodashItemFilter-CTRnGRRS.js +10067 -0
  38. package/dist/client/EodashLayerControl-Bpm0BwcW.js +31499 -0
  39. package/dist/client/EodashMap-DaMtQefq.js +114367 -0
  40. package/dist/client/EodashMapBtns-B5z8h8mr.js +50 -0
  41. package/dist/client/ExportState-Bp3n6gba.js +558 -0
  42. package/dist/client/Footer-BROOTKhV.js +115 -0
  43. package/dist/client/Header-C_fKVTsg.js +350 -0
  44. package/dist/{IframeWrapper-Dd9zrX9s.js → client/IframeWrapper-32Q3DBYr.js} +4 -4
  45. package/dist/client/MobileLayout-TVGrYpNT.js +945 -0
  46. package/dist/client/PopUp-BJzmeeKG.js +300 -0
  47. package/dist/client/VImg-BT4Vb0UT.js +291 -0
  48. package/dist/client/VMain-BDVUXNEk.js +39 -0
  49. package/dist/client/VOverlay-Ck_4Lxcb.js +972 -0
  50. package/dist/{WidgetsContainer-DxfCu0I3.js → client/WidgetsContainer-DjjX_n1U.js} +25 -18
  51. package/dist/client/asWebComponent-wn9bbgx9.js +20404 -0
  52. package/dist/{basedecoder-Qm25PwVp-CHo5Pomv.js → client/basedecoder-DHcBySSe-BmCFNFnw.js} +5 -6
  53. package/dist/{decoder-kAoyGIq9-BBR5CgzS.js → client/decoder-Cth6J7EK-CLlwAswr.js} +1 -1
  54. package/dist/client/deflate-BXt-9JA_-CWfClgpK.js +10 -0
  55. package/dist/client/eo-dash.js +6 -0
  56. package/dist/client/eox-stacinfo-l7ALSV90.js +13969 -0
  57. package/dist/client/forwardRefs--PLRH8zp.js +185 -0
  58. package/dist/client/helpers-CoqJPYdE.js +1391 -0
  59. package/dist/{index-DTkOfh2g.js → client/index-Dt0NRXIq.js} +21 -20
  60. package/dist/{jpeg-DNfUpLwy-Fjan-04T.js → client/jpeg-BAgeD1d3-oeHbFPUL.js} +5 -6
  61. package/dist/{lerc-C9VL9kri-cIdbW0sg.js → client/lerc-BgbQqdFI-CCpDWUne.js} +37 -39
  62. package/dist/{lzw-BOMhmEDy-Dboc93VO.js → client/lzw-LAGDNbSC-DkP96qO9.js} +1 -1
  63. package/dist/{packbits-DaUD6MLm-Bu1PoTGa.js → client/packbits-BlDR4Kj5-C66n1-zr.js} +1 -1
  64. package/dist/{pako.esm-C3kYPGGQ-BMki8cQY.js → client/pako.esm-CB1uQYY0-DB0PYm1P.js} +6 -12
  65. package/dist/{raw-CcGKjn8q-DFOt-i8n.js → client/raw-CMGvRjfu-BRi6E4i1.js} +1 -1
  66. package/dist/{ssrBoot-D-b4-M19.js → client/ssrBoot-DjZDIkG_.js} +1 -1
  67. package/dist/client/style.css +5 -0
  68. package/dist/client/transition-C8OQt_kp.js +34 -0
  69. package/dist/{webfontloader-CyOFAuFB.js → client/webfontloader-qotgY98I.js} +56 -92
  70. package/dist/{webimage-D2c098k3-DLj1LQxB.js → client/webimage-BM_pbLN3-L2cGWK5l.js} +1 -1
  71. package/dist/node/cli.js +20 -0
  72. package/dist/node/main.js +2 -0
  73. package/dist/node/types.d.ts +47 -0
  74. package/package.json +57 -51
  75. package/widgets/EodashDatePicker.vue +146 -40
  76. package/widgets/EodashItemFilter.vue +81 -41
  77. package/widgets/EodashLayerControl.vue +16 -0
  78. package/widgets/EodashMap.vue +186 -42
  79. package/widgets/EodashMapBtns.vue +50 -0
  80. package/widgets/ExportState.vue +112 -0
  81. package/widgets/PopUp.vue +40 -0
  82. package/widgets/WidgetsContainer.vue +47 -27
  83. package/bin/app.js +0 -22
  84. package/bin/cli.js +0 -62
  85. package/bin/main.js +0 -9
  86. package/bin/serverConfig.js +0 -165
  87. package/bin/types.d.ts +0 -52
  88. package/bin/utils.js +0 -103
  89. package/core/App.vue +0 -19
  90. package/core/SuspensedDashboard.ce.vue +0 -65
  91. package/core/asWebComponent.d.ts +0 -18
  92. package/core/asWebComponent.js +0 -12
  93. package/core/components/DashboardLayout.vue +0 -44
  94. package/core/components/DynamicWebComponent.vue +0 -68
  95. package/core/components/Header.vue +0 -14
  96. package/core/components/IframeWrapper.vue +0 -11
  97. package/core/components/Loading.vue +0 -27
  98. package/core/components/MobileLayout.vue +0 -49
  99. package/core/composables/DefineEodash.js +0 -57
  100. package/core/composables/DefineWidgets.js +0 -129
  101. package/core/composables/index.js +0 -219
  102. package/core/eodash.js +0 -100
  103. package/core/main.js +0 -2
  104. package/core/plugins/index.js +0 -18
  105. package/core/plugins/vuetify.js +0 -30
  106. package/core/render.js +0 -13
  107. package/core/store/Actions.js +0 -0
  108. package/core/store/States.js +0 -21
  109. package/core/store/index.js +0 -17
  110. package/core/store/stac.js +0 -63
  111. package/core/types.d.ts +0 -356
  112. package/core/utils/eodashSTAC.js +0 -164
  113. package/core/utils/helpers.js +0 -40
  114. package/core/utils/index.js +0 -28
  115. package/core/views/Dashboard.vue +0 -59
  116. package/core/vite-env.d.ts +0 -31
  117. package/dist/DashboardLayout-Dp8AnYD9.js +0 -141
  118. package/dist/EodashDatePicker-D_udZ26j.js +0 -1645
  119. package/dist/EodashItemFilter-BLAGx2UD.js +0 -51
  120. package/dist/EodashMap-Bxl_aqY2.js +0 -53405
  121. package/dist/Footer-AohCH8U7.js +0 -118
  122. package/dist/Header-DC50S6GR.js +0 -609
  123. package/dist/MobileLayout-CgToA7Gp.js +0 -523
  124. package/dist/VBtn-Bz7ruRUg.js +0 -1106
  125. package/dist/VMain-BHfWJU2j.js +0 -35
  126. package/dist/asWebComponent-C8rb3b1D.js +0 -11323
  127. package/dist/color-DpYEub1f.js +0 -115
  128. package/dist/deflate-Be2Arps5-hDqMz3RA.js +0 -10
  129. package/dist/dimensions-CJaGeSrj.js +0 -53
  130. package/dist/eo-dash.js +0 -6
  131. package/dist/eox-itemfilter-DcQkRD2l.js +0 -7537
  132. package/dist/eox-map-BJ9SIixs.js +0 -36205
  133. package/dist/eox-stacinfo-B-YrT7Ug.js +0 -13698
  134. package/dist/forwardRefs-Bxeu9Obx.js +0 -142
  135. package/dist/index-Sa2Vg_gx.js +0 -65
  136. package/dist/style.css +0 -5
  137. /package/dist/{.gitkeep → client/.gitkeep} +0 -0
  138. /package/dist/{_commonjsHelpers-DaMA6jEr.js → client/_commonjsHelpers-DaMA6jEr.js} +0 -0
@@ -1,56 +1,162 @@
1
1
  <template>
2
- <span class="fill-height fill-width align-center justify-center">
3
- <div v-if="inline" class="fill-height fill-width">
4
-
5
- <v-text-field ref="inlineDatePicker" base-color="primary" class="fill-height fill-width pa-2 align-center"
6
- type="date" bg-color="surface" color="primary" density="comfortable" label="Select Date" v-model="currentDate"
7
- variant="plain" hide-details />
8
- </div>
9
- <v-date-picker v-else ref="datePicker" :width="width" :height="height" hide-header v-model="currentDate"
10
- color="primary" bg-color="surface" location="center" class="overflow-auto fill-height fill-width"
11
- position="relative" show-adjacent-months></v-date-picker>
12
- </span>
2
+ <VCDatePicker v-model="currentDate" :masks="masks" :attributes="attributes">
3
+ <template #default="{ inputValue, inputEvents }">
4
+ <div
5
+ class="flex rounded-lg border border-gray-300 dark:border-gray-600"
6
+ style="margin: 2px"
7
+ >
8
+ <input
9
+ :value="inputValue"
10
+ v-on="inputEvents"
11
+ style="margin: 1px"
12
+ class="flex-grow px-1 py-1 bg-white dark:bg-gray-700"
13
+ />
14
+ </div>
15
+ </template>
16
+ </VCDatePicker>
17
+ <v-row align="center" justify="center" style="margin-top: 6px">
18
+ <v-btn
19
+ style="padding: 0px; margin-right: 4px"
20
+ density="compact"
21
+ v-tooltip:bottom="'Set date to oldest available dataset'"
22
+ @click="jumpDate(true)"
23
+ >
24
+ <v-icon :icon="[mdiRayEndArrow]" />
25
+ </v-btn>
26
+ <v-btn
27
+ style="padding: 0px; margin-left: 4px"
28
+ density="compact"
29
+ v-tooltip:bottom="'Set date to latest available dataset'"
30
+ @click="jumpDate(false)"
31
+ >
32
+ <v-icon :icon="[mdiRayStartArrow]" />
33
+ </v-btn>
34
+ </v-row>
13
35
  </template>
36
+
14
37
  <script setup>
15
- import { computed, ref, onMounted } from "vue";
16
- import { datetime } from "@/store/States"
38
+ import { DatePicker as VCDatePicker } from "v-calendar";
39
+ import "v-calendar/style.css";
40
+ import { computed, ref, onMounted, watch, inject } from "vue";
41
+ import { eodashKey } from "@/utils/keys";
42
+ import { toAbsolute } from "stac-js/src/http.js";
43
+ import { storeToRefs } from "pinia";
44
+ import { useSTAcStore } from "@/store/stac";
45
+ import { datetime } from "@/store/States";
46
+ import { mdiRayStartArrow, mdiRayEndArrow } from "@mdi/js";
47
+ import { extractCollectionUrls } from "@/utils/helpers";
48
+ import axios from "axios";
17
49
 
18
- const props = defineProps({
19
- inline: {
20
- type: Boolean
50
+ /**
51
+ * @param {boolean} reverse
52
+ */
53
+ function jumpDate(reverse) {
54
+ if (attributes.value && attributes.value.length > 0) {
55
+ // We have potentially multiple collections we need to iterate (currently only one)
56
+ let latestDateMS = reverse ? Infinity : -Infinity;
57
+ attributes.value.forEach((coll) => {
58
+ if (coll?.dates) {
59
+ coll.dates.forEach((d) => {
60
+ // TODO: we need to handle time ranges and other options here
61
+ if (d instanceof Date) {
62
+ if (
63
+ (!reverse && d.getTime() > latestDateMS) ||
64
+ (reverse && d.getTime() < latestDateMS)
65
+ ) {
66
+ latestDateMS = d.getTime();
67
+ }
68
+ }
69
+ });
70
+ }
71
+ });
72
+ if (latestDateMS !== 0) {
73
+ currentDate.value = new Date(latestDateMS);
74
+ }
21
75
  }
22
- })
76
+ }
77
+
78
+ const eodashConfig = /** @type {import("@/types").Eodash} */ inject(eodashKey);
79
+
80
+ const masks = ref({
81
+ input: "YYYY-MM-DD",
82
+ });
83
+
84
+ /**
85
+ * Attributes displayed on datepicker
86
+ *
87
+ * @type {import("vue").Ref<
88
+ * (
89
+ * | import("v-calendar/dist/types/src/utils/attribute").AttributeConfig
90
+ * | undefined
91
+ * )[]
92
+ * >}
93
+ */
94
+ const attributes = ref([]);
23
95
 
24
96
  const currentDate = computed({
25
97
  get() {
26
- return props.inline ? datetime.value.split("T")[0] : new Date(datetime.value) ?? new Date()
98
+ return datetime.value ? new Date(datetime.value) : new Date();
27
99
  },
28
100
  /** @param {Date | string} updatedDate */
29
101
  set(updatedDate) {
30
- if (props.inline) {
31
- updatedDate = new Date(updatedDate);
32
- }
33
- //@ts-expect-error
34
- if (updatedDate instanceof Date && !isNaN(updatedDate)) {
35
- datetime.value = new Date(updatedDate.getTime() - updatedDate.getTimezoneOffset() * 60000).toISOString()
102
+ if (updatedDate instanceof Date && !isNaN(updatedDate.getTime())) {
103
+ datetime.value = new Date(
104
+ updatedDate.getTime() - updatedDate.getTimezoneOffset() * 60000,
105
+ ).toISOString();
36
106
  } else {
37
- datetime.value = new Date().toISOString()
107
+ datetime.value = new Date().toISOString();
38
108
  }
39
- }
109
+ },
40
110
  });
41
-
42
- /**
43
- * @type {import("vue").Ref<import("vuetify/components").VDatePicker | null>}
44
- **/
45
- const datePicker = ref(null)
46
- /** @type {import("vue").Ref<string|undefined>} */
47
- const width = ref()
48
- /** @type {import("vue").Ref<string|undefined>} */
49
- const height = ref()
111
+ /** @type {import("@/types").WebComponentProps["onMounted"]} */
50
112
  onMounted(() => {
51
- /** @type {HTMLElement} */
52
- const parentEl = datePicker.value?.$el.parentElement?.parentElement
53
- width.value = parentEl?.clientWidth ? parentEl.clientWidth + "px" : undefined
54
- height.value = parentEl?.clientHeight ? parentEl.clientHeight + "px" : undefined
55
- })
113
+ const { selectedStac } = storeToRefs(useSTAcStore());
114
+ watch(
115
+ [selectedStac],
116
+ async ([updatedStac]) => {
117
+ if (updatedStac) {
118
+ const parentCollUrl = toAbsolute(
119
+ `./${updatedStac.id}/collection.json`,
120
+ eodashConfig.stacEndpoint,
121
+ );
122
+ const collectionUrls = extractCollectionUrls(
123
+ selectedStac.value,
124
+ parentCollUrl,
125
+ );
126
+ const wongPalette = [
127
+ "#009E73",
128
+ "#0072B2",
129
+ "#E69F00",
130
+ "#CC79A7",
131
+ "#56B4E9",
132
+ "#D55E00",
133
+ ];
134
+ for (let idx = 0; idx < collectionUrls.length; idx++) {
135
+ const stacCollection = await axios
136
+ .get(collectionUrls[idx])
137
+ .then((resp) => resp.data);
138
+ const dates = stacCollection.links
139
+ .filter(
140
+ (/** @type {{ rel: string; datetime: string }} */ item) =>
141
+ item.rel === "item" && "datetime" in item,
142
+ )
143
+ .map(
144
+ (/** @type {{ datetime: string }} */ it) => new Date(it.datetime),
145
+ );
146
+ attributes.value = [
147
+ {
148
+ bar: {
149
+ style: {
150
+ backgroundColor: wongPalette[idx % wongPalette.length],
151
+ },
152
+ },
153
+ dates,
154
+ },
155
+ ];
156
+ }
157
+ }
158
+ },
159
+ { immediate: true },
160
+ );
161
+ });
56
162
  </script>
@@ -1,60 +1,100 @@
1
1
  <template>
2
- <DynamicWebComponent :link="link" tag-name="eox-itemfilter" :properties="properties" :on-mounted="onMounted" />
2
+ <eox-itemfilter
3
+ class="fill-height"
4
+ v-bind="config"
5
+ ref="eoxItemFilter"
6
+ style="overflow: auto"
7
+ @select="onSelect"
8
+ >
9
+ <h4 slot="filterstitle" style="margin: 14px 8px">{{ filtersTitle }}</h4>
10
+
11
+ <h4 slot="resultstitle" style="margin: 14px 8px">{{ resultsTitle }}</h4>
12
+ </eox-itemfilter>
3
13
  </template>
4
14
  <script setup>
5
- import DynamicWebComponent from "@/components/DynamicWebComponent.vue";
6
- import { indicator } from "@/store/States";
15
+ import { useSTAcStore } from "@/store/stac";
16
+ import "@eox/itemfilter";
17
+ import { onMounted, ref } from "vue";
7
18
 
8
- const link = () => import("@eox/itemfilter");
19
+ const props = defineProps({
20
+ filtersTitle: {
21
+ type: String,
22
+ default: "Indicators",
23
+ },
24
+ resultsTitle: {
25
+ type: String,
26
+ default: "",
27
+ },
28
+ titleProperty: {
29
+ type: String,
30
+ default: "title",
31
+ },
9
32
 
10
- const properties = {
11
- config: {
12
- titleProperty: "title",
13
- filterProperties: [
33
+ aggregateResults: {
34
+ type: String,
35
+ default: "themes",
36
+ },
37
+ enableHighlighting: { type: Boolean, default: true },
38
+ expandMultipleFilters: { type: Boolean, default: false },
39
+ expandMultipleResults: { type: Boolean, default: false },
40
+ filterProperties: {
41
+ /** @type {import("vue").PropType<{
42
+ * keys:string[];
43
+ * title:string;
44
+ * type:string;
45
+ * expanded?:boolean
46
+ * }[]> }*/
47
+ type: Array,
48
+ default: () => [
14
49
  {
15
- keys: ["title", "themes"],
50
+ keys: ["title", "themes", "description"],
16
51
  title: "Search",
17
52
  type: "text",
18
- expanded: true,
19
53
  },
20
54
  {
21
55
  key: "themes",
22
- title: "Theme",
56
+ title: "Theme Filter",
23
57
  type: "multiselect",
24
- featured: true,
25
58
  },
26
59
  ],
27
- aggregateResults: "themes",
28
- enableHighlighting: true,
29
60
  },
61
+ });
62
+ /** @param {any} evt*/
63
+ const onSelect = async (evt) => {
64
+ const item = /** @type {import('stac-ts').StacLink} */ evt.detail;
65
+ await store.loadSelectedSTAC(item.href);
66
+ console.log(item, store.selectedStac);
67
+ };
68
+ const config = {
69
+ titleProperty: props.titleProperty,
70
+ filterProperties: props.filterProperties,
71
+ aggregateResults: props.aggregateResults,
72
+ enableHighlighting: props.enableHighlighting,
73
+ expandMultipleFilters: props.expandMultipleFilters,
74
+ expandMultipleResults: props.expandMultipleResults,
30
75
  };
76
+ /** @type {import("vue").Ref<HTMLElement & Record<string,any> | null>} */
77
+ const eoxItemFilter = ref(null);
31
78
 
32
- /** @type {import("../core/types").WebComponentProps["onMounted"]}*/
33
- const onMounted = (el, store) => {
34
- /**
35
- * @typedef {object} Item
36
- * @property {string} href
37
- * */
38
- /** @type {any} */ (el).apply(
39
- // Only list child elements in list
40
- store.stac?.filter((item) => item.rel === "child")
41
- );
42
- // Check if selected indicator was already set in store
43
- if (indicator && indicator.value !== "") {
44
- const match = store.stac?.find((item) => item.id === indicator.value);
45
- if (match) {
46
- //@ts-expect-error
47
- (el).selectedResult = match;
48
- store.loadSelectedSTAC(match.href);
79
+ const store = useSTAcStore();
80
+
81
+ onMounted(() => {
82
+ const style = document.createElement("style");
83
+ style.innerHTML = `
84
+ section {
85
+ margin: 0 !important;
49
86
  }
50
- }
51
- /** @type {any} */ (el).config.onSelect =
52
- /**
53
- * @param {Item} item
54
- * */
55
- async (item) => {
56
- console.log(item);
57
- await store.loadSelectedSTAC(item.href);
58
- };
59
- };
87
+ section button#filter-reset {
88
+ padding: 0 8px;
89
+ top: 8px;
90
+ right: 8px;
91
+ }
92
+ `;
93
+ eoxItemFilter.value?.shadowRoot?.appendChild(style);
94
+
95
+ // Only list child elements in list
96
+ const items = store.stac?.filter((item) => item.rel === "child");
97
+ /** @type {any} */
98
+ (eoxItemFilter.value).items = items;
99
+ });
60
100
  </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <eox-layercontrol ref="eoxLayercontrol" class="pa-4" />
3
+ </template>
4
+ <script setup>
5
+ import "@eox/layercontrol";
6
+ import "@eox/jsonform";
7
+ import { onMounted, ref } from "vue";
8
+ import { mapEl } from "@/store/States";
9
+ /** @type { import("vue").Ref<HTMLElement & Record<string,any> | null>} */
10
+ const eoxLayercontrol = ref(null);
11
+
12
+ onMounted(() => {
13
+ /** @type{ HTMLElement & Record<string,any> }*/
14
+ (eoxLayercontrol.value).for = mapEl.value;
15
+ });
16
+ </script>
@@ -1,38 +1,68 @@
1
1
  <template>
2
- <DynamicWebComponent :link="link" tag-name="eox-map" :properties="properties" :on-mounted="onMounted"
3
- :on-unmounted="onUnmounted" />
2
+ <eox-map
3
+ class="fill-height fill-width overflow-none"
4
+ ref="eoxMap"
5
+ :config="eoxMapConfig"
6
+ />
4
7
  </template>
5
8
  <script setup>
6
- import { inject, watch } from "vue";
7
- import { toAbsolute } from "stac-js/src/http.js";
9
+ import { transformExtent } from "ol/proj";
10
+ import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
8
11
  import { EodashCollection } from "@/utils/eodashSTAC";
9
- import { eodashKey } from "@/utils/keys";
10
- import { datetime, mapPosition } from "@/store/States";
11
- import DynamicWebComponent from "@/components/DynamicWebComponent.vue";
12
+ import { extractCollectionUrls, uid } from "@/utils/helpers";
13
+ import { currentUrl, datetime, mapEl, mapPosition } from "@/store/States";
12
14
  import { storeToRefs } from "pinia";
15
+ import { useSTAcStore } from "@/store/stac";
16
+ import "@eox/map";
13
17
  import "@eox/map/dist/eox-map-advanced-layers-and-sources.js";
14
18
 
15
- const eodashConfig = /** @type {import("@/types").Eodash} */ inject(eodashKey);
19
+ /** @type {import("vue").Ref<(HTMLElement & Record<string,any>) | null>} */
20
+ const eoxMap = ref(null);
16
21
 
17
- /** @type {Record<string,unknown>} */
18
- const properties = {
19
- class: "fill-height fill-width overflow-none",
22
+ const eoxMapConfig = reactive({
23
+ /** @type {(number|undefined)[] | undefined} */
20
24
  center: [15, 48],
21
- layers: [{ type: "Tile", source: { type: "OSM" } }],
22
- };
25
+ /** @type {number | undefined} */
26
+ zoom: 4,
27
+ // TODO: we should probably introduce some way of defining
28
+ layers: [
29
+ {
30
+ type: "Vector",
31
+ source: {
32
+ type: "Vector",
33
+ url: "https://openlayers.org/data/vector/ecoregions.json",
34
+ format: "GeoJSON",
35
+ },
36
+ properties: {
37
+ id: "Regions",
38
+ title: "Regions",
39
+ },
40
+ },
41
+ {
42
+ type: "Tile",
43
+ properties: {
44
+ id: "osm",
45
+ title: "Background",
46
+ },
47
+ source: {
48
+ type: "OSM",
49
+ },
50
+ },
51
+ ],
52
+ });
53
+
23
54
  // Check if selected indicator was already set in store
24
55
  if (mapPosition && mapPosition.value && mapPosition.value.length === 3) {
25
56
  // TODO: do further checks for invalid values?
26
- properties.center = [mapPosition.value?.[0], mapPosition.value[1]];
27
- properties.zoom = mapPosition.value[2];
57
+ // TODO: can we expect the values to be in a specific projection
58
+ eoxMapConfig.center = [mapPosition.value?.[0], mapPosition.value[1]];
59
+ eoxMapConfig.zoom = mapPosition.value[2];
28
60
  }
29
61
 
30
- const link = () => import("@eox/map");
31
-
32
- /** @type {import("openlayers").EventsListenerFunctionType}*/
62
+ /** @type {import("openlayers").EventsListenerFunctionType} */
33
63
  const handleMoveEnd = (evt) => {
34
64
  const map = /** @type {import("openlayers").Map | undefined} */ (
35
- /** @type {*} */ (evt).map
65
+ /** @type {any} */ (evt).map
36
66
  );
37
67
  const [x, y] = map?.getView().getCenter() ?? [0, 0];
38
68
  const z = map?.getView().getZoom();
@@ -41,42 +71,156 @@ const handleMoveEnd = (evt) => {
41
71
  }
42
72
  };
43
73
 
44
- /** @type {import("@/types").WebComponentProps["onMounted"]} */
45
- const onMounted = (el, store) => {
46
- /** @type {any} */
47
- (el)?.map?.on("moveend", handleMoveEnd);
74
+ const store = useSTAcStore();
75
+
76
+ onMounted(() => {
77
+ mapEl.value = /** @type {HTMLElement & Record<string,any>} */ (eoxMap.value);
78
+
79
+ /** @type {import('ol/Map').default} */
80
+ (eoxMap.value?.map)?.on("moveend", handleMoveEnd);
48
81
 
49
82
  const { selectedStac } = storeToRefs(store);
50
83
 
51
84
  watch(
52
85
  [selectedStac, datetime],
53
- async ([updatedStac, updatedTime]) => {
86
+ async ([updatedStac, updatedTime], [previousSTAC, _previousTime]) => {
54
87
  if (updatedStac) {
55
- const parentCollUrl = toAbsolute(
56
- `./${updatedStac.id}/collection.json`,
57
- eodashConfig.stacEndpoint
88
+ const collectionUrls = extractCollectionUrls(
89
+ selectedStac.value,
90
+ currentUrl.value,
58
91
  );
59
- const childCollUrl = toAbsolute(
60
- updatedStac.links[1].href,
61
- parentCollUrl
92
+
93
+ const eodashCollections = collectionUrls.map(
94
+ (cu) => new EodashCollection(cu),
62
95
  );
63
- const eodash = new EodashCollection(childCollUrl);
64
- if (updatedTime) {
65
- /** @type {any} */ (el).layers = await eodash.createLayersJson(
66
- new Date(updatedTime)
96
+
97
+ const layersCollection = [];
98
+ const dataLayers = {
99
+ type: "Group",
100
+ properties: {
101
+ id: uid(),
102
+ title: "Analysis Layers",
103
+ layerControlExpand: true,
104
+ },
105
+ layers: /** @type {Record<string,any>[]}*/ ([]),
106
+ };
107
+
108
+ for (const ec of eodashCollections) {
109
+ let layers;
110
+ if (updatedTime) {
111
+ layers = await ec.createLayersJson(new Date(updatedTime));
112
+ } else {
113
+ layers = await ec.createLayersJson();
114
+ }
115
+ if (layers) {
116
+ dataLayers.layers.push(...layers);
117
+ }
118
+ }
119
+ // Add expand to all analysis layers
120
+ dataLayers.layers.forEach((dl) => {
121
+ dl.properties.layerControlExpand = true;
122
+ dl.properties.layerControlToolsExpand = true;
123
+ });
124
+
125
+ layersCollection.push(dataLayers);
126
+ const indicator = new EodashCollection(currentUrl.value);
127
+ const indicatorLayers = await indicator.buildJsonArray(
128
+ // @ts-expect-error we pass a collection instead of an item, we want to reuse the layer extraction logic
129
+ selectedStac.value,
130
+ currentUrl.value,
131
+ selectedStac.value?.title ?? "",
132
+ selectedStac.value?.endpointtype ?? false,
67
133
  );
134
+
135
+ const baseLayers = {
136
+ type: "Group",
137
+ properties: {
138
+ id: uid(),
139
+ title: "Base Layers",
140
+ },
141
+ layers: /** @type {Record<string,any>[]}*/ ([]),
142
+ };
143
+
144
+ const indicatorBaseLayers = indicatorLayers.filter(
145
+ (l) => l.properties.group === "baselayer",
146
+ );
147
+ if (indicatorBaseLayers.length) {
148
+ baseLayers.layers.push(...indicatorBaseLayers);
149
+
150
+ // Add exclusive to baselayers and make sure only one is selected
151
+ baseLayers.layers.forEach((bl) => {
152
+ bl.properties.layerControlExclusive = true;
153
+ });
68
154
  } else {
69
- /** @type {any} */ (el).layers = await eodash.createLayersJson();
155
+ // Default to some baselayer
156
+ baseLayers.layers.push({
157
+ type: "Tile",
158
+ properties: {
159
+ id: "osm",
160
+ title: "Background",
161
+ layerControlExclusive: true,
162
+ },
163
+ source: {
164
+ type: "OSM",
165
+ },
166
+ });
167
+ }
168
+
169
+ if (baseLayers.layers.length) {
170
+ layersCollection.push(baseLayers);
171
+ }
172
+
173
+ const overlayLayers = {
174
+ type: "Group",
175
+ properties: {
176
+ id: uid(),
177
+ title: "Overlay Layers",
178
+ },
179
+ layers: /** @type {Record<string,any>[]}*/ ([]),
180
+ };
181
+
182
+ const indicatorOverlays = indicatorLayers.filter(
183
+ (l) => l.properties.group === "overlay",
184
+ );
185
+ if (indicatorOverlays.length) {
186
+ overlayLayers.layers.push(...indicatorOverlays);
187
+ layersCollection.unshift(overlayLayers);
188
+ }
189
+
190
+ /** @type {any} */
191
+ (eoxMap.value).layers = layersCollection;
192
+
193
+ // only on different indicator selection and not on time change
194
+ if (previousSTAC?.id !== updatedStac.id) {
195
+ // Try to move map view to extent
196
+ const extent = await indicator.getExtent();
197
+ // Make sure for now we are always converting from 4326
198
+ // of stac items into current map projection
199
+ // TODO: This might change if we decide to use 4326 as default for zoom and extent
200
+ // Sanitize extent
201
+ const b = extent.spatial.bbox[0];
202
+ const sanitizedExtent = [
203
+ b[0] > -180 ? b[0] : -180,
204
+ b[1] > -90 ? b[1] : -90,
205
+ b[2] < 180 ? b[2] : 180,
206
+ b[3] < 90 ? b[3] : 90,
207
+ ];
208
+ const reprojExtent = transformExtent(
209
+ sanitizedExtent,
210
+ "EPSG:4326",
211
+ eoxMap.value?.map?.getView().getProjection(),
212
+ );
213
+ /** @type {any} */
214
+ (eoxMap.value).zoomExtent = reprojExtent;
70
215
  }
71
216
  }
72
217
  },
73
- { immediate: true }
218
+ { immediate: true },
74
219
  );
75
- };
220
+ });
76
221
 
77
- /** @type {import("@/types").WebComponentProps["onUnmounted"]} */
78
- const onUnmounted = (el, _store) => {
79
- /** @type {any} */
80
- (el)?.map?.un("moveend", handleMoveEnd);
81
- };
222
+ onUnmounted(() => {
223
+ /** @type {import('ol/Map').default} */
224
+ (eoxMap.value?.map)?.un("moveend", handleMoveEnd);
225
+ });
82
226
  </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div ref="rootRef" class="d-flex flex-column align-end justify-end my-3 pa-2">
3
+ <v-btn
4
+ v-if="exportMap"
5
+ class="map-btn"
6
+ :icon="[mdiMapPlus]"
7
+ @click="showMapState = !showMapState"
8
+ />
9
+ <ExportState v-if="exportMap" v-model="showMapState" />
10
+ <v-btn
11
+ class="map-btn"
12
+ :icon="[mdiEarthBox]"
13
+ v-if="changeProjection && !!availableMapProjection"
14
+ @click="changeMapProjection(availableMapProjection)"
15
+ />
16
+ </div>
17
+ </template>
18
+ <script setup>
19
+ import { makePanelTransparent } from "@/composables";
20
+ import { changeMapProjection } from "@/store/Actions";
21
+ import { availableMapProjection } from "@/store/States";
22
+ import { mdiEarthBox, mdiMapPlus } from "@mdi/js";
23
+ import ExportState from "^/ExportState.vue";
24
+ import { ref } from "vue";
25
+
26
+ defineProps({
27
+ exportMap: {
28
+ type: Boolean,
29
+ default: true,
30
+ },
31
+ changeProjection: {
32
+ type: Boolean,
33
+ default: true,
34
+ },
35
+ });
36
+
37
+ const showMapState = ref(false);
38
+
39
+ /** @type {import("vue").Ref<HTMLDivElement|null>} */
40
+ const rootRef = ref(null);
41
+ makePanelTransparent(rootRef);
42
+ </script>
43
+ <style scoped>
44
+ .map-btn {
45
+ width: 36px;
46
+ height: 36px;
47
+ border-radius: 25%;
48
+ margin: 4px;
49
+ }
50
+ </style>