Skip to content

Commit 801fd0f

Browse files
authored
Merge pull request #711 from SlideRuleEarth/carlos-dev3
Carlos dev3
2 parents 78db906 + d348525 commit 801fd0f

File tree

3 files changed

+220
-2
lines changed

3 files changed

+220
-2
lines changed

web-client/src/components/SrAnalysisMap.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565
const requestsStore = useRequestsStore();
6666
const recTreeStore = useRecTreeStore();
6767
const deckStore = useDeckStore();
68-
const deck3DConfigStore = useDeck3DConfigStore();
6968
const srParquetCfgStore = useSrParquetCfgStore();
7069
const analysisMapStore = useAnalysisMapStore();
7170
const globalChartStore = useGlobalChartStore();
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<!-- SrExportPolygonControl.vue -->
2+
<template>
3+
<div
4+
ref="containerEl"
5+
class="ol-control ol-unselectable sr-control sr-export-polygon-control"
6+
role="group"
7+
aria-label="Export Polygon control"
8+
:style="varStyle"
9+
>
10+
<Button
11+
icon="pi pi-download"
12+
class="p-button-icon-only sr-export-polygon-button"
13+
@click="exportAsGeoJSON"
14+
variant="text"
15+
:disabled="!hasPolygon"
16+
aria-label="Export polygon as GeoJSON"
17+
:title="tooltipText"
18+
/>
19+
</div>
20+
</template>
21+
22+
<script setup lang="ts">
23+
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
24+
import Control from 'ol/control/Control';
25+
import Button from 'primevue/button';
26+
import { useReqParamsStore } from '@/stores/reqParamsStore';
27+
import { useSrToastStore } from '@/stores/srToastStore';
28+
import type OLMap from 'ol/Map.js';
29+
30+
type Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
31+
32+
const props = defineProps({
33+
map: { type: Object as () => OLMap | null, default: null },
34+
35+
/** Which corner to pin to */
36+
corner: { type: String as () => Corner, default: 'top-left' },
37+
38+
/** Offsets from the chosen corner */
39+
offsetX: { type: [Number, String], default: '0.5rem' },
40+
offsetY: { type: [Number, String], default: '20.5rem' },
41+
42+
/** Style knobs */
43+
bg: { type: String, default: 'rgba(0, 0, 0, 0.8)' },
44+
color: { type: String, default: 'white' },
45+
radius: { type: String, default: 'var(--p-border-radius)' },
46+
zIndex: { type: [Number, String], default: undefined },
47+
});
48+
49+
const emit = defineEmits<{
50+
(e: 'export-polygon-control-created', control: Control): void;
51+
}>();
52+
53+
const reqParamsStore = useReqParamsStore();
54+
const toastStore = useSrToastStore();
55+
56+
const hasPolygon = computed(() => {
57+
return reqParamsStore.poly && reqParamsStore.poly.length > 0;
58+
});
59+
60+
const tooltipText = 'Export the drawn polygon as GeoJSON';
61+
62+
function exportAsGeoJSON() {
63+
try {
64+
const poly = reqParamsStore.poly;
65+
66+
if (!poly || poly.length === 0) {
67+
toastStore.warn('No Polygon', 'Please draw a polygon on the map first');
68+
return;
69+
}
70+
71+
// Create GeoJSON from the poly (array of {lat, lon})
72+
const coordinates = poly.map(point => [point.lon, point.lat]);
73+
74+
// Close the polygon if not already closed
75+
if (coordinates.length > 0) {
76+
const first = coordinates[0];
77+
const last = coordinates[coordinates.length - 1];
78+
if (first[0] !== last[0] || first[1] !== last[1]) {
79+
coordinates.push([...first]);
80+
}
81+
}
82+
83+
const geojson = {
84+
type: 'FeatureCollection',
85+
features: [
86+
{
87+
type: 'Feature',
88+
properties: {
89+
name: 'SlideRule Polygon',
90+
description: 'Exported from SlideRule',
91+
created: new Date().toISOString()
92+
},
93+
geometry: {
94+
type: 'Polygon',
95+
coordinates: [coordinates]
96+
}
97+
}
98+
]
99+
};
100+
101+
const jsonString = JSON.stringify(geojson, null, 2);
102+
const blob = new Blob([jsonString], { type: 'application/json' });
103+
const url = URL.createObjectURL(blob);
104+
105+
const filename = `sliderule-polygon-${new Date().toISOString().replace(/[:.]/g, '-')}.geojson`;
106+
const a = document.createElement('a');
107+
a.href = url;
108+
a.download = filename;
109+
a.click();
110+
111+
URL.revokeObjectURL(url);
112+
113+
toastStore.info('Export Successful', `Polygon exported as ${filename}`);
114+
} catch (error) {
115+
console.error('Error exporting GeoJSON:', error);
116+
toastStore.error('Export Failed', 'Failed to export polygon as GeoJSON');
117+
}
118+
}
119+
120+
const toCss = (v: number | string) => typeof v === 'number' ? `${v}px` : v;
121+
122+
const varStyle = computed(() => {
123+
const x = toCss(props.offsetX);
124+
const y = toCss(props.offsetY);
125+
const top = props.corner.startsWith('top') ? y : 'auto';
126+
const bottom = props.corner.startsWith('bottom') ? y : 'auto';
127+
const left = props.corner.endsWith('left') ? x : 'auto';
128+
const right = props.corner.endsWith('right') ? x : 'auto';
129+
130+
return {
131+
'--sr-top': top,
132+
'--sr-right': right,
133+
'--sr-bottom': bottom,
134+
'--sr-left': left,
135+
'--sr-bg': props.bg,
136+
'--sr-color': props.color,
137+
'--sr-radius': props.radius,
138+
...(props.zIndex != null ? { zIndex: String(props.zIndex) } : {}),
139+
} as Record<string, string>;
140+
});
141+
142+
const containerEl = ref<HTMLDivElement | null>(null);
143+
let control: Control | null = null;
144+
145+
onMounted(() => {
146+
if (!containerEl.value) return;
147+
control = new Control({ element: containerEl.value });
148+
emit('export-polygon-control-created', control);
149+
});
150+
151+
onBeforeUnmount(() => {
152+
control?.setMap?.(null);
153+
control = null;
154+
});
155+
</script>
156+
157+
<style scoped>
158+
.sr-export-polygon-button.p-button {
159+
display: flex;
160+
align-items: center;
161+
justify-content: center;
162+
margin: 0.25rem;
163+
background: var(--sr-bg);
164+
color: var(--sr-color);
165+
border-radius: var(--sr-radius);
166+
}
167+
168+
.sr-export-polygon-button:hover:not(:disabled) {
169+
background: rgba(255, 255, 255, 0.2);
170+
}
171+
172+
.sr-export-polygon-button:disabled {
173+
opacity: 0.5;
174+
cursor: not-allowed;
175+
}
176+
177+
/* Position the control using CSS variables */
178+
.sr-export-polygon-control {
179+
position: absolute;
180+
top: var(--sr-top);
181+
right: var(--sr-right);
182+
bottom: var(--sr-bottom);
183+
left: var(--sr-left);
184+
background: transparent;
185+
padding: 0;
186+
}
187+
</style>

web-client/src/components/SrMap.vue

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import SrCustomTooltip from "@/components//SrCustomTooltip.vue";
5050
import SrDropPinControl from "@/components//SrDropPinControl.vue";
5151
import SrUploadRegionControl from "@/components/SrUploadRegionControl.vue";
52+
import SrExportPolygonControl from "@/components/SrExportPolygonControl.vue";
5253
import Point from 'ol/geom/Point.js';
5354
import { readShapefileToOlFeatures } from "@/composables/useReadShapefiles";
5455
import { useGeoJsonStore } from "@/stores/geoJsonStore";
@@ -911,6 +912,15 @@
911912
}
912913
}
913914
915+
function handleExportPolygonControlCreated(exportControl: any) {
916+
const map = mapRef.value?.map;
917+
if (map) {
918+
map.addControl(exportControl);
919+
} else {
920+
console.error("handleExportPolygonControlCreated Error: map is null");
921+
}
922+
}
923+
914924
async function addRecordLayer() : Promise<void> {
915925
const startTime = performance.now(); // Start time
916926
const reqIds = recTreeStore.allReqIds;
@@ -1217,10 +1227,18 @@
12171227
corner="top-right"
12181228
:offsetX="'0.5rem'"
12191229
:offsetY="'2.5rem'"
1220-
bg="rgba(255,255,255,0.6)"
1230+
bg="rgba(255,255,255,0.6)"
12211231
color="black"
12221232
@upload-region-control-created="handleUploadRegionControlCreated"
12231233
/>
1234+
<SrExportPolygonControl
1235+
v-if="reqParamsStore.iceSat2SelectedAPI != 'atl13x'"
1236+
:map="mapRef?.map"
1237+
corner="top-left"
1238+
:offsetX="'0.5rem'"
1239+
:offsetY="'20.5rem'"
1240+
@export-polygon-control-created="handleExportPolygonControlCreated"
1241+
/>
12241242

12251243
</Map.OlMap>
12261244

@@ -1561,6 +1579,20 @@
15611579
padding: 0.25rem;
15621580
}
15631581
1582+
:deep(.ol-control.sr-export-polygon-control) {
1583+
position: absolute;
1584+
top: var(--sr-top, auto);
1585+
right: var(--sr-right, auto);
1586+
bottom: var(--sr-bottom, auto);
1587+
left: var(--sr-left, auto);
1588+
1589+
background-color: var(--sr-bg, rgba(0, 0, 0, 0.8));
1590+
color: var(--sr-color, white);
1591+
border-radius: var(--sr-radius, var(--p-border-radius));
1592+
1593+
padding: 0.25rem;
1594+
}
1595+
15641596
:deep(.ol-measure-hud) {
15651597
font: 700 0.9rem/1.4 var(--p-font-family, system-ui, sans-serif);
15661598
padding: 0.35rem 0.6rem;

0 commit comments

Comments
 (0)