- Introduction
- Prérequis
- Installation
- Configuration
- Architecture du Script
- Multiprocessing
- Fonctions Détaillées
- Flux de Données
- Formats de Données
- Exemples d'Utilisation
- Dépannage
Ce script Python génère des visualisations 3D interactives de villes à partir des données OpenStreetMap (OSM). Il combine :
- Bâtiments : Extraits d'OSM avec leurs hauteurs réelles
- Rues : Réseau routier complet (voitures, piétons, vélos)
- Terrain : Relief topographique basé sur les données d'élévation SRTM
Le résultat est une vue 3D interactive que vous pouvez explorer avec la souris.
Performance : Le script utilise le multiprocessing (ProcessPoolExecutor) pour paralléliser tous les calculs intensifs sur tous les cœurs CPU disponibles.
- Python 3.8+
- Conda (recommandé pour la gestion des dépendances)
| Bibliothèque | Version | Description |
|---|---|---|
osmnx |
≥1.0 | Extraction de données OpenStreetMap |
pyvista |
≥0.35 | Visualisation 3D et manipulation de maillages |
pandas |
≥1.3 | Manipulation de données tabulaires |
numpy |
≥1.20 | Calculs numériques |
shapely |
≥1.8 | Manipulation de géométries |
srtm.py |
≥0.3 | Téléchargement des données d'élévation SRTM |
pyproj |
≥3.0 | Transformation de coordonnées |
multiprocessing |
(stdlib) | Parallélisation sur tous les cœurs CPU |
concurrent.futures |
(stdlib) | ProcessPoolExecutor pour le traitement par lots |
conda create -n carte3d python=3.10
conda activate carte3dconda install -c conda-forge osmnx pyvista pandas numpy shapely pyprojpip install srtm.pypython Main.pyLa configuration se trouve à la fin du fichier Main.py :
# === CONFIGURATION ===
location = "Montpellier, France" # Lieu à visualiser
radius = 500 # Rayon en mètres
# Options d'affichage
SHOW_TERRAIN = True # Afficher le relief/topographie
SHOW_BUILDINGS = True # Afficher les bâtiments
SHOW_STREETS = True # Afficher les rues
COLOR_BY_HEIGHT = True # Colorer les bâtiments selon leur hauteur
TERRAIN_RESOLUTION = 100 # Résolution du terrain (50-500 recommandé)
TERRAIN_EXAGGERATION = 3.0 # Exagération verticale du relief
BUILDING_EXAGGERATION = 1.0 # Exagération de la hauteur des bâtiments
STREET_COLOR = 'white' # Couleur des rues
# =====================| Paramètre | Type | Description |
|---|---|---|
location |
str | Nom du lieu (géocodé par Nominatim) |
radius |
int | Rayon de la zone en mètres |
SHOW_TERRAIN |
bool | Active/désactive le terrain 3D |
SHOW_BUILDINGS |
bool | Active/désactive les bâtiments |
SHOW_STREETS |
bool | Active/désactive les rues |
COLOR_BY_HEIGHT |
bool | Coloration par hauteur (sinon gris) |
TERRAIN_RESOLUTION |
int | Points par axe pour le terrain |
TERRAIN_EXAGGERATION |
float | Facteur d'exagération verticale du terrain |
BUILDING_EXAGGERATION |
float | Facteur d'exagération des bâtiments |
STREET_COLOR |
str | Couleur des rues (nom ou hex) |
- Petite zone (< 500m) :
TERRAIN_RESOLUTION = 50-100 - Zone moyenne (500m-2km) :
TERRAIN_RESOLUTION = 100-200 - Grande zone (> 2km) :
TERRAIN_RESOLUTION = 200-500
Le multiprocessing accélère considérablement le traitement, mais une résolution très élevée reste coûteuse.
Le script est organisé en 7 sections principales :
┌─────────────────────────────────────────────────────────────┐
│ 1. IMPORTS & MULTIPROCESSING SETUP │
│ Configuration de l'environnement Python │
│ Détection du nombre de cœurs CPU (NUM_CORES) │
├─────────────────────────────────────────────────────────────┤
│ 2. EXTRACTION OSM │
│ extract_osm_data() - Télécharge bâtiments et rues │
├─────────────────────────────────────────────────────────────┤
│ 2b. DONNÉES TERRAIN (PARALLÉLISÉ) │
│ create_terrain_mesh() - Crée le relief 3D │
│ _fetch_elevation_row() - Worker parallèle │
├─────────────────────────────────────────────────────────────┤
│ 3. GÉNÉRATION EMPREINTES │
│ generate_footprints_with_heights() - Extrait polygones │
├─────────────────────────────────────────────────────────────┤
│ 4. CRÉATION BÂTIMENTS 3D (ENTIÈREMENT PARALLÉLISÉ) │
│ _create_building_data() - Données d'un bâtiment │
│ _process_building_batch() - Worker par lots │
│ extrude_buildings() - Extrusion + fusion rapide │
├─────────────────────────────────────────────────────────────┤
│ 5. EXPORT │
│ save_to_obj() - Sauvegarde en format OBJ │
├─────────────────────────────────────────────────────────────┤
│ 6. CONVERSION RUES (ENTIÈREMENT PARALLÉLISÉ) │
│ _process_street_batch() - Worker par lots │
│ streetGraph_to_pyvista() - Convertit le graphe en 3D │
├─────────────────────────────────────────────────────────────┤
│ 7. CONFIGURATION & VISUALISATION │
│ Code principal d'exécution │
└─────────────────────────────────────────────────────────────┘
Le script détecte automatiquement le nombre de cœurs CPU disponibles et les utilise tous pour accélérer les calculs :
NUM_CORES = mp.cpu_count()
print(f"⚡ Multiprocessing activé: {NUM_CORES} cœurs CPU disponibles")| Étape | Fonction Worker | Stratégie |
|---|---|---|
| Terrain | _fetch_elevation_row() |
Une tâche par ligne de la grille |
| Bâtiments | _process_building_batch() |
Traitement par lots (batch_size = total / (NUM_CORES × 4)) |
| Rues | _process_street_batch() |
Traitement par lots |
Pour les bâtiments et les rues, les données sont divisées en lots :
batch_size = max(1, total // (NUM_CORES * 4))Chaque worker traite un lot complet, ce qui réduit l'overhead de communication entre processus.
Au lieu d'utiliser la fusion séquentielle de PyVista (complexité O(n²)), le script utilise une concaténation directe des tableaux numpy (complexité O(n)) :
# Concaténation directe des vertices
combined_vertices = np.vstack(all_vertices)
# Ajustement des indices de faces
for idx, vertices, faces, height in all_results:
# Décalage des indices pour chaque mesh
adjusted_faces = adjust_face_indices(faces, vertex_offset)
all_faces.extend(adjusted_faces)
vertex_offset += len(vertices)
# Création du mesh unique
city_mesh = pv.PolyData(combined_vertices, np.array(all_faces))Cette optimisation est critique pour les grandes villes avec des milliers de bâtiments.
Chaque étape parallélisée affiche un pourcentage de progression :
→ Élévation: 75%
→ Extrusion bâtiments: 50%
→ Conversion rues: 100% ✓
Afin de respecter au mieux l'affichage et le traitements des données géospatial venant d'OSM (Open Street Map), il nous faut utiliser le format EPSG:2154 plus connut sous le nom de projection de lambert.
But : Télécharge les données géographiques depuis OpenStreetMap.
Processus :
- Géocode le lieu pour obtenir les coordonnées (lat, lon)
- Télécharge les bâtiments avec le tag
building=* - Télécharge le réseau routier (tous types)
- Projette les données en Lambert-93 (EPSG:2154)
Retourne :
buildings: GeoDataFrame des bâtimentsstreets: Graphe NetworkX du réseau routiercenter_point: Tuple (latitude, longitude)
But : Récupère les élévations pour une ligne de la grille terrain.
Arguments :
row_idx: Index de la lignelat_row: Tableau des latitudeslon_row: Tableau des longitudes
Retourne :
(row_idx, row_elevations): Index et élévations de la ligne
But : Crée un maillage 3D du terrain basé sur les données d'élévation SRTM.
Processus :
- Calcule la boîte englobante en coordonnées géographiques
- Crée une grille de points (resolution × resolution)
- Parallélise : Récupère l'élévation pour chaque ligne via
ProcessPoolExecutor - Transforme les coordonnées en Lambert-93
- Applique l'exagération verticale
- Crée un maillage PyVista StructuredGrid
Retourne :
terrain: Maillage PyVista du terrainelevation_data: Objet SRTM pour requêtes ultérieurestransformer: Objet pyproj pour transformationsz_exaggeration: Facteur d'exagération utilisé
But : Extrait les empreintes des bâtiments avec leurs hauteurs réelles.
Sources de hauteur (par ordre de priorité) :
- Attribut OSM
height(hauteur en mètres) - Attribut OSM
building:levels× 3m par étage - Hauteur par défaut : 10m
Gestion des géométries :
Polygon: Ajouté directementMultiPolygon: Décomposé en polygones individuels- Autres types : Ignorés
Retourne :
footprints: Liste de polygones Shapelyheights: Liste des hauteurs correspondantes
But : Crée les données brutes (vertices et faces) pour un bâtiment.
Structure du bâtiment :
- Base : n vertices au niveau du sol
- Toit : n vertices au niveau sol + hauteur
- Murs : n faces quadrilatères
Retourne :
vertices: Array numpy des vertices (2n × 3)faces: Array numpy des indices de faces (format PyVista)
But : Traite un lot de bâtiments en parallèle.
Pour chaque bâtiment du lot :
- Transforme les coordonnées pour obtenir lat/lon du centroïde
- Récupère l'élévation du terrain
- Applique l'exagération du terrain
- Crée les données du bâtiment via
_create_building_data()
Retourne :
- Liste de tuples
(idx, vertices, faces, height)
But : Extrude toutes les empreintes en bâtiments 3D (entièrement parallélisé).
Processus :
- Prépare les données de tous les bâtiments
- Divise en lots pour le multiprocessing
- Parallélise : Traite les lots via
ProcessPoolExecutor - Trie les résultats par index
- Fusion rapide : Concatène les vertices et ajuste les indices de faces
- Crée le mesh unique avec attribut
heightpour la colormap
Retourne :
city_mesh: Maillage combiné de tous les bâtiments[]: Liste vide (pour compatibilité)
But : Traite un lot de segments de rue en parallèle.
Pour chaque segment :
- Récupère les coordonnées X, Y
- Transforme pour obtenir lat/lon
- Récupère l'élévation pour chaque point
- Ajoute un décalage de 0.5m au-dessus du sol
Retourne :
- Liste de tuples
(idx, points_3d)
But : Convertit le graphe des rues en lignes 3D PyVista (entièrement parallélisé).
Processus :
- Extrait les arêtes du graphe OSMnx
- Prépare les données de toutes les rues
- Divise en lots pour le multiprocessing
- Parallélise : Traite les lots via
ProcessPoolExecutor - Trie les résultats par index
- Fusion rapide : Construit les indices de lignes pour PyVista
Retourne :
PolyDataPyVista avec lignes
| Étape | Système | Code EPSG | Description |
|---|---|---|---|
| Entrée OSM | WGS84 | 4326 | Coordonnées GPS (lat, lon) |
| Traitement | Lambert-93 | 2154 | Projection française (mètres) |
| Visualisation | Lambert-93 | 2154 | Coordonnées en mètres |
Points : Array numpy de shape (n_points, 3)
[[x1, y1, z1],
[x2, y2, z2],
...]Faces : Array plat avec format [n_vertices, v1, v2, ..., vn]
[4, 0, 1, 2, 3, # Quad (4 vertices)
3, 4, 5, 6, # Triangle (3 vertices)
...]Rendu (oui, il faut un pc puissant ^^)
location = "Millau, France"
radius = 30000
SHOW_TERRAIN = True
SHOW_BUILDINGS = True
SHOW_STREETS = False
TERRAIN_EXAGGERATION = 2.0
TERRAIN_RESOLUTION = 10000Rendu en entier :
Vue sur la ville de Millau et son environnement :
Zoom sur la ville
Voici la page de chargement avec un total de 123 937 bâtiments
location = "Paris, France"
radius = 5000
SHOW_TERRAIN = True
SHOW_BUILDINGS = True
SHOW_STREETS = False
TERRAIN_RESOLUTION = 1000
TERRAIN_EXAGGERATION = 3.0 Voici une vue d'ensemble de Paris :
De plus bas :
Champs Élysées :
butte Montmartre :
location = "Montpellier, France"
radius = 5000
SHOW_TERRAIN = True
SHOW_BUILDINGS = True
SHOW_STREETS = False
TERRAIN_RESOLUTION = 1000
TERRAIN_EXAGGERATION = 3.0 # Après la génération du maillage
save_to_obj(mesh, "output/ma_ville.obj")Cause : Serveur Overpass surchargé ou connexion internet instable.
Solution : Attendre quelques minutes et réessayer.
Causes possibles :
- Zone sans données SRTM (océan, certaines régions)
TERRAIN_EXAGGERATIONtrop faible
Solution : Augmenter TERRAIN_EXAGGERATION à 5-10.
Cause : Trop de données (grand rayon + haute résolution).
Solutions :
- Réduire
TERRAIN_RESOLUTION - Réduire
radius - Désactiver certaines couches (
SHOW_STREETS = False)
Cause : Incohérence entre les données d'élévation du terrain et des bâtiments.
Solution : Vérifier que TERRAIN_EXAGGERATION est bien transmis aux bâtiments.
Cause : Sur Windows, le multiprocessing nécessite que le code soit dans un bloc if __name__ == "__main__":.
Solution : C'est déjà le cas dans ce script. Si le problème persiste, essayez de réduire NUM_CORES manuellement.
Florent Labrousse-Lhuissier
MIT












