|
import streamlit as st |
|
import geopandas as gpd |
|
import folium |
|
from streamlit_folium import folium_static, st_folium |
|
from folium.plugins import Draw |
|
import requests |
|
from shapely.geometry import box |
|
import json |
|
import pyproj |
|
|
|
st.set_page_config(layout="wide", page_title="Extracteur de données géospatiales") |
|
|
|
ESRI_SERVICES = { |
|
'World_Imagery' : 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer', |
|
'World_Topo_Map' : 'https://services.arcgisonline.com/arcgis/rest/services/World_Topo_Map/MapServer', |
|
'World_Shaded_Relief' : 'https://services.arcgisonline.com/arcgis/rest/services/World_Shaded_Relief/MapServer', |
|
'World_Terrain_Base' : 'https://services.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer', |
|
'World_Street_Map' : 'https://services.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer', |
|
'World_Elevation' : 'https://elevation.arcgis.com/arcgis/rest/services/WorldElevation/Terrain/ImageServer', |
|
'World_Elevation3D' : 'https://services.arcgisonline.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer', |
|
} |
|
|
|
LAYERS = { |
|
"Swisstopo - SWISSIMAGE 10 cm": {"id": "ch.swisstopo.swissimage-dop10", "source": "swisstopo", "type": "image"}, |
|
"Swisstopo - Carte nationale 1:25'000": {"id": "ch.swisstopo.pixelkarte-farbe-pk25.noscale", "source": "swisstopo", "type": "image"}, |
|
"Swisstopo - MNT": {"id": "ch.swisstopo.swissalti3d", "source": "swisstopo", "type": "raster"}, |
|
"Swisstopo - Carte géologique": {"id": "ch.swisstopo.geologie-geologische_karte", "source": "swisstopo", "type": "image"}, |
|
"Swisstopo - Limites administratives": {"id": "ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill", "source": "swisstopo", "type": "vector"}, |
|
"Swisstopo - Réseau hydrographique": {"id": "ch.bafu.vec25-gewaessernetz", "source": "swisstopo", "type": "vector"}, |
|
"Swisstopo - swissBUILDINGS3D 3.0 Beta": {"id": "ch.swisstopo.swissbuildings3d_3_0", "source": "swisstopo", "type": "3d"}, |
|
"ESRI - World Imagery": {"id": "World_Imagery", "source": "esri", "type": "image"}, |
|
"ESRI - World Elevation": {"id": "World_Elevation", "source": "esri", "type": "raster"}, |
|
"ESRI - World Elevation3D": {"id": "World_Elevation3D", "source": "esri", "type": "raster"}, |
|
"ESRI - World Topographic": {"id": "World_Topo_Map", "source": "esri", "type": "image"}, |
|
"ESRI - World Street Map": {"id": "World_Street_Map", "source": "esri", "type": "image"}, |
|
"ESRI - World Terrain": {"id": "World_Terrain_Base", "source": "esri", "type": "image"}, |
|
} |
|
|
|
|
|
wgs84 = pyproj.CRS('EPSG:4326') |
|
ch1903 = pyproj.CRS('EPSG:2056') |
|
|
|
|
|
transformer = pyproj.Transformer.from_crs(wgs84, ch1903, always_xy=True) |
|
|
|
def draw_box_on_map(): |
|
m = folium.Map(location=[46.8, 8.2], zoom_start=8) |
|
Draw(draw_options={'polyline': False, 'polygon': False, 'circle': False, 'marker': False, 'circlemarker': False}, |
|
edit_options={'edit': False}).add_to(m) |
|
return st_folium(m, width=700, height=500) |
|
|
|
def create_geojson_from_box(bbox): |
|
gdf = gpd.GeoDataFrame({'geometry': [bbox]}, crs="EPSG:4326") |
|
return json.loads(gdf.to_json()) |
|
|
|
@st.cache_data |
|
def get_download_url(bbox, layer_info): |
|
if layer_info["source"] == "swisstopo": |
|
|
|
xmin, ymin = transformer.transform(bbox[0], bbox[1]) |
|
xmax, ymax = transformer.transform(bbox[2], bbox[3]) |
|
swiss_bbox = (xmin, ymin, xmax, ymax) |
|
return get_swisstopo_url(swiss_bbox, layer_info) |
|
else: |
|
return get_esri_url(bbox, layer_info) |
|
|
|
def get_swisstopo_url(bbox, layer_info): |
|
width = bbox[2] - bbox[0] |
|
height = bbox[3] - bbox[1] |
|
ratio = width / height |
|
|
|
if ratio > 1: |
|
size = f'1000,{int(1000/ratio)}' |
|
else: |
|
size = f'{int(1000*ratio)},1000' |
|
|
|
|
|
buffer = min(width, height) * 0.01 |
|
bbox_buffered = (bbox[0]-buffer, bbox[1]-buffer, bbox[2]+buffer, bbox[3]+buffer) |
|
|
|
api_url = f"https://data.geo.admin.ch/api/stac/v0.9/collections/{layer_info['id']}/items" |
|
params = { |
|
"bbox": ",".join(map(str, bbox_buffered)), |
|
"limit": 1, |
|
"width": size.split(',')[0], |
|
"height": size.split(',')[1] |
|
} |
|
response = requests.get(api_url, params=params) |
|
data = response.json() |
|
if data['features']: |
|
feature = data['features'][0] |
|
asset_keys = feature['assets'].keys() |
|
data_key = 'rgb' if 'rgb' in asset_keys else 'data' if 'data' in asset_keys else next(iter(asset_keys)) |
|
return feature['assets'][data_key]['href'] |
|
return None |
|
|
|
def get_esri_url(bbox, layer_info): |
|
service_url = ESRI_SERVICES.get(layer_info["id"]) |
|
if not service_url: |
|
return None |
|
|
|
if layer_info["type"] == "image": |
|
return get_esri_image_url(bbox, service_url) |
|
elif layer_info["type"] == "raster": |
|
return get_esri_terrain_url(bbox, service_url) |
|
return None |
|
|
|
def get_esri_image_url(bbox, service_url): |
|
width = bbox[2] - bbox[0] |
|
height = bbox[3] - bbox[1] |
|
ratio = width / height |
|
|
|
if ratio > 1: |
|
size = f'1000,{int(1000/ratio)}' |
|
else: |
|
size = f'{int(1000*ratio)},1000' |
|
|
|
|
|
buffer = min(width, height) * 0.01 |
|
bbox_buffered = (bbox[0]-buffer, bbox[1]-buffer, bbox[2]+buffer, bbox[3]+buffer) |
|
|
|
params = { |
|
'bbox': f'{bbox_buffered[0]},{bbox_buffered[1]},{bbox_buffered[2]},{bbox_buffered[3]}', |
|
'bboxSR': 4326, |
|
'size': size, |
|
'imageSR': 4326, |
|
'format': 'png', |
|
'f': 'html', |
|
'transparent': 'true' |
|
} |
|
return f"{service_url}/export?{'&'.join([f'{k}={v}' for k, v in params.items()])}" |
|
|
|
def get_esri_terrain_url(bbox, service_url): |
|
width = bbox[2] - bbox[0] |
|
height = bbox[3] - bbox[1] |
|
ratio = width / height |
|
|
|
if ratio > 1: |
|
size = f'1000,{int(1000/ratio)}' |
|
else: |
|
size = f'{int(1000*ratio)},1000' |
|
|
|
|
|
buffer = min(width, height) * 0.01 |
|
bbox_buffered = (bbox[0]-buffer, bbox[1]-buffer, bbox[2]+buffer, bbox[3]+buffer) |
|
|
|
params = { |
|
'bbox': f'{bbox_buffered[0]},{bbox_buffered[1]},{bbox_buffered[2]},{bbox_buffered[3]}', |
|
'bboxSR': 4326, |
|
'size': size, |
|
'imageSR': 4326, |
|
'format': 'tiff', |
|
'pixelType': 'F32', |
|
'noDataInterpretation': 'esriNoDataMatchAny', |
|
'interpolation': '+RSP_BilinearInterpolation', |
|
'f': 'image' |
|
} |
|
return f"{service_url}/exportImage?{'&'.join([f'{k}={v}' for k, v in params.items()])}" |
|
|
|
def main(): |
|
st.title("Extracteur de données Swisstopo et ESRI") |
|
|
|
col1, col2 = st.columns([1, 2]) |
|
|
|
with col1: |
|
st.subheader("Sélection des couches") |
|
selected_layers = st.multiselect("Couches à extraire", list(LAYERS.keys())) |
|
|
|
st.subheader("Définir la zone d'intérêt") |
|
method = st.radio("Méthode", ["Dessiner", "GeoJSON"], horizontal=True) |
|
|
|
if method == "GeoJSON": |
|
uploaded_file = st.file_uploader("GeoJSON", type="geojson") |
|
if uploaded_file: |
|
gdf = gpd.read_file(uploaded_file) |
|
st.session_state['geojson'] = json.loads(gdf.to_crs(4326).to_json()) |
|
st.success("GeoJSON chargé!") |
|
|
|
if st.button("Obtenir les liens de téléchargement"): |
|
if 'geojson' not in st.session_state: |
|
st.error("Définissez d'abord une zone d'intérêt.") |
|
elif not selected_layers: |
|
st.error("Sélectionnez au moins une couche à extraire.") |
|
else: |
|
for layer_name in selected_layers: |
|
layer_info = LAYERS[layer_name] |
|
bbox = gpd.GeoDataFrame.from_features(st.session_state['geojson']).total_bounds |
|
st.write(f"Traitement de la couche : {layer_name}") |
|
st.write(f"Bbox (WGS84): {bbox}") |
|
download_url = get_download_url(bbox, layer_info) |
|
if download_url: |
|
st.markdown(f"[Télécharger {layer_name}]({download_url})") |
|
else: |
|
st.error(f"Impossible d'obtenir le lien pour {layer_name}") |
|
|
|
st.markdown("---") |
|
st.markdown("## À propos\nExtracteur de données géospatiales Swisstopo et ESRI.\nDéveloppé par Vertdure") |
|
|
|
with col2: |
|
if 'bbox' not in st.session_state: |
|
map_data = draw_box_on_map() |
|
if map_data['last_active_drawing']: |
|
coords = map_data['last_active_drawing']['geometry']['coordinates'][0] |
|
bbox = box(min(c[0] for c in coords), min(c[1] for c in coords), |
|
max(c[0] for c in coords), max(c[1] for c in coords)) |
|
st.session_state['bbox'] = bbox |
|
st.session_state['geojson'] = create_geojson_from_box(bbox) |
|
st.success("Zone sélectionnée!") |
|
else: |
|
st.success("Zone déjà sélectionnée. Utilisez le bouton ci-dessous pour redessiner.") |
|
if st.button("Redessiner la zone"): |
|
del st.session_state['bbox'] |
|
del st.session_state['geojson'] |
|
st.experimental_rerun() |
|
else: |
|
m = folium.Map(location=[46.8, 8.2], zoom_start=8) |
|
folium.GeoJson(st.session_state['geojson']).add_to(m) |
|
st_folium(m, width=700, height=500) |
|
|
|
if __name__ == "__main__": |
|
main() |