Vertdure commited on
Commit
96479f0
‱
1 Parent(s): 0e20c98

Update pages/5_📍_VertXtractor.py

Browse files
Files changed (1) hide show
  1. pages/5_📍_VertXtractor.py +181 -162
pages/5_📍_VertXtractor.py CHANGED
@@ -1,179 +1,198 @@
1
  import streamlit as st
 
 
 
 
 
 
 
2
  import folium
3
  from streamlit_folium import folium_static
4
- from folium.plugins import Draw
5
- import requests
6
- import json
7
- import os
8
- import urllib.request
9
- import csv
10
- from osgeo import gdal
11
- import numpy as np
12
- import math
13
- import shutil
14
- import sys
15
- import glob
16
 
17
- # Configuration de la page Streamlit
18
- st.set_page_config(page_title="SwissTopo Downloader", layout="wide")
 
19
 
20
- # Constantes
21
- PRODUCTS = {
22
- 'Luftbild 10cm': 'ch.swisstopo.swissimage-dop10',
23
- 'Landeskarte 1:10': 'ch.swisstopo.landeskarte-farbe-10',
24
- 'Landeskarte 1:25': 'ch.swisstopo.pixelkarte-farbe-pk25.noscale',
25
- 'Landeskarte 1:50': 'ch.swisstopo.pixelkarte-farbe-pk50.noscale',
26
- 'Landeskarte 1:100': 'ch.swisstopo.pixelkarte-farbe-pk100.noscale',
27
- 'Landeskarte 1:200': 'ch.swisstopo.pixelkarte-farbe-pk200.noscale',
28
- 'Höhenmodell': 'ch.swisstopo.swissalti3d',
29
- }
30
 
31
- # Fonctions utilitaires
32
- def osm_to_decimal(tile_x, tile_y, zoom):
33
- """Convertit les coordonnées OSM en coordonnées décimales."""
34
- n = 2.0 ** zoom
35
- lon_deg = tile_x / n * 360.0 - 180.0
36
- lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * tile_y / n)))
37
- lat_deg = math.degrees(lat_rad)
38
- return lat_deg, lon_deg
39
 
40
- def get_items(productname, LLlon, LLlat, URlon, URlat, first100=False):
41
- """RĂ©cupĂšre les Ă©lĂ©ments pour une zone d'intĂ©rĂȘt donnĂ©e."""
42
- url = f"https://data.geo.admin.ch/api/stac/v0.9/collections/{productname}/items?bbox={LLlon},{LLlat},{URlon},{URlat}"
43
- response = requests.get(url)
44
- items_result = json.loads(response.content)
45
- assets = items_result.get('features', [])
46
-
47
- items_files = []
48
- for asset in assets:
49
- asset_url = asset.get('assets', {}).get('data', {}).get('href')
50
- if asset_url:
51
- items_files.append(asset_url)
52
-
53
- # Filtrage des fichiers selon les cas spécifiques
54
- if "_krel_" in productname:
55
- items_files = [i for i in items_files if "_krel_" in i]
56
-
57
- if "0.1" in productname:
58
- items_files = [i for i in items_files if "_0.1_" in i]
59
-
60
- if productname == 'ch.swisstopo.swissalti3d':
61
- items_files = [i for i in items_files if i.endswith(".tif")]
62
- items_files = [i for i in items_files if "_0.5_" in i]
63
-
64
- return items_files
65
 
66
- def create_csv(productname, LLlon, LLlat, URlon, URlat):
67
- """Crée un fichier CSV contenant les URLs des données à télécharger."""
68
- coords = f"{LLlon}_{LLlat}_{URlon}_{URlat}"
69
- csv_filepath = os.path.join(os.getcwd(), f"{productname}{coords}.csv")
70
-
71
- items_files = get_items(productname, LLlon, LLlat, URlon, URlat)
72
-
73
- with open(csv_filepath, 'w', newline='') as f:
74
- writer = csv.writer(f)
75
- for item in items_files:
76
- writer.writerow([item])
77
-
78
- return csv_filepath
 
 
 
79
 
80
- def merge_rasters(input_files, output_file, compress_method='JPEG'):
81
- """Fusionne plusieurs rasters en un seul."""
82
- gdal.UseExceptions()
83
-
84
- # Ouvrir le premier fichier pour obtenir les métadonnées
85
- src_ds = gdal.Open(input_files[0])
86
- driver = gdal.GetDriverByName("GTiff")
87
-
88
- # Créer le fichier de sortie
89
- dst_ds = driver.CreateCopy(output_file, src_ds, 0,
90
- options=[f"COMPRESS={compress_method}",
91
- "TILED=YES",
92
- "BIGTIFF=YES"])
93
-
94
- # Fermer le premier fichier
95
- src_ds = None
96
-
97
- # Fusionner les autres fichiers
98
- for file in input_files[1:]:
99
- gdal.Warp(output_file, file, format="GTiff", options=[f"COMPRESS={compress_method}"])
100
-
101
- # Fermer le fichier de sortie
102
- dst_ds = None
103
-
104
- def process_csv(csv_filepath, no_merge=False):
105
- """Traite le fichier CSV pour télécharger et fusionner les données."""
106
- download_dir = os.path.dirname(csv_filepath)
107
- order_name = os.path.basename(csv_filepath).split('.')[0]
108
-
109
- with open(csv_filepath, 'r') as file:
110
- urls = file.readlines()
111
-
112
- downloaded_files = []
113
- for i, url in enumerate(urls):
114
- url = url.strip()
115
- filename = os.path.join(download_dir, url.split('/')[-1])
116
 
117
- if not os.path.isfile(filename):
118
- st.text(f"Téléchargement du fichier : {i+1} sur {len(urls)}")
119
- urllib.request.urlretrieve(url, filename)
 
 
 
 
120
 
121
- downloaded_files.append(filename)
122
-
123
- if not no_merge:
124
- output_file = os.path.join(download_dir, f"{order_name}_merged.tif")
125
- st.text("Fusion des fichiers téléchargés...")
126
- merge_rasters(downloaded_files, output_file)
127
- st.text(f"Résultat fusionné dans : {output_file}")
128
- else:
129
- st.text(f"Fichiers téléchargés dans : {download_dir}")
130
-
131
- return output_file if not no_merge else download_dir
132
 
133
- # Interface utilisateur Streamlit
134
- def main():
135
- st.title("SwissTopo Downloader")
136
-
137
- # SĂ©lection du produit
138
- selected_product = st.selectbox("SĂ©lectionnez un produit", list(PRODUCTS.keys()))
139
-
140
- # Carte interactive pour dessiner la bbox
141
- st.subheader("Dessinez une boĂźte englobante sur la carte")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  m = folium.Map(location=[46.8182, 8.2275], zoom_start=8)
143
- draw = Draw(
144
- draw_options={
145
- 'rectangle': True,
146
- 'polygon': False,
147
- 'polyline': False,
148
- 'circle': False,
149
- 'marker': False,
150
- 'circlemarker': False,
151
- },
152
- edit_options={'edit': False}
153
- )
154
- draw.add_to(m)
155
-
156
- output = folium_static(m)
157
 
158
- # Récupération des coordonnées de la bbox
159
- bbox_coords = None
160
- if 'last_active_drawing' in st.session_state:
161
- bbox = st.session_state.last_active_drawing['geometry']['coordinates'][0]
162
- lons = [coord[0] for coord in bbox]
163
- lats = [coord[1] for coord in bbox]
164
- bbox_coords = [min(lons), min(lats), max(lons), max(lats)]
165
- st.write(f"Coordonnées de la bbox : {bbox_coords}")
166
 
167
- # Options
168
- no_merge = st.checkbox("Ne pas fusionner les fichiers téléchargés")
169
 
170
- if st.button("Télécharger") and bbox_coords:
171
- product = PRODUCTS[selected_product]
172
- csv_filepath = create_csv(product, *bbox_coords)
173
- result = process_csv(csv_filepath, no_merge)
174
- st.success(f"Téléchargement terminé ! Résultat : {result}")
175
- elif st.button("Télécharger") and not bbox_coords:
176
- st.error("Veuillez dessiner une boßte englobante sur la carte avant de télécharger.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
- if __name__ == "__main__":
179
- main()
 
1
  import streamlit as st
2
+ import geopandas as gpd
3
+ import rasterio
4
+ import numpy as np
5
+ from pyproj import Transformer
6
+ import trimesh
7
+ import logging
8
+ from io import BytesIO
9
  import folium
10
  from streamlit_folium import folium_static
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ # Configuration du logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
 
16
+ # Fonction pour charger le MNT de la Suisse (à implémenter)
17
+ def load_swiss_dem():
18
+ """
19
+ Charge le MNT de la Suisse. Cette fonction devrait ĂȘtre implĂ©mentĂ©e pour charger
20
+ le MNT complet de la Suisse à partir d'une source de données appropriée.
21
+ """
22
+ # Placeholder - à remplacer par le chargement réel du MNT suisse
23
+ return None, None
 
 
24
 
25
+ # Fonction pour extraire une partie du MNT
26
+ def extract_dem_region(dem, transform, bbox):
27
+ """
28
+ Extrait une région spécifique du MNT basée sur une boßte englobante.
 
 
 
 
29
 
30
+ Args:
31
+ dem (numpy.array): Données du MNT complet
32
+ transform (affine.Affine): Transformation géospatiale
33
+ bbox (tuple): BoĂźte englobante (minx, miny, maxx, maxy)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ Returns:
36
+ tuple: (données du MNT extraites, nouvelle transformation)
37
+ """
38
+ # Conversion des coordonnées de la bbox en indices de pixels
39
+ minx, miny, maxx, maxy = bbox
40
+ rows, cols = rasterio.transform.rowcol(transform, [minx, maxx], [miny, maxy])
41
+
42
+ # Extraction de la région
43
+ window = ((rows[0], rows[1]), (cols[0], cols[1]))
44
+ dem_region = dem[window[0][0]:window[0][1], window[1][0]:window[1][1]]
45
+
46
+ # Calcul de la nouvelle transformation
47
+ new_transform = rasterio.transform.from_bounds(minx, miny, maxx, maxy,
48
+ dem_region.shape[1], dem_region.shape[0])
49
+
50
+ return dem_region, new_transform
51
 
52
+ def create_mesh(dem, transform, resolution):
53
+ """
54
+ Crée un maillage 3D à partir des données du MNT.
55
+
56
+ Args:
57
+ dem (numpy.array): Données du MNT
58
+ transform (affine.Affine): Transformation géospatiale
59
+ resolution (int): RĂ©solution du maillage
60
+
61
+ Returns:
62
+ trimesh.Trimesh: Maillage 3D
63
+ """
64
+ try:
65
+ height, width = dem.shape
66
+ x, y = np.meshgrid(np.arange(width), np.arange(height))
67
+ lon, lat = rasterio.transform.xy(transform, y, x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ vertices = np.column_stack((lon.flatten(), lat.flatten(), dem.flatten()))
70
+ faces = []
71
+ for i in range(height - 1):
72
+ for j in range(width - 1):
73
+ idx = i * width + j
74
+ faces.append([idx, idx + 1, idx + width])
75
+ faces.append([idx + 1, idx + width + 1, idx + width])
76
 
77
+ mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
78
+
79
+ target_faces = (height * width) // (resolution ** 2)
80
+ mesh = mesh.simplify_quadric_decimation(target_faces)
81
+
82
+ return mesh
83
+ except Exception as e:
84
+ logger.error(f"Erreur lors de la création du maillage: {str(e)}")
85
+ st.error("Erreur lors de la création du maillage. Veuillez vérifier les paramÚtres.")
86
+ return None
 
87
 
88
+ def visualize_mesh(mesh):
89
+ """
90
+ Crée une visualisation du maillage.
91
+
92
+ Args:
93
+ mesh (trimesh.Trimesh): Maillage 3D Ă  visualiser
94
+
95
+ Returns:
96
+ matplotlib.figure.Figure: Figure contenant la visualisation du maillage
97
+ """
98
+ import matplotlib.pyplot as plt
99
+ from mpl_toolkits.mplot3d import Axes3D
100
+
101
+ fig = plt.figure()
102
+ ax = fig.add_subplot(111, projection='3d')
103
+ ax.plot_trisurf(mesh.vertices[:, 0], mesh.vertices[:, 1], mesh.vertices[:, 2],
104
+ triangles=mesh.faces, cmap='viridis')
105
+ ax.set_xlabel('X')
106
+ ax.set_ylabel('Y')
107
+ ax.set_zlabel('Z')
108
+ return fig
109
+
110
+ def export_for_blender(mesh):
111
+ """
112
+ Exporte le maillage dans un format compatible avec Blender.
113
+
114
+ Args:
115
+ mesh (trimesh.Trimesh): Maillage 3D Ă  exporter
116
+
117
+ Returns:
118
+ bytes: Contenu du fichier d'export
119
+ """
120
+ try:
121
+ obj_file = BytesIO()
122
+ mesh.export(obj_file, file_type='obj')
123
+ return obj_file.getvalue()
124
+ except Exception as e:
125
+ logger.error(f"Erreur lors de l'export pour Blender: {str(e)}")
126
+ st.error("Erreur lors de l'export pour Blender. Veuillez réessayer.")
127
+ return None
128
+
129
+ # Interface Streamlit
130
+ st.title("Mesh Tiler CH - Streamlit Edition")
131
+ st.write("Sélectionnez une zone en Suisse pour créer un maillage 3D.")
132
+
133
+ # Chargement du MNT complet de la Suisse
134
+ swiss_dem, swiss_transform = load_swiss_dem()
135
+
136
+ # Sélection de la méthode d'entrée
137
+ input_method = st.radio("Choisissez la méthode de sélection de la zone :",
138
+ ("Dessiner sur la carte", "Entrer les coordonnées"))
139
+
140
+ if input_method == "Dessiner sur la carte":
141
+ # Créer une carte centrée sur la Suisse
142
  m = folium.Map(location=[46.8182, 8.2275], zoom_start=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ # Ajouter un outil de dessin de rectangle
145
+ draw = folium.plugins.Draw(export=True)
146
+ draw.add_to(m)
 
 
 
 
 
147
 
148
+ # Afficher la carte
149
+ folium_static(m)
150
 
151
+ # Récupérer les coordonnées du rectangle dessiné
152
+ if st.button("Créer le maillage à partir de la sélection"):
153
+ # Ici, vous devriez récupérer les coordonnées du rectangle dessiné
154
+ # Cette partie nécessite une implémentation plus avancée avec JavaScript
155
+ st.write("Fonctionnalité en cours de développement.")
156
+
157
+ else:
158
+ # Entrer les coordonnées manuellement
159
+ col1, col2 = st.columns(2)
160
+ with col1:
161
+ minx = st.number_input("Min X", value=7.0)
162
+ miny = st.number_input("Min Y", value=46.0)
163
+ with col2:
164
+ maxx = st.number_input("Max X", value=8.0)
165
+ maxy = st.number_input("Max Y", value=47.0)
166
+
167
+ if st.button("Créer le maillage"):
168
+ bbox = (minx, miny, maxx, maxy)
169
+ dem_region, region_transform = extract_dem_region(swiss_dem, swiss_transform, bbox)
170
+
171
+ resolution = st.slider("RĂ©solution du maillage", min_value=1, max_value=100, value=10)
172
+ mesh = create_mesh(dem_region, region_transform, resolution)
173
+
174
+ if mesh is not None:
175
+ fig = visualize_mesh(mesh)
176
+ st.pyplot(fig)
177
+
178
+ if st.button("Exporter pour Blender"):
179
+ blender_file = export_for_blender(mesh)
180
+ if blender_file is not None:
181
+ st.download_button("Télécharger le fichier Blender (.obj)",
182
+ blender_file,
183
+ file_name="mesh_export.obj",
184
+ mime="application/octet-stream")
185
+
186
+ # Guide d'utilisation simplifié
187
+ st.markdown("""
188
+ ## Guide d'utilisation
189
+
190
+ 1. Choisissez entre dessiner sur la carte ou entrer les coordonnées manuellement.
191
+ 2. SĂ©lectionnez la zone d'intĂ©rĂȘt en Suisse.
192
+ 3. Cliquez sur "Créer le maillage" pour générer le modÚle 3D.
193
+ 4. Ajustez la résolution du maillage si nécessaire.
194
+ 5. Visualisez le maillage 3D généré.
195
+ 6. Exportez le maillage au format OBJ pour Blender si souhaité.
196
 
197
+ Pour toute question ou problÚme, n'hésitez pas à contacter le support technique.
198
+ """)