Update pages/12_🌲_VertXtractor.py
Browse files- pages/12_🌲_VertXtractor.py +75 -166
pages/12_🌲_VertXtractor.py
CHANGED
@@ -1,15 +1,15 @@
|
|
1 |
import streamlit as st
|
2 |
-
import folium
|
3 |
-
from streamlit_folium import folium_static
|
4 |
import geopandas as gpd
|
5 |
import tempfile
|
6 |
import os
|
7 |
-
import urllib
|
8 |
import json
|
9 |
from pathlib import Path
|
10 |
import datetime
|
11 |
from osgeo import gdal
|
12 |
-
|
|
|
|
|
13 |
|
14 |
# Constants
|
15 |
CATEGORIES = {
|
@@ -33,16 +33,15 @@ DIC_LAYERS = {
|
|
33 |
# Helper functions
|
34 |
def wgs84_to_lv95(lat, lon):
|
35 |
url = f'http://geodesy.geo.admin.ch/reframe/wgs84tolv95?easting={lat}&northing={lon}&format=json'
|
36 |
-
|
37 |
-
|
38 |
return data['easting'], data['northing']
|
39 |
|
40 |
def lv95_to_wgs84(x, y):
|
41 |
url = f'http://geodesy.geo.admin.ch/reframe/lv95towgs84?easting={x}&northing={y}&format=json'
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
return json_res
|
46 |
|
47 |
def detect_and_convert_bbox(bbox):
|
48 |
xmin, ymin, xmax, ymax = bbox
|
@@ -82,7 +81,7 @@ def detect_and_convert_bbox(bbox):
|
|
82 |
wgs84_min = lv95_to_wgs84(xmin, ymin)
|
83 |
wgs84_max = lv95_to_wgs84(xmax, ymax)
|
84 |
|
85 |
-
bbox_wgs84 = (wgs84_min
|
86 |
return (bbox_wgs84, bbox)
|
87 |
|
88 |
return None
|
@@ -98,9 +97,8 @@ def get_list_from_STAC_swisstopo(url, est, sud, ouest, nord, gdb=False):
|
|
98 |
res = []
|
99 |
|
100 |
while url:
|
101 |
-
|
102 |
-
|
103 |
-
json_res = json.loads(txt)
|
104 |
url = None
|
105 |
links = json_res.get('links', None)
|
106 |
if links:
|
@@ -164,26 +162,31 @@ def suppr_doublons_list_mnt(lst):
|
|
164 |
res.append(url)
|
165 |
return res
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
def geojson_forest(bbox, fn_geojson):
|
189 |
xmin, ymin, xmax, ymax = bbox
|
@@ -207,137 +210,12 @@ def geojson_forest(bbox, fn_geojson):
|
|
207 |
url = url_base + query_string
|
208 |
|
209 |
with urllib.request.urlopen(url) as response:
|
210 |
-
|
211 |
-
data = json.loads(response_data)
|
212 |
-
|
213 |
-
with open(fn_geojson, 'w') as f:
|
214 |
-
json.dump(data, f)
|
215 |
-
|
216 |
-
def get_urls(bbox_wgs84, mnt=True, mns=True, bati3D_v2=True, bati3D_v3=True, ortho=True, mnt_resol=0.5, ortho_resol=0.1):
|
217 |
-
est, sud, ouest, nord = bbox_wgs84
|
218 |
-
urls = []
|
219 |
-
|
220 |
-
if mnt:
|
221 |
-
mnt_resol = 0.5 if mnt_resol < 2 else 2
|
222 |
-
tri = f'_{mnt_resol}_'
|
223 |
-
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS['mnt']
|
224 |
-
lst = [v for v in get_list_from_STAC_swisstopo(url, est, sud, ouest, nord) if tri in v]
|
225 |
-
lst = suppr_doublons_list_mnt(lst)
|
226 |
-
urls += lst
|
227 |
-
|
228 |
-
if mns:
|
229 |
-
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS['mns']
|
230 |
-
lst = [v for v in get_list_from_STAC_swisstopo(url, est, sud, ouest, nord) if 'raster' in v]
|
231 |
-
lst = suppr_doublons_list_mnt(lst)
|
232 |
-
urls += lst
|
233 |
-
|
234 |
-
if bati3D_v2:
|
235 |
-
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS['bati3D_v2']
|
236 |
-
lst = get_list_from_STAC_swisstopo(url, est, sud, ouest, nord)
|
237 |
-
lst = suppr_doublons_bati3D_v2(lst)
|
238 |
-
urls += lst
|
239 |
-
|
240 |
-
if bati3D_v3:
|
241 |
-
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS['bati3D_v3']
|
242 |
-
lst = get_list_from_STAC_swisstopo(url, est, sud, ouest, nord, gdb=True)
|
243 |
-
lst = suppr_doublons_bati3D_v3(lst)
|
244 |
-
urls += lst
|
245 |
-
|
246 |
-
if ortho:
|
247 |
-
ortho_resol = 0.1 if ortho_resol < 2 else 2
|
248 |
-
tri = f'_{ortho_resol}_'
|
249 |
-
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS['ortho']
|
250 |
-
lst = [v for v in get_list_from_STAC_swisstopo(url, est, sud, ouest, nord) if tri in v]
|
251 |
-
lst = suppr_doublons_list_ortho(lst)
|
252 |
-
urls += lst
|
253 |
-
|
254 |
-
return urls
|
255 |
-
|
256 |
-
def classification_urls(urls):
|
257 |
-
dic = {}
|
258 |
-
for url in urls:
|
259 |
-
fn = url.split('/')[-1]
|
260 |
-
dirname = fn.split('_')[0]
|
261 |
-
|
262 |
-
if dirname == 'swissbuildings3d':
|
263 |
-
name, version, *a = fn.split('_')
|
264 |
-
if version == '2':
|
265 |
-
an = fn.split('_')[2].split('-')[0]
|
266 |
-
elif version == '3':
|
267 |
-
an = fn.split('_')[3]
|
268 |
-
dirname = f'{name}_v{version}_{an}'
|
269 |
-
|
270 |
-
elif dirname == 'swissalti3d':
|
271 |
-
name, an, no_flle, resol, *a = fn.split('_')
|
272 |
-
if resol == '0.5':
|
273 |
-
resol = '50cm'
|
274 |
-
fn = fn.replace('0.5', '50cm')
|
275 |
-
elif resol == '2':
|
276 |
-
resol = '2m'
|
277 |
-
fn = fn.replace('2', '2m')
|
278 |
-
dirname = f'{name}_{an}_{resol}'
|
279 |
-
elif dirname == 'swisssurface3d-raster':
|
280 |
-
name, an, no_flle, resol, *a = fn.split('_')
|
281 |
-
if resol == '0.5':
|
282 |
-
resol = '50cm'
|
283 |
-
fn = fn.replace('0.5', '50cm')
|
284 |
-
dirname = f'{name}_{an}_{resol}'
|
285 |
-
elif dirname == 'swissimage-dop10':
|
286 |
-
name, an, no_flle, resol, *a = fn.split('_')
|
287 |
-
if resol == '0.1':
|
288 |
-
resol = '10cm'
|
289 |
-
fn = fn.replace('0.1', '10cm')
|
290 |
-
elif resol == '2':
|
291 |
-
resol = '2m'
|
292 |
-
fn = fn.replace('2', '2m')
|
293 |
-
dirname = f'{name}_{an}_{resol}'
|
294 |
-
|
295 |
-
dic.setdefault(dirname, []).append((url, fn))
|
296 |
-
return dic
|
297 |
-
|
298 |
-
def download_files(urls, path):
|
299 |
-
now = datetime.datetime.now()
|
300 |
-
path = Path(path) / f'swisstopo_extraction_{now.strftime("%Y%m%d_%H%M")}'
|
301 |
-
path.mkdir(exist_ok=True)
|
302 |
-
|
303 |
-
for k, v in classification_urls(urls).items():
|
304 |
-
p = path / k
|
305 |
-
p.mkdir(exist_ok=True)
|
306 |
-
for url, fn in v:
|
307 |
-
urllib.request.urlretrieve(url, p / fn)
|
308 |
-
return path
|
309 |
-
|
310 |
-
def download_and_merge_ortho(urls, output_path):
|
311 |
-
"""
|
312 |
-
Download ortho images from URLs and merge them into a single GeoTIFF.
|
313 |
-
|
314 |
-
:param urls: List of URLs to download ortho images from
|
315 |
-
:param output_path: Path to save the merged image
|
316 |
-
:return: Path to the merged image
|
317 |
-
"""
|
318 |
-
# Create a temporary directory to store downloaded files
|
319 |
-
with tempfile.TemporaryDirectory() as temp_dir:
|
320 |
-
# Download all files
|
321 |
-
local_files = []
|
322 |
-
for i, url in enumerate(urls):
|
323 |
-
local_filename = os.path.join(temp_dir, f"ortho_{i}.tif")
|
324 |
-
urllib.request.urlretrieve(url, local_filename)
|
325 |
-
local_files.append(local_filename)
|
326 |
-
|
327 |
-
# Merge downloaded files
|
328 |
-
vrt_options = gdal.BuildVRTOptions(resampleAlg='nearest', addAlpha=False)
|
329 |
-
vrt_path = os.path.join(temp_dir, "merged.vrt")
|
330 |
-
vrt = gdal.BuildVRT(vrt_path, local_files, options=vrt_options)
|
331 |
-
vrt = None # Close the dataset
|
332 |
|
333 |
-
|
334 |
-
|
335 |
-
gdal.Translate(output_path, vrt_path, options=translate_options)
|
336 |
-
|
337 |
-
return output_path
|
338 |
|
339 |
def create_geojson_with_links(urls, bbox):
|
340 |
-
"""Create a GeoJSON file with download links"""
|
341 |
features = []
|
342 |
for url in urls:
|
343 |
feature = {
|
@@ -348,7 +226,7 @@ def create_geojson_with_links(urls, bbox):
|
|
348 |
},
|
349 |
"properties": {
|
350 |
"url": url,
|
351 |
-
"type": url.split('/')[-2].split('_')[0]
|
352 |
}
|
353 |
}
|
354 |
features.append(feature)
|
@@ -360,7 +238,6 @@ def create_geojson_with_links(urls, bbox):
|
|
360 |
return json.dumps(geojson)
|
361 |
|
362 |
def merge_ortho_images(urls, output_format='GTiff'):
|
363 |
-
"""Merge ortho images and return the result as bytes"""
|
364 |
with tempfile.TemporaryDirectory() as temp_dir:
|
365 |
local_files = []
|
366 |
for i, url in enumerate(urls):
|
@@ -387,7 +264,6 @@ def merge_ortho_images(urls, output_format='GTiff'):
|
|
387 |
return f.read()
|
388 |
|
389 |
def prepare_download_package(urls, bbox, ortho_format):
|
390 |
-
"""Prepare a zip file containing all data"""
|
391 |
geojson_data = create_geojson_with_links(urls, bbox)
|
392 |
ortho_urls = [url for url in urls if 'swissimage-dop10' in url]
|
393 |
ortho_data = merge_ortho_images(ortho_urls, ortho_format) if ortho_urls else None
|
@@ -405,7 +281,40 @@ st.set_page_config(page_title="Swiss Geospatial Data Downloader", layout="wide")
|
|
405 |
|
406 |
st.title("Swiss Geospatial Data Downloader")
|
407 |
|
408 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
409 |
|
410 |
if st.session_state.bbox:
|
411 |
st.write(f"Selected bounding box (WGS84): {st.session_state.bbox}")
|
|
|
1 |
import streamlit as st
|
|
|
|
|
2 |
import geopandas as gpd
|
3 |
import tempfile
|
4 |
import os
|
5 |
+
import urllib.request
|
6 |
import json
|
7 |
from pathlib import Path
|
8 |
import datetime
|
9 |
from osgeo import gdal
|
10 |
+
import io
|
11 |
+
import zipfile
|
12 |
+
import base64
|
13 |
|
14 |
# Constants
|
15 |
CATEGORIES = {
|
|
|
33 |
# Helper functions
|
34 |
def wgs84_to_lv95(lat, lon):
|
35 |
url = f'http://geodesy.geo.admin.ch/reframe/wgs84tolv95?easting={lat}&northing={lon}&format=json'
|
36 |
+
with urllib.request.urlopen(url) as response:
|
37 |
+
data = json.load(response)
|
38 |
return data['easting'], data['northing']
|
39 |
|
40 |
def lv95_to_wgs84(x, y):
|
41 |
url = f'http://geodesy.geo.admin.ch/reframe/lv95towgs84?easting={x}&northing={y}&format=json'
|
42 |
+
with urllib.request.urlopen(url) as response:
|
43 |
+
data = json.load(response)
|
44 |
+
return data['easting'], data['northing']
|
|
|
45 |
|
46 |
def detect_and_convert_bbox(bbox):
|
47 |
xmin, ymin, xmax, ymax = bbox
|
|
|
81 |
wgs84_min = lv95_to_wgs84(xmin, ymin)
|
82 |
wgs84_max = lv95_to_wgs84(xmax, ymax)
|
83 |
|
84 |
+
bbox_wgs84 = (wgs84_min, wgs84_max[0], wgs84_max[1], wgs84_min[1])
|
85 |
return (bbox_wgs84, bbox)
|
86 |
|
87 |
return None
|
|
|
97 |
res = []
|
98 |
|
99 |
while url:
|
100 |
+
with urllib.request.urlopen(url) as response:
|
101 |
+
json_res = json.load(response)
|
|
|
102 |
url = None
|
103 |
links = json_res.get('links', None)
|
104 |
if links:
|
|
|
162 |
res.append(url)
|
163 |
return res
|
164 |
|
165 |
+
@st.cache_data
|
166 |
+
def get_urls(bbox_wgs84, data_types, resolutions):
|
167 |
+
urls = []
|
168 |
+
for data_type, enabled in data_types.items():
|
169 |
+
if enabled:
|
170 |
+
url = URL_STAC_SWISSTOPO_BASE + DIC_LAYERS[data_type]
|
171 |
+
if data_type in ['mnt', 'ortho']:
|
172 |
+
resolution = resolutions[data_type]
|
173 |
+
tri = f'_{resolution}_'
|
174 |
+
lst = [v for v in get_list_from_STAC_swisstopo(url, *bbox_wgs84) if tri in v]
|
175 |
+
if data_type == 'mnt':
|
176 |
+
lst = suppr_doublons_list_mnt(lst)
|
177 |
+
else:
|
178 |
+
lst = suppr_doublons_list_ortho(lst)
|
179 |
+
elif data_type == 'mns':
|
180 |
+
lst = [v for v in get_list_from_STAC_swisstopo(url, *bbox_wgs84) if 'raster' in v]
|
181 |
+
lst = suppr_doublons_list_mnt(lst)
|
182 |
+
elif data_type == 'bati3D_v2':
|
183 |
+
lst = get_list_from_STAC_swisstopo(url, *bbox_wgs84)
|
184 |
+
lst = suppr_doublons_bati3D_v2(lst)
|
185 |
+
elif data_type == 'bati3D_v3':
|
186 |
+
lst = get_list_from_STAC_swisstopo(url, *bbox_wgs84, gdb=True)
|
187 |
+
lst = suppr_doublons_bati3D_v3(lst)
|
188 |
+
urls.extend(lst)
|
189 |
+
return urls
|
190 |
|
191 |
def geojson_forest(bbox, fn_geojson):
|
192 |
xmin, ymin, xmax, ymax = bbox
|
|
|
210 |
url = url_base + query_string
|
211 |
|
212 |
with urllib.request.urlopen(url) as response:
|
213 |
+
data = json.load(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
|
215 |
+
with open(fn_geojson, 'w') as f:
|
216 |
+
json.dump(data, f)
|
|
|
|
|
|
|
217 |
|
218 |
def create_geojson_with_links(urls, bbox):
|
|
|
219 |
features = []
|
220 |
for url in urls:
|
221 |
feature = {
|
|
|
226 |
},
|
227 |
"properties": {
|
228 |
"url": url,
|
229 |
+
"type": url.split('/')[-2].split('_')[0]
|
230 |
}
|
231 |
}
|
232 |
features.append(feature)
|
|
|
238 |
return json.dumps(geojson)
|
239 |
|
240 |
def merge_ortho_images(urls, output_format='GTiff'):
|
|
|
241 |
with tempfile.TemporaryDirectory() as temp_dir:
|
242 |
local_files = []
|
243 |
for i, url in enumerate(urls):
|
|
|
264 |
return f.read()
|
265 |
|
266 |
def prepare_download_package(urls, bbox, ortho_format):
|
|
|
267 |
geojson_data = create_geojson_with_links(urls, bbox)
|
268 |
ortho_urls = [url for url in urls if 'swissimage-dop10' in url]
|
269 |
ortho_data = merge_ortho_images(ortho_urls, ortho_format) if ortho_urls else None
|
|
|
281 |
|
282 |
st.title("Swiss Geospatial Data Downloader")
|
283 |
|
284 |
+
# Sidebar for data selection
|
285 |
+
st.sidebar.header("Data Selection")
|
286 |
+
data_types = {
|
287 |
+
'mnt': st.sidebar.checkbox("Digital Terrain Model (MNT)", value=True),
|
288 |
+
'mns': st.sidebar.checkbox("Digital Surface Model (MNS)", value=True),
|
289 |
+
'bati3D_v2': st.sidebar.checkbox("3D Buildings v2", value=True),
|
290 |
+
'bati3D_v3': st.sidebar.checkbox("3D Buildings v3", value=True),
|
291 |
+
'ortho': st.sidebar.checkbox("Orthophotos", value=True),
|
292 |
+
}
|
293 |
+
|
294 |
+
resolutions = {
|
295 |
+
'mnt': st.sidebar.selectbox("MNT Resolution", [0.5, 2.0], index=0),
|
296 |
+
'ortho': st.sidebar.selectbox("Orthophoto Resolution", [0.1, 2.0], index=0),
|
297 |
+
}
|
298 |
+
|
299 |
+
ortho_format = st.sidebar.selectbox("Ortho Output Format", ['GTiff', 'JPEG', 'PNG'], index=0)
|
300 |
+
|
301 |
+
# Main content area
|
302 |
+
st.subheader("Enter Bounding Box Coordinates")
|
303 |
+
col1, col2, col3, col4 = st.columns(4)
|
304 |
+
with col1:
|
305 |
+
xmin = st.number_input("Min Longitude", value=6.0500, step=0.0001, format="%.4f")
|
306 |
+
with col2:
|
307 |
+
ymin = st.number_input("Min Latitude", value=46.1800, step=0.0001, format="%.4f")
|
308 |
+
with col3:
|
309 |
+
xmax = st.number_input("Max Longitude", value=6.2200, step=0.0001, format="%.4f")
|
310 |
+
with col4:
|
311 |
+
ymax = st.number_input("Max Latitude", value=46.2500, step=0.0001, format="%.4f")
|
312 |
+
|
313 |
+
if 'bbox' not in st.session_state:
|
314 |
+
st.session_state.bbox = None
|
315 |
+
|
316 |
+
if st.button("Set Bounding Box"):
|
317 |
+
st.session_state.bbox = [xmin, ymin, xmax, ymax]
|
318 |
|
319 |
if st.session_state.bbox:
|
320 |
st.write(f"Selected bounding box (WGS84): {st.session_state.bbox}")
|