@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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -3
  3. package/core/client/App.vue +26 -0
  4. package/core/client/SuspensedDashboard.ce.vue +105 -0
  5. package/core/client/asWebComponent.d.ts +23 -0
  6. package/core/client/asWebComponent.js +12 -0
  7. package/core/client/components/DashboardLayout.vue +77 -0
  8. package/core/client/components/DynamicWebComponent.vue +68 -0
  9. package/core/client/components/ErrorAlert.vue +36 -0
  10. package/core/{components → client/components}/Footer.vue +34 -15
  11. package/core/client/components/Header.vue +19 -0
  12. package/core/client/components/IframeWrapper.vue +11 -0
  13. package/core/client/components/Loading.vue +37 -0
  14. package/core/client/components/MobileLayout.vue +109 -0
  15. package/core/client/composables/DefineEodash.js +67 -0
  16. package/core/client/composables/DefineWidgets.js +161 -0
  17. package/core/client/composables/index.js +136 -0
  18. package/core/client/eodash.js +136 -0
  19. package/core/client/main.js +2 -0
  20. package/core/client/plugins/index.js +20 -0
  21. package/core/client/plugins/vuetify.js +35 -0
  22. package/core/client/render.js +12 -0
  23. package/core/client/store/Actions.js +62 -0
  24. package/core/client/store/States.js +21 -0
  25. package/core/client/store/index.js +20 -0
  26. package/core/client/store/stac.js +81 -0
  27. package/core/client/types.d.ts +287 -0
  28. package/core/client/utils/eodashSTAC.js +337 -0
  29. package/core/client/utils/helpers.js +172 -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-C_3q8Y10.js +156 -0
  35. package/dist/{DynamicWebComponent-BDOQsEcj.js → client/DynamicWebComponent-IiTTLBt-.js} +11 -11
  36. package/dist/client/EodashDatePicker-DgC1lccp.js +252 -0
  37. package/dist/{eox-itemfilter-DcQkRD2l.js → client/EodashItemFilter-DoWZGe6r.js} +1515 -1381
  38. package/dist/client/EodashMap-C96D3LLv.js +114352 -0
  39. package/dist/client/EodashMapBtns-DDtzV94u.js +50 -0
  40. package/dist/client/ExportState-CduS2mFs.js +558 -0
  41. package/dist/client/Footer-DHQcutCv.js +115 -0
  42. package/dist/client/Header-DC0y7rLK.js +350 -0
  43. package/dist/{IframeWrapper-DYHQQRUh.js → client/IframeWrapper-BK-10FbS.js} +4 -4
  44. package/dist/client/MobileLayout-BpLoMY4i.js +945 -0
  45. package/dist/client/PopUp-CNMCWL0z.js +300 -0
  46. package/dist/client/VImg-DT7eb8V-.js +291 -0
  47. package/dist/client/VMain-OenWyy46.js +39 -0
  48. package/dist/client/VOverlay-x3UXpKhr.js +972 -0
  49. package/dist/{WidgetsContainer-D_PLP1qf.js → client/WidgetsContainer-qMUwtN0W.js} +26 -19
  50. package/dist/client/asWebComponent-DQrnLsI2.js +20404 -0
  51. package/dist/{basedecoder-Qm25PwVp-CHo5Pomv.js → client/basedecoder-DHcBySSe-BmCFNFnw.js} +5 -6
  52. package/dist/{decoder-kAoyGIq9-HAkjSTCt.js → client/decoder-Cth6J7EK-CtUoHA6r.js} +1 -1
  53. package/dist/client/deflate-BXt-9JA_-CWfClgpK.js +10 -0
  54. package/dist/client/eo-dash.js +6 -0
  55. package/dist/client/eox-jsonform-DCErgjhw.js +17515 -0
  56. package/dist/client/eox-layercontrol-D0Ht7Mag.js +21298 -0
  57. package/dist/client/eox-stacinfo-l7ALSV90.js +13969 -0
  58. package/dist/client/forwardRefs-CpNjL95t.js +185 -0
  59. package/dist/client/helpers-KCSgmfim.js +1417 -0
  60. package/dist/{index-8ch8xKfa.js → client/index-DopVrUzM.js} +21 -20
  61. package/dist/{jpeg-DNfUpLwy-Fjan-04T.js → client/jpeg-BAgeD1d3-oeHbFPUL.js} +5 -6
  62. package/dist/{lerc-C9VL9kri-DyVxBvsf.js → client/lerc-BgbQqdFI-D986ErVw.js} +37 -39
  63. package/dist/{lzw-BOMhmEDy-Dboc93VO.js → client/lzw-LAGDNbSC-DkP96qO9.js} +1 -1
  64. package/dist/{packbits-DaUD6MLm-Bu1PoTGa.js → client/packbits-BlDR4Kj5-C66n1-zr.js} +1 -1
  65. package/dist/{pako.esm-C3kYPGGQ-BMki8cQY.js → client/pako.esm-CB1uQYY0-DB0PYm1P.js} +6 -12
  66. package/dist/{raw-CcGKjn8q-DFOt-i8n.js → client/raw-CMGvRjfu-BRi6E4i1.js} +1 -1
  67. package/dist/{ssrBoot-Cm5pWM14.js → client/ssrBoot-DkjdOWvj.js} +1 -1
  68. package/dist/client/style.css +5 -0
  69. package/dist/client/transition-BtJHh7JK.js +34 -0
  70. package/dist/{webfontloader-CyOFAuFB.js → client/webfontloader-qotgY98I.js} +56 -92
  71. package/dist/{webimage-D2c098k3-DLj1LQxB.js → client/webimage-BM_pbLN3-L2cGWK5l.js} +1 -1
  72. package/dist/node/cli.js +20 -0
  73. package/dist/node/main.js +2 -0
  74. package/dist/node/types.d.ts +47 -0
  75. package/package.json +54 -51
  76. package/widgets/EodashDatePicker.vue +145 -40
  77. package/widgets/EodashItemFilter.vue +73 -38
  78. package/widgets/EodashMap.vue +210 -37
  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 -59
  91. package/core/asWebComponent.d.ts +0 -18
  92. package/core/asWebComponent.js +0 -12
  93. package/core/components/DashboardLayout.vue +0 -54
  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 -355
  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-YLfAa8wH.js +0 -168
  118. package/dist/EodashDatePicker-DlehqSgz.js +0 -1645
  119. package/dist/EodashItemFilter-TSp7XRGy.js +0 -51
  120. package/dist/EodashMap-DInb9cfS.js +0 -53405
  121. package/dist/Footer-CIzHgJbK.js +0 -118
  122. package/dist/Header-C5PSB8Uc.js +0 -609
  123. package/dist/MobileLayout-DZmqrpub.js +0 -523
  124. package/dist/VBtn-CFPhsIX8.js +0 -1106
  125. package/dist/VMain-deI9jyIf.js +0 -35
  126. package/dist/asWebComponent-BKe6Spni.js +0 -11323
  127. package/dist/color-D72nEBIR.js +0 -115
  128. package/dist/deflate-Be2Arps5-hDqMz3RA.js +0 -10
  129. package/dist/dimensions-BZkmIizV.js +0 -53
  130. package/dist/eo-dash.js +0 -6
  131. package/dist/eox-map-BJ9SIixs.js +0 -36205
  132. package/dist/eox-stacinfo-B-YrT7Ug.js +0 -13698
  133. package/dist/forwardRefs-Bxeu9Obx.js +0 -142
  134. package/dist/index-DIt8GjSR.js +0 -65
  135. package/dist/style.css +0 -5
  136. /package/dist/{.gitkeep → client/.gitkeep} +0 -0
  137. /package/dist/{_commonjsHelpers-DaMA6jEr.js → client/_commonjsHelpers-DaMA6jEr.js} +0 -0
@@ -1,56 +1,161 @@
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";
17
48
 
18
- const props = defineProps({
19
- inline: {
20
- type: Boolean
49
+ /**
50
+ * @param {boolean} reverse
51
+ */
52
+ function jumpDate(reverse) {
53
+ if (attributes.value && attributes.value.length > 0) {
54
+ // We have potentially multiple collections we need to iterate (currently only one)
55
+ let latestDateMS = reverse ? Infinity : -Infinity;
56
+ attributes.value.forEach((coll) => {
57
+ if (coll?.dates) {
58
+ coll.dates.forEach((d) => {
59
+ // TODO: we need to handle time ranges and other options here
60
+ if (d instanceof Date) {
61
+ if (
62
+ (!reverse && d.getTime() > latestDateMS) ||
63
+ (reverse && d.getTime() < latestDateMS)
64
+ ) {
65
+ latestDateMS = d.getTime();
66
+ }
67
+ }
68
+ });
69
+ }
70
+ });
71
+ if (latestDateMS !== 0) {
72
+ currentDate.value = new Date(latestDateMS);
73
+ }
21
74
  }
22
- })
75
+ }
76
+
77
+ const eodashConfig = /** @type {import("@/types").Eodash} */ inject(eodashKey);
78
+
79
+ const masks = ref({
80
+ input: "YYYY-MM-DD",
81
+ });
82
+
83
+ /**
84
+ * Attributes displayed on datepicker
85
+ *
86
+ * @type {import("vue").Ref<
87
+ * (
88
+ * | import("v-calendar/dist/types/src/utils/attribute").AttributeConfig
89
+ * | undefined
90
+ * )[]
91
+ * >}
92
+ */
93
+ const attributes = ref([]);
23
94
 
24
95
  const currentDate = computed({
25
96
  get() {
26
- return props.inline ? datetime.value.split("T")[0] : new Date(datetime.value) ?? new Date()
97
+ return datetime.value ? new Date(datetime.value) : new Date();
27
98
  },
28
99
  /** @param {Date | string} updatedDate */
29
100
  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()
101
+ if (updatedDate instanceof Date && !isNaN(updatedDate.getTime())) {
102
+ datetime.value = new Date(
103
+ updatedDate.getTime() - updatedDate.getTimezoneOffset() * 60000,
104
+ ).toISOString();
36
105
  } else {
37
- datetime.value = new Date().toISOString()
106
+ datetime.value = new Date().toISOString();
38
107
  }
39
- }
108
+ },
40
109
  });
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()
110
+ /** @type {import("@/types").WebComponentProps["onMounted"]} */
50
111
  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
- })
112
+ const { selectedStac } = storeToRefs(useSTAcStore());
113
+ watch(
114
+ [selectedStac],
115
+ async ([updatedStac]) => {
116
+ if (updatedStac) {
117
+ const parentCollUrl = toAbsolute(
118
+ `./${updatedStac.id}/collection.json`,
119
+ eodashConfig.stacEndpoint,
120
+ );
121
+ const collectionUrls = extractCollectionUrls(
122
+ selectedStac.value,
123
+ parentCollUrl,
124
+ );
125
+ const wongPalette = [
126
+ "#009E73",
127
+ "#0072B2",
128
+ "#E69F00",
129
+ "#CC79A7",
130
+ "#56B4E9",
131
+ "#D55E00",
132
+ ];
133
+ for (let idx = 0; idx < collectionUrls.length; idx++) {
134
+ const stacCollection = await (
135
+ await fetch(collectionUrls[idx])
136
+ ).json();
137
+ const dates = stacCollection.links
138
+ .filter(
139
+ (/** @type {{ rel: string; datetime: string }} */ item) =>
140
+ item.rel === "item" && "datetime" in item,
141
+ )
142
+ .map(
143
+ (/** @type {{ datetime: string }} */ it) => new Date(it.datetime),
144
+ );
145
+ attributes.value = [
146
+ {
147
+ bar: {
148
+ style: {
149
+ backgroundColor: wongPalette[idx % wongPalette.length],
150
+ },
151
+ },
152
+ dates,
153
+ },
154
+ ];
155
+ }
156
+ }
157
+ },
158
+ { immediate: true },
159
+ );
160
+ });
56
161
  </script>
@@ -1,60 +1,95 @@
1
1
  <template>
2
- <DynamicWebComponent :link="link" tag-name="eox-itemfilter" :properties="properties" :on-mounted="onMounted" />
2
+ <eox-itemfilter class="fill-height" :config="config" ref="eoxItemFilter">
3
+ <h4 slot="filterstitle" style="margin: 14px 8px">{{ filtersTitle }}</h4>
4
+
5
+ <h4 slot="resultstitle" style="margin: 14px 8px">{{ resultsTitle }}</h4>
6
+ </eox-itemfilter>
3
7
  </template>
4
8
  <script setup>
5
- import DynamicWebComponent from "@/components/DynamicWebComponent.vue";
6
- import { indicator } from "@/store/States";
9
+ import { useSTAcStore } from "@/store/stac";
10
+ import "@eox/itemfilter";
11
+ import { onMounted, ref } from "vue";
7
12
 
8
- const link = () => import("@eox/itemfilter");
13
+ const props = defineProps({
14
+ filtersTitle: {
15
+ type: String,
16
+ default: "Indicators",
17
+ },
18
+ resultsTitle: {
19
+ type: String,
20
+ default: "",
21
+ },
22
+ titleProperty: {
23
+ type: String,
24
+ default: "title",
25
+ },
9
26
 
10
- const properties = {
11
- config: {
12
- titleProperty: "title",
13
- filterProperties: [
27
+ aggregateResults: {
28
+ type: String,
29
+ default: "themes",
30
+ },
31
+ enableHighlighting: { type: Boolean, default: true },
32
+ expandMultipleFilters: { type: Boolean, default: false },
33
+ expandMultipleResults: { type: Boolean, default: false },
34
+ filterProperties: {
35
+ /** @type {import("vue").PropType<{
36
+ * keys:string[];
37
+ * title:string;
38
+ * type:string;
39
+ * expanded?:boolean
40
+ * }[]> }*/
41
+ type: Array,
42
+ default: () => [
14
43
  {
15
- keys: ["title", "themes"],
44
+ keys: ["title", "themes", "description"],
16
45
  title: "Search",
17
46
  type: "text",
18
- expanded: true,
19
47
  },
20
48
  {
21
49
  key: "themes",
22
- title: "Theme",
50
+ title: "Theme Filter",
23
51
  type: "multiselect",
24
- featured: true,
25
52
  },
26
53
  ],
27
- aggregateResults: "themes",
28
- enableHighlighting: true,
29
54
  },
55
+ });
56
+
57
+ const config = {
58
+ titleProperty: props.titleProperty,
59
+ filterProperties: props.filterProperties,
60
+ aggregateResults: props.aggregateResults,
61
+ enableHighlighting: props.enableHighlighting,
62
+ expandMultipleFilters: props.expandMultipleFilters,
63
+ expandMultipleResults: props.expandMultipleResults,
30
64
  };
65
+ /** @type {import("vue").Ref<HTMLElement & Record<string,any> | null>} */
66
+ const eoxItemFilter = ref(null);
67
+
68
+ const store = useSTAcStore();
31
69
 
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);
70
+ onMounted(() => {
71
+ const style = document.createElement("style");
72
+ style.innerHTML = `
73
+ section {
74
+ margin: 0 !important;
75
+ }
76
+ section button#filter-reset {
77
+ padding: 0 8px;
78
+ top: 8px;
79
+ right: 8px;
49
80
  }
50
- }
51
- /** @type {any} */ (el).config.onSelect =
52
- /**
53
- * @param {Item} item
54
- * */
81
+ `;
82
+ eoxItemFilter.value?.shadowRoot?.appendChild(style);
83
+
84
+ eoxItemFilter.value?.apply(
85
+ // Only list child elements in list
86
+ store.stac?.filter((item) => item.rel === "child"),
87
+ );
88
+ /** @type {any} */ (eoxItemFilter.value).config.onSelect =
89
+ /** @param {import('stac-ts').StacLink} item */
55
90
  async (item) => {
56
- console.log(item);
57
91
  await store.loadSelectedSTAC(item.href);
92
+ console.log(item, store.selectedStac);
58
93
  };
59
- };
94
+ });
60
95
  </script>
@@ -1,39 +1,80 @@
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";
9
+ import { transformExtent } from "ol/proj";
10
+ import { inject, onMounted, onUnmounted, reactive, ref, watch } from "vue";
7
11
  import { toAbsolute } from "stac-js/src/http.js";
8
12
  import { EodashCollection } from "@/utils/eodashSTAC";
13
+ import { extractCollectionUrls } from "@/utils/helpers";
9
14
  import { eodashKey } from "@/utils/keys";
10
15
  import { datetime, mapPosition } from "@/store/States";
11
- import DynamicWebComponent from "@/components/DynamicWebComponent.vue";
12
16
  import { storeToRefs } from "pinia";
17
+ import { useSTAcStore } from "@/store/stac";
18
+ import "@eox/map";
13
19
  import "@eox/map/dist/eox-map-advanced-layers-and-sources.js";
14
20
 
15
- const eodashConfig = /** @type {import("@/types").Eodash} */ inject(eodashKey);
21
+ const eodashConfig = /** @type {import("@/types").Eodash} */ (
22
+ inject(eodashKey)
23
+ );
16
24
 
17
- /** @type {Record<string,unknown>} */
18
- const properties = {
19
- class: "fill-height fill-width overflow-none",
25
+ /** @type {import("vue").Ref<(HTMLElement & Record<string,unknown>) | null>} */
26
+ const eoxMap = ref(null);
27
+
28
+ const eoxMapConfig = reactive({
29
+ /** @type {(number|undefined)[] | undefined} */
20
30
  center: [15, 48],
21
- layers: [{ type: "Tile", source: { type: "OSM" } }],
22
- };
31
+ /** @type {number | undefined} */
32
+ zoom: 4,
33
+ // TODO: we should probably introduce some way of defining
34
+ layers: [
35
+ {
36
+ type: "Vector",
37
+ source: {
38
+ type: "Vector",
39
+ url: "https://openlayers.org/data/vector/ecoregions.json",
40
+ format: "GeoJSON",
41
+ },
42
+ properties: {
43
+ id: "Regions",
44
+ title: "Regions",
45
+ },
46
+ },
47
+ {
48
+ type: "Tile",
49
+ properties: {
50
+ id: "osm",
51
+ title: "Background",
52
+ },
53
+ source: {
54
+ type: "OSM",
55
+ },
56
+ },
57
+ ],
58
+ });
59
+
23
60
  // Check if selected indicator was already set in store
24
61
  if (mapPosition && mapPosition.value && mapPosition.value.length === 3) {
25
62
  // TODO: do further checks for invalid values?
26
- properties.center = [mapPosition.value?.[0], mapPosition.value[1]];
27
- properties.zoom = mapPosition.value[2];
63
+ // TODO: can we expect the values to be in a specific projection
64
+ eoxMapConfig.center = [mapPosition.value?.[0], mapPosition.value[1]];
65
+ eoxMapConfig.zoom = mapPosition.value[2];
28
66
  }
29
67
 
30
- const link = () => import("@eox/map");
31
-
32
- /** @type {import("openlayers").EventsListenerFunctionType}*/
68
+ /** @type {import("openlayers").EventsListenerFunctionType} */
33
69
  const handleMoveEnd = (evt) => {
34
70
  const map = /** @type {import("openlayers").Map | undefined} */ (
35
- /** @type {*} */ (evt).map
71
+ /** @type {any} */ (evt).map
36
72
  );
73
+ /*
74
+ const currentProj = map?.getView().getProjection();
75
+ const transFunc = getTransform(currentProj?.getCode(), 'EPSG:4326');
76
+ const [x, y] = transFunc(map?.getView().getCenter() ?? [0, 0], undefined, undefined);
77
+ */
37
78
  const [x, y] = map?.getView().getCenter() ?? [0, 0];
38
79
  const z = map?.getView().getZoom();
39
80
  if (!Number.isNaN(x) && !Number.isNaN(y) && !Number.isNaN(z)) {
@@ -41,42 +82,174 @@ const handleMoveEnd = (evt) => {
41
82
  }
42
83
  };
43
84
 
44
- /** @type {import("@/types").WebComponentProps["onMounted"]} */
45
- const onMounted = (el, store) => {
46
- /** @type {any} */
47
- (el)?.map?.on("moveend", handleMoveEnd);
85
+ const store = useSTAcStore();
86
+
87
+ onMounted(() => {
88
+ /** @type {import('ol/Map').default} */
89
+ (eoxMap.value?.map)?.on("moveend", handleMoveEnd);
48
90
 
49
91
  const { selectedStac } = storeToRefs(store);
50
92
 
51
93
  watch(
52
94
  [selectedStac, datetime],
53
- async ([updatedStac, updatedTime]) => {
95
+ async ([updatedStac, updatedTime], [previousSTAC, _previousTime]) => {
54
96
  if (updatedStac) {
55
97
  const parentCollUrl = toAbsolute(
56
98
  `./${updatedStac.id}/collection.json`,
57
- eodashConfig.stacEndpoint
99
+ eodashConfig.stacEndpoint,
58
100
  );
59
- const childCollUrl = toAbsolute(
60
- updatedStac.links[1].href,
61
- parentCollUrl
101
+ const collectionUrls = extractCollectionUrls(
102
+ selectedStac.value,
103
+ parentCollUrl,
62
104
  );
63
- const eodash = new EodashCollection(childCollUrl);
64
- if (updatedTime) {
65
- /** @type {any} */ (el).layers = await eodash.createLayersJson(
66
- new Date(updatedTime)
105
+ /** @type {import("@/utils/eodashSTAC").EodashCollection[]} */
106
+ const eodashCollections = [];
107
+ collectionUrls.forEach((cu) => {
108
+ eodashCollections.push(new EodashCollection(cu));
109
+ });
110
+ const uid = function () {
111
+ return (
112
+ Date.now().toString(36) + Math.random().toString(36).substring(2)
113
+ );
114
+ };
115
+ const layersCollection = [];
116
+ const dataLayers = {
117
+ type: "Group",
118
+ properties: {
119
+ id: uid(),
120
+ title: "Analysis Layers",
121
+ layerControlExpand: true,
122
+ },
123
+ layers: /** @type {object[]}*/ ([]),
124
+ };
125
+ for (let idx = 0; idx < eodashCollections.length; idx++) {
126
+ const ec = eodashCollections[idx];
127
+ let layers;
128
+ if (updatedTime) {
129
+ layers = await ec.createLayersJson(new Date(updatedTime));
130
+ } else {
131
+ layers = await ec.createLayersJson();
132
+ }
133
+ if (layers) {
134
+ dataLayers.layers.push(...layers);
135
+ }
136
+ }
137
+ // Add expand to all analysis layers
138
+ dataLayers.layers.forEach((dl) => {
139
+ // @ts-expect-error no type definition for eox-map config
140
+ dl.properties.layerControlExpand = true;
141
+ // @ts-expect-error no type definition for eox-map config
142
+ dl.properties.layerControlToolsExpand = true;
143
+ });
144
+
145
+ layersCollection.push(dataLayers);
146
+ // TODO: add base layers and overlays as defined in the top collection / indicator
147
+ // Probably best also to introduce background and overlay groups
148
+ // For now adding OSM as background
149
+ const indicator = new EodashCollection(parentCollUrl);
150
+ const indicatorLayers = await indicator.buildJsonArray(
151
+ // @ts-expect-error we pass a collection instead of an item, we want to reuse the layer extraction logic
152
+ selectedStac.value,
153
+ false,
154
+ false,
67
155
  );
156
+ const baseLayers = {
157
+ type: "Group",
158
+ properties: {
159
+ id: uid(),
160
+ title: "Base Layers",
161
+ },
162
+ layers: /** @type {object[]}*/ ([]),
163
+ };
164
+ const overlayLayers = {
165
+ type: "Group",
166
+ properties: {
167
+ id: uid(),
168
+ title: "Overlay Layers",
169
+ },
170
+ layers: /** @type {object[]}*/ ([]),
171
+ };
172
+ if (indicatorLayers && indicatorLayers.length > 0) {
173
+ baseLayers.layers.push(
174
+ ...indicatorLayers.filter(
175
+ // @ts-expect-error group is added by the buildJsonArray
176
+ (l) => l.properties.group === "baselayer",
177
+ ),
178
+ );
179
+ // Add exclusive to baselayers and make sure only one is selected
180
+ baseLayers.layers.forEach((bl) => {
181
+ // @ts-expect-error no type definition for eox-map config
182
+ bl.properties.layerControlExclusive = true;
183
+ });
184
+ overlayLayers.layers.push(
185
+ // @ts-expect-error group is added by the buildJsonArray
186
+ ...indicatorLayers.filter((l) => l.properties.group === "overlay"),
187
+ );
68
188
  } else {
69
- /** @type {any} */ (el).layers = await eodash.createLayersJson();
189
+ // Default to some baselayer
190
+ baseLayers.layers.push({
191
+ type: "Tile",
192
+ properties: {
193
+ id: "osm",
194
+ title: "Background",
195
+ },
196
+ source: {
197
+ type: "OSM",
198
+ },
199
+ });
200
+ }
201
+ if (baseLayers.layers.length > 0) {
202
+ layersCollection.push(baseLayers);
203
+ }
204
+ if (overlayLayers.layers.length > 0) {
205
+ layersCollection.unshift(overlayLayers);
206
+ }
207
+
208
+ // TODO: we can check if the collection / indicator has a specific
209
+ // projection it wants to be displayed in the map we can register
210
+ // and set the attribute here, e.g. like following
211
+ /*
212
+ (el)?.registerProjection(
213
+ 'EPSG:3031','+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs +type=crs'
214
+ );
215
+ (el)?.projection = "EPSG:3031";
216
+ */
217
+
218
+ /** @type {any} */
219
+ (eoxMap.value).layers = layersCollection;
220
+
221
+ // only on different indicator selection and not on time change
222
+ if (previousSTAC?.id !== updatedStac.id) {
223
+ // Try to move map view to extent
224
+ const extent = await indicator.getExtent();
225
+ // Make sure for now we are always converting from 4326
226
+ // of stac items into current map projection
227
+ // TODO: This might change if we decide to use 4326 as default for zoom and extent
228
+ // Sanitize extent
229
+ const b = extent.spatial.bbox[0];
230
+ const sanitizedExtent = [
231
+ b[0] > -180 ? b[0] : -180,
232
+ b[1] > -90 ? b[1] : -90,
233
+ b[2] < 180 ? b[2] : 180,
234
+ b[3] < 90 ? b[3] : 90,
235
+ ];
236
+ const reprojExtent = transformExtent(
237
+ sanitizedExtent,
238
+ "EPSG:4326",
239
+ // @ts-expect-error we should expect to have the view here
240
+ eoxMap.value?.map?.getView().getProjection(),
241
+ );
242
+ /** @type {any} */
243
+ (eoxMap.value).zoomExtent = reprojExtent;
70
244
  }
71
245
  }
72
246
  },
73
- { immediate: true }
247
+ { immediate: true },
74
248
  );
75
- };
249
+ });
76
250
 
77
- /** @type {import("@/types").WebComponentProps["onUnmounted"]} */
78
- const onUnmounted = (el, _store) => {
79
- /** @type {any} */
80
- (el)?.map?.un("moveend", handleMoveEnd);
81
- };
251
+ onUnmounted(() => {
252
+ /** @type {import('ol/Map').default} */
253
+ (eoxMap.value?.map)?.un("moveend", handleMoveEnd);
254
+ });
82
255
  </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>