Streamlit / pages /5_📍_VertXtractor.py
Vertdure's picture
Update pages/5_📍_VertXtractor.py
71cfbc3 verified
raw
history blame
10.3 kB
import streamlit as st
import geopandas as gpd
import rasterio
import numpy as np
from pyproj import Transformer
import trimesh
import logging
from io import BytesIO
import folium
from streamlit_folium import folium_static, st_folium
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import random
import requests
from rasterio.io import MemoryFile
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@st.cache_data
def load_swiss_dem(bbox):
"""
Charge le MNT de la Suisse depuis l'API STAC de Swisstopo.
"""
api_url = "https://data.geo.admin.ch/api/stac/v0.9/collections/ch.swisstopo.swissalti3d/items"
params = {
"bbox": ",".join(map(str, bbox)),
"limit": 1
}
st.write("ParamĂštres de l'API :", params)
try:
response = requests.get(api_url, params=params)
st.write("Code de statut de la réponse :", response.status_code)
st.write("Contenu de la réponse :", response.text)
response.raise_for_status()
data = response.json()
if 'features' in data and data['features']:
feature = data['features'][0]
assets = feature.get('assets', {})
# Chercher le fichier GeoTIFF avec la meilleure résolution
tiff_asset = None
best_resolution = float('inf')
for asset_name, asset_info in assets.items():
if asset_info['type'].startswith('image/tiff'):
resolution = float(asset_info.get('eo', float('inf')))
if resolution < best_resolution:
best_resolution = resolution
tiff_asset = asset_info
if tiff_asset:
asset_url = tiff_asset['href']
st.write(f"URL de l'asset sélectionné : {asset_url}")
response = requests.get(asset_url)
response.raise_for_status()
with MemoryFile(response.content) as memfile:
with memfile.open() as dataset:
dem = dataset.read(1)
transform = dataset.transform
return dem, transform
else:
st.error("Aucun fichier GeoTIFF trouvé dans les assets")
else:
st.error("Aucune donnée trouvée dans la réponse de l'API")
except requests.RequestException as e:
st.error(f"Erreur lors de la requĂȘte Ă  l'API : {str(e)}")
except ValueError as e:
st.error(f"Erreur lors du décodage JSON : {str(e)}")
except Exception as e:
st.error(f"Une erreur inattendue s'est produite : {str(e)}")
return None, None
def extract_dem_region(dem, transform, bbox):
"""
Extrait une région spécifique du MNT basée sur une boßte englobante.
"""
minx, miny, maxx, maxy = bbox
rows, cols = rasterio.transform.rowcol(transform, [minx, maxx], [miny, maxy])
window = ((min(rows), max(rows)), (min(cols), max(cols)))
dem_region = dem[window[0][0]:window[0][1], window[1][0]:window[1][1]]
new_transform = rasterio.transform.from_bounds(minx, miny, maxx, maxy,
dem_region.shape[1], dem_region.shape[0])
return dem_region, new_transform
def check_invalid_values(dem):
invalid_values = []
if np.any(np.isnan(dem)):
invalid_values.append('Le DEM contient des valeurs NaN.')
if np.any(np.isinf(dem)):
invalid_values.append('Le DEM contient des valeurs infinies.')
return invalid_values
def create_mesh(dem, transform, resolution):
"""
Crée un maillage 3D à partir des données du MNT.
"""
try:
height, width = dem.shape
if height < 3 or width < 3:
raise ValueError('Le MNT est trop petit pour créer un maillage.')
invalid_values = check_invalid_values(dem)
if invalid_values:
raise ValueError('\n'.join(invalid_values))
x, y = np.meshgrid(np.arange(width), np.arange(height))
lon, lat = rasterio.transform.xy(transform, y, x)
vertices = np.column_stack((lon.flatten(), lat.flatten(), dem.flatten()))
faces = []
for i in range(height - 1):
for j in range(width - 1):
idx = i * width + j
faces.append([idx, idx + 1, idx + width])
faces.append([idx + 1, idx + width + 1, idx + width])
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
target_faces = (height * width) // (resolution ** 2)
mesh = mesh.simplify_quadric_decimation(target_faces)
return mesh
except Exception as e:
logger.error(f"Erreur lors de la création du maillage: {str(e)}")
st.error(f"Erreur lors de la création du maillage: {str(e)}")
return None
def visualize_mesh(mesh):
"""
Crée une visualisation du maillage en 3D Trisurf.
"""
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(mesh.vertices[:, 0], mesh.vertices[:, 1], mesh.vertices[:, 2],
triangles=mesh.faces, cmap='viridis')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
return fig
def visualize_mesh_wireframe(mesh):
"""
Crée une visualisation du maillage en wireframe.
"""
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(mesh.vertices[:, 0], mesh.vertices[:, 1], mesh.vertices[:, 2],
triangles=mesh.faces, color='black')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
return fig
def export_for_blender(mesh):
"""
Exporte le maillage dans un format compatible avec Blender.
"""
try:
obj_file = BytesIO()
mesh.export(obj_file, file_type='obj')
return obj_file.getvalue()
except Exception as e:
logger.error(f"Erreur lors de l'export pour Blender: {str(e)}")
st.error(f"Erreur lors de l'export pour Blender: {str(e)}")
return None
def main():
st.title("Mesh Tiler CH - Streamlit Edition")
st.write("Sélectionnez une zone en Suisse pour créer un maillage 3D.")
input_method = st.radio("Choisissez la méthode de sélection de la zone :",
("Dessiner sur la carte", "Entrer les coordonnées", "Point aléatoire"))
bbox_coords = None
if input_method == "Point aléatoire":
minx, maxx = 5.9, 10.5
miny, maxy = 45.8, 47.8
rand_minx = random.uniform(minx, maxx)
rand_maxx = random.uniform(rand_minx, maxx)
rand_miny = random.uniform(miny, maxy)
rand_maxy = random.uniform(rand_miny, maxy)
bbox_coords = (rand_minx, rand_miny, rand_maxx, rand_maxy)
st.write(f"Zone aléatoire générée : {bbox_coords}")
elif input_method == "Dessiner sur la carte":
m = folium.Map(location=[46.8182, 8.2275], zoom_start=8)
draw = folium.plugins.Draw(
draw_options={
'rectangle': True,
'polyline': False,
'polygon': False,
'circle': False,
'marker': False,
'circlemarker': False},
edit_options={'edit': False}
)
draw.add_to(m)
output = st_folium(m, width=700, height=500)
if output['last_active_drawing']:
bbox = output['last_active_drawing']['geometry']['coordinates'][0]
bbox_coords = (
min(coord[0] for coord in bbox),
min(coord[1] for coord in bbox),
max(coord[0] for coord in bbox),
max(coord[1] for coord in bbox)
)
st.write(f"Zone sélectionnée : {bbox_coords}")
else:
col1, col2 = st.columns(2)
with col1:
minx = st.number_input("Min X", value=7.0)
miny = st.number_input("Min Y", value=46.0)
with col2:
maxx = st.number_input("Max X", value=8.0)
maxy = st.number_input("Max Y", value=47.0)
bbox_coords = (minx, miny, maxx, maxy)
if bbox_coords and st.button("Créer le maillage", key="create_mesh"):
dem_region, region_transform = load_swiss_dem(bbox_coords)
if dem_region is not None and region_transform is not None:
resolution = st.slider("RĂ©solution du maillage", min_value=1, max_value=100, value=10)
mesh = create_mesh(dem_region, region_transform, resolution)
if mesh is not None:
visual_type = st.radio("Choisissez le type de visualisation:", ("3D Trisurf", "Wireframe"))
if visual_type == "3D Trisurf":
fig = visualize_mesh(mesh)
else:
fig = visualize_mesh_wireframe(mesh)
st.pyplot(fig)
if st.button("Exporter pour Blender", key="export_blender"):
blender_file = export_for_blender(mesh)
if blender_file is not None:
st.download_button("Télécharger le fichier Blender (.obj)",
blender_file,
file_name="mesh_export.obj",
mime="application/octet-stream",
key="download_blender")
st.markdown("""
## Guide d'utilisation
1. Choisissez entre dessiner sur la carte, entrer les coordonnées manuellement ou générer un point aléatoire.
2. SĂ©lectionnez la zone d'intĂ©rĂȘt en Suisse.
3. Cliquez sur "Créer le maillage" pour générer le modÚle 3D.
4. Ajustez la résolution du maillage si nécessaire.
5. Choisissez le type de visualisation (3D Trisurf ou Wireframe).
6. Visualisez le maillage 3D généré.
7. Exportez le maillage au format OBJ pour Blender si souhaité.
Pour toute question ou problÚme, n'hésitez pas à contacter le support technique.
""")
if __name__ == "__main__":
main()