Vertdure commited on
Commit
eb034a8
·
verified ·
1 Parent(s): c184731

Delete pages/12_🌲_VertXtractor.py

Browse files
Files changed (1) hide show
  1. pages/12_🌲_VertXtractor.py +0 -443
pages/12_🌲_VertXtractor.py DELETED
@@ -1,443 +0,0 @@
1
- import streamlit as st
2
- import folium
3
- from streamlit_folium import st_folium
4
- from folium.plugins import Draw
5
- import geopandas as gpd
6
- import tempfile
7
- import os
8
- import urllib.request
9
- import json
10
- from pathlib import Path
11
- import datetime
12
- from osgeo import gdal
13
- import io
14
- import zipfile
15
- import base64
16
- import concurrent.futures
17
- import requests
18
- from functools import partial
19
-
20
- # Constants
21
- CATEGORIES = {
22
- 'Gebueschwald': 'Forêt buissonnante',
23
- 'Wald': 'Forêt',
24
- 'Wald offen': 'Forêt claisemée',
25
- 'Gehoelzflaeche': 'Zone boisée',
26
- }
27
- MERGE_CATEGORIES = True
28
-
29
- URL_STAC_SWISSTOPO_BASE = 'https://data.geo.admin.ch/api/stac/v0.9/collections/'
30
-
31
- DIC_LAYERS = {
32
- 'ortho': 'ch.swisstopo.swissimage-dop10',
33
- 'mnt': 'ch.swisstopo.swissalti3d',
34
- 'mns': 'ch.swisstopo.swisssurface3d-raster',
35
- 'bati3D_v2': 'ch.swisstopo.swissbuildings3d_2',
36
- 'bati3D_v3': 'ch.swisstopo.swissbuildings3d_3_0',
37
- }
38
-
39
- # Helper functions
40
- def wgs84_to_lv95(lat, lon):
41
- url = f'http://geodesy.geo.admin.ch/reframe/wgs84tolv95?easting={lon}&northing={lat}&format=json'
42
- with urllib.request.urlopen(url) as response:
43
- data = json.load(response)
44
- return data['easting'], data['northing']
45
-
46
- def lv95_to_wgs84(x, y):
47
- url = f'http://geodesy.geo.admin.ch/reframe/lv95towgs84?easting={x}&northing={y}&format=json'
48
- with urllib.request.urlopen(url) as response:
49
- data = json.load(response)
50
- return data['northing'], data['easting']
51
-
52
- def detect_and_convert_bbox(bbox):
53
- xmin, ymin, xmax, ymax = bbox
54
-
55
- wgs84_margin = 0.9
56
- wgs84_bounds = {
57
- 'xmin': 5.96 - wgs84_margin,
58
- 'ymin': 45.82 - wgs84_margin,
59
- 'xmax': 10.49 + wgs84_margin,
60
- 'ymax': 47.81 + wgs84_margin
61
- }
62
-
63
- lv95_margin = 100000
64
- lv95_bounds = {
65
- 'xmin': 2485000 - lv95_margin,
66
- 'ymin': 1075000 - lv95_margin,
67
- 'xmax': 2834000 + lv95_margin,
68
- 'ymax': 1296000 + lv95_margin
69
- }
70
-
71
- if (wgs84_bounds['xmin'] <= xmin <= wgs84_bounds['xmax'] and
72
- wgs84_bounds['ymin'] <= ymin <= wgs84_bounds['ymax'] and
73
- wgs84_bounds['xmin'] <= xmax <= wgs84_bounds['xmax'] and
74
- wgs84_bounds['ymin'] <= ymax <= wgs84_bounds['ymax']):
75
-
76
- lv95_min = wgs84_to_lv95(ymin, xmin)
77
- lv95_max = wgs84_to_lv95(ymax, xmax)
78
-
79
- bbox_lv95 = (lv95_min[0], lv95_min[1], lv95_max[0], lv95_max[1])
80
- return (bbox, bbox_lv95)
81
-
82
- if (lv95_bounds['xmin'] <= xmin <= lv95_bounds['xmax'] and
83
- lv95_bounds['ymin'] <= ymin <= lv95_bounds['ymax'] and
84
- lv95_bounds['xmin'] <= xmax <= lv95_bounds['xmax'] and
85
- lv95_bounds['ymin'] <= ymax <= lv95_bounds['ymax']):
86
-
87
- wgs84_min = lv95_to_wgs84(xmin, ymin)
88
- wgs84_max = lv95_to_wgs84(xmax, ymax)
89
-
90
- bbox_wgs84 = (wgs84_min[1], wgs84_min[0], wgs84_max[1], wgs84_max[0])
91
- return (bbox_wgs84, bbox)
92
-
93
- return None
94
-
95
- def get_list_from_STAC_swisstopo(url, est, sud, ouest, nord, gdb=False):
96
- if gdb:
97
- lst_indesirables = []
98
- else:
99
- lst_indesirables = ['.xyz.zip', '.gdb.zip']
100
-
101
- sufixe_url = f"/items?bbox={est},{sud},{ouest},{nord}"
102
- url += sufixe_url
103
- res = []
104
-
105
- while url:
106
- with urllib.request.urlopen(url) as response:
107
- json_res = json.load(response)
108
- url = None
109
- links = json_res.get('links', None)
110
- if links:
111
- for link in links:
112
- if link['rel'] == 'next':
113
- url = link['href']
114
-
115
- for item in json_res['features']:
116
- for k, dic in item['assets'].items():
117
- href = dic['href']
118
- if gdb:
119
- if href[-8:] == '.gdb.zip':
120
- if len(dic['href'].split('/')[-1].split('_')) == 7:
121
- res.append(dic['href'])
122
- else:
123
- if href[-8:] not in lst_indesirables:
124
- res.append(dic['href'])
125
- return res
126
-
127
- def suppr_doublons_bati3D_v2(lst_url):
128
- dico = {}
129
- dxf_files = [url for url in lst_url if url[-8:] == '.dxf.zip']
130
- for dxf in dxf_files:
131
- *a, date, feuille = dxf.split('/')[-2].split('_')
132
- dico.setdefault(feuille, []).append((date, dxf))
133
- res = []
134
- for k, liste in dico.items():
135
- res.append(sorted(liste, reverse=True)[0][1])
136
- return res
137
-
138
- def suppr_doublons_bati3D_v3(lst_url):
139
- dico = {}
140
- gdb_files = [url for url in lst_url if url[-8:] == '.gdb.zip']
141
- for gdb in gdb_files:
142
- *a, date, feuille = gdb.split('/')[-2].split('_')
143
- dico.setdefault(feuille, []).append((date, gdb))
144
- res = []
145
- for k, liste in dico.items():
146
- res.append(sorted(liste, reverse=True)[0][1])
147
- return res
148
-
149
- def suppr_doublons_list_ortho(lst):
150
- dic = {}
151
- for url in lst:
152
- nom, an, noflle, taille_px, epsg = url.split('/')[-1][:-4].split('_')
153
- dic.setdefault((noflle, float(taille_px)), []).append((an, url))
154
- res = []
155
- for noflle, lst in dic.items():
156
- an, url = sorted(lst, reverse=True)[0]
157
- res.append(url)
158
- return res
159
-
160
- def suppr_doublons_list_mnt(lst):
161
- dic = {}
162
- for url in lst:
163
- nom, an, noflle, taille_px, epsg, inconnu = url.split('/')[-1][:-4].split('_')
164
- dic.setdefault((noflle, float(taille_px)), []).append((an, url))
165
- res = []
166
- for noflle, lst in dic.items():
167
- an, url = sorted(lst, reverse=True)[0]
168
- res.append(url)
169
- return res
170
-
171
- @st.cache_data
172
- def get_urls(bbox_wgs84, data_types, resolutions):
173
- urls = []
174
- with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
175
- future_to_data_type = {
176
- executor.submit(
177
- get_urls_for_data_type,
178
- data_type,
179
- bbox_wgs84,
180
- resolutions.get(data_type)
181
- ): data_type for data_type, enabled in data_types.items() if enabled
182
- }
183
- for future in concurrent.futures.as_completed(future_to_data_type):
184
- data_type = future_to_data_type[future]
185
- try:
186
- urls.extend(future.result())
187
- except Exception as exc:
188
- st.error(f"Error fetching URLs for {data_type}: {exc}")
189
- return urls
190
-
191
- def get_urls_for_data_type(data_type, bbox_wgs84, resolution=None):
192
- url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS[data_type]
193
- if data_type in ['mnt', 'ortho']:
194
- tri = f'_{resolution}_'
195
- lst = [v for v in get_list_from_STAC_swisstopo(url, *bbox_wgs84) if tri in v]
196
- if data_type == 'mnt':
197
- return suppr_doublons_list_mnt(lst)
198
- else:
199
- return suppr_doublons_list_ortho(lst)
200
- elif data_type == 'mns':
201
- lst = [v for v in get_list_from_STAC_swisstopo(url, *bbox_wgs84) if 'raster' in v]
202
- return suppr_doublons_list_mnt(lst)
203
- elif data_type == 'bati3D_v2':
204
- lst = get_list_from_STAC_swisstopo(url, *bbox_wgs84)
205
- return suppr_doublons_bati3D_v2(lst)
206
- elif data_type == 'bati3D_v3':
207
- lst = get_list_from_STAC_swisstopo(url, *bbox_wgs84, gdb=True)
208
- return suppr_doublons_bati3D_v3(lst)
209
- return []
210
-
211
- def fetch_url(url):
212
- response = requests.get(url)
213
- return response.content
214
-
215
- def merge_ortho_images(urls, output_format='GTiff'):
216
- try:
217
- with tempfile.TemporaryDirectory() as temp_dir:
218
- local_files = []
219
- with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
220
- future_to_url = {executor.submit(fetch_url, url): url for url in urls}
221
- for i, future in enumerate(concurrent.futures.as_completed(future_to_url)):
222
- url = future_to_url[future]
223
- try:
224
- data = future.result()
225
- local_filename = os.path.join(temp_dir, f"ortho_{i}.tif")
226
- with open(local_filename, 'wb') as f:
227
- f.write(data)
228
- local_files.append(local_filename)
229
- except Exception as exc:
230
- st.error(f"Error downloading {url}: {exc}")
231
-
232
- if not local_files:
233
- st.error("No ortho images were successfully downloaded.")
234
- return None
235
-
236
- vrt_options = gdal.BuildVRTOptions(resampleAlg='nearest', addAlpha=False)
237
- vrt_path = os.path.join(temp_dir, "merged.vrt")
238
- vrt = gdal.BuildVRT(vrt_path, local_files, options=vrt_options)
239
- vrt = None # Close the dataset
240
-
241
- output_path = os.path.join(temp_dir, f"merged.{output_format.lower()}")
242
- if output_format == 'GTiff':
243
- translate_options = gdal.TranslateOptions(format="GTiff", creationOptions=["COMPRESS=LZW", "TILED=YES"])
244
- elif output_format == 'JPEG':
245
- translate_options = gdal.TranslateOptions(format="JPEG", creationOptions=["QUALITY=85"])
246
- elif output_format == 'PNG':
247
- translate_options = gdal.TranslateOptions(format="PNG", creationOptions=["COMPRESS=DEFLATE"])
248
- else:
249
- st.error(f"Unsupported output format: {output_format}")
250
- return None
251
-
252
- gdal.Translate(output_path, vrt_path, options=translate_options)
253
-
254
- if not os.path.exists(output_path):
255
- st.error(f"Failed to create merged image: {output_path}")
256
- return None
257
-
258
- with open(output_path, 'rb') as f:
259
- return f.read()
260
- except Exception as e:
261
- st.error(f"Error in merge_ortho_images: {e}")
262
- return None
263
-
264
- def create_geojson_with_links(urls, bbox):
265
- features = []
266
- for url in urls:
267
- feature = {
268
- "type": "Feature",
269
- "geometry": {
270
- "type": "Polygon",
271
- "coordinates": [bbox]
272
- },
273
- "properties": {
274
- "url": url,
275
- "type": url.split('/')[-2].split('_')[0]
276
- }
277
- }
278
- features.append(feature)
279
-
280
- geojson = {
281
- "type": "FeatureCollection",
282
- "features": features
283
- }
284
- return json.dumps(geojson)
285
-
286
- @st.cache_data
287
- def prepare_download_package(urls, bbox, ortho_format):
288
- geojson_data = create_geojson_with_links(urls, bbox)
289
- ortho_urls = [url for url in urls if 'swissimage-dop10' in url]
290
- ortho_data = merge_ortho_images(ortho_urls, ortho_format) if ortho_urls else None
291
-
292
- zip_buffer = io.BytesIO()
293
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
294
- zip_file.writestr('download_links.geojson', geojson_data)
295
- if ortho_data:
296
- zip_file.writestr(f'merged_ortho.{ortho_format.lower()}', ortho_data)
297
- else:
298
- st.warning("Failed to merge ortho images. Only download links will be included in the package.")
299
-
300
- return zip_buffer.getvalue()
301
-
302
- def geojson_forest(bbox, fn_geojson):
303
- xmin, ymin, xmax, ymax = bbox
304
- url_base = 'https://hepiadata.hesge.ch/arcgis/rest/services/suisse/TLM_C4D_couverture_sol/FeatureServer/1/query?'
305
-
306
- sql = ' OR '.join([f"OBJEKTART='{cat}'" for cat in CATEGORIES.keys()])
307
-
308
- params = {
309
- "geometry": f"{xmin},{ymin},{xmax},{ymax}",
310
- "geometryType": "esriGeometryEnvelope",
311
- "returnGeometry": "true",
312
- "outFields": "OBJEKTART",
313
- "orderByFields": "OBJEKTART",
314
- "where": sql,
315
- "returnZ": "true",
316
- "outSR": '2056',
317
- "spatialRel": "esriSpatialRelIntersects",
318
- "f": "geojson"
319
- }
320
- query_string = urllib.parse.urlencode(params)
321
- url = url_base + query_string
322
-
323
- with urllib.request.urlopen(url) as response:
324
- data = json.load(response)
325
-
326
- with open(fn_geojson, 'w') as f:
327
- json.dump(data, f)
328
-
329
- # Streamlit app
330
- st.set_page_config(page_title="Swiss Geospatial Data Downloader", layout="wide")
331
-
332
- st.title("Swiss Geospatial Data Downloader")
333
-
334
- # Sidebar for data selection
335
- st.sidebar.header("Data Selection")
336
- data_types = {
337
- 'mnt': st.sidebar.checkbox("Digital Terrain Model (MNT)", value=True),
338
- 'mns': st.sidebar.checkbox("Digital Surface Model (MNS)", value=True),
339
- 'bati3D_v2': st.sidebar.checkbox("3D Buildings v2", value=True),
340
- 'bati3D_v3': st.sidebar.checkbox("3D Buildings v3", value=True),
341
- 'ortho': st.sidebar.checkbox("Orthophotos", value=True),
342
- }
343
-
344
- resolutions = {
345
- 'mnt': st.sidebar.selectbox("MNT Resolution", [0.5, 2.0], index=0),
346
- 'ortho': st.sidebar.selectbox("Orthophoto Resolution", [0.1, 2.0], index=0),
347
- }
348
-
349
- ortho_format = st.sidebar.selectbox("Ortho Output Format", ['GTiff', 'JPEG', 'PNG'], index=0)
350
-
351
- # Main content area
352
- st.subheader("Select Bounding Box")
353
-
354
- # Create a map centered on Switzerland
355
- m = folium.Map(location=[46.8182, 8.2275], zoom_start=8)
356
-
357
- # Add rectangle draw control
358
- draw = Draw(
359
- draw_options={
360
- 'rectangle': True,
361
- 'polygon': False,
362
- 'polyline': False,
363
- 'circle': False,
364
- 'marker': False,
365
- 'circlemarker': False
366
- },
367
- edit_options={'edit': False}
368
- )
369
- draw.add_to(m)
370
-
371
- # Use st_folium to render the map and get the drawn bbox
372
- output = st_folium(m, width=700, height=500)
373
-
374
- # Initialize session state for bbox
375
- if 'bbox' not in st.session_state:
376
- st.session_state.bbox = [6.0, 46.0, 10.0, 47.0] # Default values for Switzerland
377
-
378
- # Update bbox if a new one is drawn
379
- if output['last_active_drawing']:
380
- coordinates = output['last_active_drawing']['geometry']['coordinates'][0]
381
- st.session_state.bbox = [
382
- min(coord[0] for coord in coordinates),
383
- min(coord[1] for coord in coordinates),
384
- max(coord[0] for coord in coordinates),
385
- max(coord[1] for coord in coordinates)
386
- ]
387
-
388
- # Display and allow editing of bounding box coordinates
389
- st.subheader("Enter Bounding Box Coordinates")
390
- col1, col2, col3, col4 = st.columns(4)
391
- with col1:
392
- xmin = st.number_input("Min Longitude", value=st.session_state.bbox[0], format="%.4f", key="xmin")
393
- with col2:
394
- ymin = st.number_input("Min Latitude", value=st.session_state.bbox[1], format="%.4f", key="ymin")
395
- with col3:
396
- xmax = st.number_input("Max Longitude", value=st.session_state.bbox[2], format="%.4f", key="xmax")
397
- with col4:
398
- ymax = st.number_input("Max Latitude", value=st.session_state.bbox[3], format="%.4f", key="ymax")
399
-
400
- # Update session state if coordinates are manually changed
401
- st.session_state.bbox = [xmin, ymin, xmax, ymax]
402
-
403
- if st.session_state.bbox:
404
- st.write(f"Selected bounding box (WGS84): {st.session_state.bbox}")
405
-
406
- bbox_results = detect_and_convert_bbox(st.session_state.bbox)
407
-
408
- if bbox_results:
409
- bbox_wgs84, bbox_lv95 = bbox_results
410
- st.write(f"Converted bounding box (LV95): {bbox_lv95}")
411
-
412
- if st.button("Get Download Package"):
413
- with st.spinner("Preparing download package..."):
414
- urls = get_urls(bbox_wgs84, data_types, resolutions)
415
- if urls:
416
- zip_data = prepare_download_package(urls, bbox_wgs84, ortho_format)
417
- b64 = base64.b64encode(zip_data).decode()
418
- href = f'<a href="data:application/zip;base64,{b64}" download="swiss_geospatial_data.zip">Download All Data</a>'
419
- st.markdown(href, unsafe_allow_html=True)
420
- st.success("Download package prepared. Click the link above to download.")
421
- else:
422
- st.warning("No files found for the selected area and options.")
423
-
424
- if st.button("Download Forest Data"):
425
- with st.spinner("Downloading forest data..."):
426
- with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.geojson') as tmp:
427
- geojson_forest(bbox_lv95, tmp.name)
428
- gdf = gpd.read_file(tmp.name)
429
- st.write(gdf)
430
-
431
- # Provide download link for forest data
432
- with open(tmp.name, 'r') as f:
433
- forest_data = f.read()
434
- b64 = base64.b64encode(forest_data.encode()).decode()
435
- href = f'<a href="data:application/json;base64,{b64}" download="forest_data.geojson">Download Forest Data</a>'
436
- st.markdown(href, unsafe_allow_html=True)
437
-
438
- os.unlink(tmp.name)
439
- st.success("Forest data prepared. Click the link above to download.")
440
- else:
441
- st.error("Selected area is outside Switzerland. Please select an area within Switzerland.")
442
-
443
- st.sidebar.info("This application allows you to download various types of geospatial data for Switzerland. Select the data types you want, draw a bounding box on the map, and click 'Get Download Package' to prepare all data for download.")