|
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 |
|
|
|
|
|
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', {}) |
|
|
|
|
|
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() |