import streamlit as st import geopandas as gpd import folium from streamlit_folium import folium_static from folium.plugins import Draw from streamlit_folium import st_folium from shapely.geometry import box, shape import requests import json from io import BytesIO from PIL import Image import numpy as np import rasterio from rasterio.io import MemoryFile # Liste des couches de données Swisstopo et ESRI LAYERS = { "Swisstopo - Bâtiments": "ch.swisstopo.vec25-gebaeude", "Swisstopo - Bâtiments 3D": "ch.swisstopo.swissbuildings3d_2", "Swisstopo - Réseau hydrographique": "ch.swisstopo.vec25-gewaessernetz", "Swisstopo - Réseau routier": "ch.swisstopo.vec25-strassen", "Swisstopo - Couverture du sol": "ch.swisstopo.vec25-landesbedeckung", "Swisstopo - Limites administratives": "ch.swisstopo.swissboundaries3d", "Swisstopo - Modèle numérique de terrain": "ch.swisstopo.swissalti3d", "Swisstopo - Carte nationale 1:25'000": "ch.swisstopo.pixelkarte-farbe-pk25.noscale", "Swisstopo - Carte nationale 1:50'000": "ch.swisstopo.pixelkarte-farbe-pk50.noscale", "Swisstopo - Carte nationale 1:100'000": "ch.swisstopo.pixelkarte-farbe-pk100.noscale", "Swisstopo - Orthophoto SWISSIMAGE": "ch.swisstopo.swissimage-product", "Swisstopo - Noms géographiques": "ch.swisstopo.swissnames3d", "ESRI - World Imagery": "esri_world_imagery", "ESRI - World Elevation": "esri_world_elevation", } def extract_swisstopo_data(min_x, min_y, max_x, max_y, layer): bbox = box(min_x, min_y, max_x, max_y) api_url = "https://api3.geo.admin.ch/rest/services/api/MapServer/identify" params = { "geometry": f"{bbox.bounds[0]},{bbox.bounds[1]},{bbox.bounds[2]},{bbox.bounds[3]}", "geometryType": "esriGeometryEnvelope", "layers": f"all:{layer}", "mapExtent": f"{bbox.bounds[0]},{bbox.bounds[1]},{bbox.bounds[2]},{bbox.bounds[3]}", "imageDisplay": "1000,1000,96", "tolerance": "0", "lang": "fr", "sr": "2056" } response = requests.get(api_url, params=params) response.raise_for_status() data = response.json() features = [feature["geometry"] for feature in data["results"]] gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:2056") return gdf def download_swissimage(min_x, min_y, max_x, max_y): api_url = "https://data.geo.admin.ch/api/stac/v0.9/collections/ch.swisstopo.swissimage-dop10/items" params = { "bbox": f"{min_x},{min_y},{max_x},{max_y}", "limit": 1 } response = requests.get(api_url, params=params) response.raise_for_status() data = response.json() if data['features']: asset_url = data['features'][0]['assets']['rgb']['href'] image_response = requests.get(asset_url) image_response.raise_for_status() with MemoryFile(image_response.content) as memfile: with memfile.open() as dataset: image_array = dataset.read() image = Image.fromarray(np.transpose(image_array, (1, 2, 0))) return image return None def download_esri_imagery(min_x, min_y, max_x, max_y): api_url = "https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/export" params = { "bbox": f"{min_x},{min_y},{max_x},{max_y}", "bboxSR": "2056", "size": "1000,1000", "format": "png", "f": "image" } response = requests.get(api_url, params=params) response.raise_for_status() return Image.open(BytesIO(response.content)) def download_esri_elevation(min_x, min_y, max_x, max_y): api_url = "https://elevation.arcgis.com/arcgis/rest/services/WorldElevation/Terrain/ImageServer/exportImage" params = { "bbox": f"{min_x},{min_y},{max_x},{max_y}", "bboxSR": "2056", "size": "1000,1000", "format": "tiff", "pixelType": "F32", "noDataInterpretation": "esriNoDataMatchAny", "interpolation": "RSP_BilinearInterpolation", "f": "image" } response = requests.get(api_url, params=params) response.raise_for_status() return Image.open(BytesIO(response.content)) def main(): st.title("Extracteur de données Swisstopo et ESRI") # Sélection de la méthode d'entrée input_method = st.radio("Choisissez la méthode d'entrée", ["Coordonnées manuelles", "Charger un GeoJSON", "Dessiner sur la carte"]) if input_method == "Coordonnées manuelles": col1, col2 = st.columns(2) with col1: min_x = st.number_input("Coordonnée X minimale", value=2600000) min_y = st.number_input("Coordonnée Y minimale", value=1200000) with col2: max_x = st.number_input("Coordonnée X maximale", value=2601000) max_y = st.number_input("Coordonnée Y maximale", value=1201000) bbox = box(min_x, min_y, max_x, max_y) elif input_method == "Charger un GeoJSON": uploaded_file = st.file_uploader("Choisissez un fichier GeoJSON", type="geojson") if uploaded_file is not None: gdf = gpd.read_file(uploaded_file) bbox = gdf.total_bounds min_x, min_y, max_x, max_y = bbox elif input_method == "Dessiner sur la carte": m = folium.Map(location=[46.8, 8.2], zoom_start=8) Draw(export=True).add_to(m) output = st_folium(m, width=700, height=500) if output["last_active_drawing"]: geom = shape(output["last_active_drawing"]["geometry"]) bbox = geom.bounds min_x, min_y, max_x, max_y = bbox # Sélection de la couche selected_layer_name = st.selectbox("Sélectionnez la couche de données", list(LAYERS.keys())) layer = LAYERS[selected_layer_name] if st.button("Extraire les données"): try: if "Swisstopo" in selected_layer_name: if layer == "ch.swisstopo.swissimage-product": st.subheader("Image SWISSIMAGE") image = download_swissimage(min_x, min_y, max_x, max_y) if image is not None: st.image(image, caption="Image SWISSIMAGE", use_column_width=True) buf = BytesIO() image.save(buf, format="PNG") st.download_button( label="Télécharger l'image SWISSIMAGE (PNG)", data=buf.getvalue(), file_name="swissimage.png", mime="image/png" ) else: st.warning("Aucune image SWISSIMAGE disponible pour cette zone.") else: gdf = extract_swisstopo_data(min_x, min_y, max_x, max_y, layer) st.subheader("Aperçu des données") st.write(gdf.head()) st.subheader("Visualisation des données") m = folium.Map(location=[gdf.geometry.centroid.y.mean(), gdf.geometry.centroid.x.mean()], zoom_start=14) folium.GeoJson(gdf).add_to(m) folium_static(m) st.download_button( label="Télécharger les données (GeoJSON)", data=gdf.to_json(), file_name=f"{selected_layer_name.lower().replace(' ', '_')}.geojson", mime="application/json" ) elif "ESRI" in selected_layer_name: if layer == "esri_world_imagery": st.subheader("Image ESRI World Imagery") image = download_esri_imagery(min_x, min_y, max_x, max_y) st.image(image, caption="ESRI World Imagery", use_column_width=True) buf = BytesIO() image.save(buf, format="PNG") st.download_button( label="Télécharger l'image (PNG)", data=buf.getvalue(), file_name="esri_world_imagery.png", mime="image/png" ) elif layer == "esri_world_elevation": st.subheader("Modèle numérique de terrain ESRI") image = download_esri_elevation(min_x, min_y, max_x, max_y) st.image(image, caption="ESRI World Elevation", use_column_width=True, clamp=True) buf = BytesIO() image.save(buf, format="TIFF") st.download_button( label="Télécharger le MNT (TIFF)", data=buf.getvalue(), file_name="esri_world_elevation.tiff", mime="image/tiff" ) except Exception as e: st.error(f"Une erreur s'est produite : {str(e)}") st.sidebar.markdown("## À propos") st.sidebar.markdown("Cet outil permet d'extraire des données géospatiales de Swisstopo et ESRI.") st.sidebar.markdown("Développé par Vertdure") st.sidebar.markdown("[Code source](https://huggingface.co/spaces/Vertdure/Streamlit/tree/main)") if __name__ == "__main__": main()