2025-03-16 12:54:21 +01:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Version simplifiée du script pour générer une carte visuelle d ' une ville à partir de son ID OpenStreetMap.
Utilise l ' API Overpass et Folium pour créer une carte interactive.
"""
import os
import sys
import argparse
import json
import requests
import folium
from folium . plugins import MarkerCluster
import logging
from datetime import datetime
# Configuration de la journalisation
logging . basicConfig ( level = logging . INFO , format = ' %(asctime)s - %(levelname)s - %(message)s ' )
logger = logging . getLogger ( __name__ )
def get_city_data_from_osm ( osm_id ) :
"""
Récupère les données d ' une ville à partir de son ID OpenStreetMap via l ' API Overpass .
Args :
osm_id ( int ) : L ' identifiant OpenStreetMap de la ville
Returns :
dict : Un dictionnaire contenant les données de la ville
"""
try :
logger . info ( f " Récupération des données pour l ' ID OSM: { osm_id } " )
# Essayer d'abord en tant que relation
city_data = _try_get_osm_data ( osm_id , " relation " )
# Si ça ne fonctionne pas, essayer en tant que way (chemin)
if not city_data :
logger . info ( f " Essai en tant que chemin (way) pour l ' ID OSM: { osm_id } " )
city_data = _try_get_osm_data ( osm_id , " way " )
# Si ça ne fonctionne toujours pas, essayer en tant que node (nœud)
if not city_data :
logger . info ( f " Essai en tant que nœud (node) pour l ' ID OSM: { osm_id } " )
city_data = _try_get_osm_data ( osm_id , " node " )
# Si aucune méthode ne fonctionne
if not city_data :
logger . error ( f " Aucune donnée trouvée pour l ' ID OSM: { osm_id } " )
return None
return city_data
except Exception as e :
logger . error ( f " Erreur lors de la récupération des données: { str ( e ) } " )
return None
def _try_get_osm_data ( osm_id , element_type ) :
"""
Essaie de récupérer les données OSM pour un type d ' élément spécifique.
Args :
osm_id ( int ) : L ' identifiant OpenStreetMap
element_type ( str ) : Le type d ' élément ( ' relation ' , ' way ' , ' node ' )
Returns :
dict : Un dictionnaire contenant les données ou None en cas d ' échec
"""
try :
# Requête pour obtenir les informations de base
if element_type == " relation " :
# Pour les relations (villes, quartiers, etc.)
query = f """
[ out : json ] ;
relation ( { osm_id } ) ;
out body ;
> ;
out skel qt ;
"""
elif element_type == " way " :
# Pour les chemins (routes, contours, etc.)
query = f """
[ out : json ] ;
way ( { osm_id } ) ;
out body ;
> ;
out skel qt ;
"""
elif element_type == " node " :
# Pour les nœuds (points d'intérêt, etc.)
query = f """
[ out : json ] ;
node ( { osm_id } ) ;
out body ;
"""
else :
logger . error ( f " Type d ' élément non reconnu: { element_type } " )
return None
# Envoyer la requête à l'API Overpass
overpass_url = " https://overpass-api.de/api/interpreter "
response = requests . post ( overpass_url , data = { " data " : query } )
if response . status_code != 200 :
logger . error ( f " Erreur lors de la requête Overpass: { response . status_code } " )
return None
data = response . json ( )
if not data . get ( ' elements ' ) :
logger . warning ( f " Aucune donnée trouvée pour l ' ID OSM { osm_id } en tant que { element_type } " )
return None
# Extraire les informations de base
element_info = next ( ( e for e in data [ ' elements ' ] if e . get ( ' id ' ) == osm_id ) , None )
if not element_info :
logger . warning ( f " Élément { element_type } non trouvé pour l ' ID OSM: { osm_id } " )
return None
# Récupérer le nom et les coordonnées
name = element_info . get ( ' tags ' , { } ) . get ( ' name ' , f " Lieu_ { osm_id } " )
logger . info ( f " Lieu identifié: { name } " )
# Déterminer les coordonnées centrales
if element_type == " node " :
# Pour un nœud, utiliser directement ses coordonnées
center_lat = element_info . get ( ' lat ' )
center_lon = element_info . get ( ' lon ' )
center = ( center_lat , center_lon )
# Pour un nœud, définir une zone autour
bbox = ( center_lat - 0.01 , center_lon - 0.01 , center_lat + 0.01 , center_lon + 0.01 )
else :
# Pour les relations et chemins, calculer à partir des nœuds
nodes = [ e for e in data [ ' elements ' ] if e . get ( ' type ' ) == ' node ' ]
if not nodes :
logger . error ( f " Aucun nœud trouvé pour { element_type } { osm_id } " )
return None
lats = [ n . get ( ' lat ' , 0 ) for n in nodes if ' lat ' in n ]
lons = [ n . get ( ' lon ' , 0 ) for n in nodes if ' lon ' in n ]
if not lats or not lons :
logger . error ( f " Coordonnées manquantes pour { element_type } { osm_id } " )
return None
center_lat = sum ( lats ) / len ( lats )
center_lon = sum ( lons ) / len ( lons )
center = ( center_lat , center_lon )
# Calculer la boîte englobante
min_lat , max_lat = min ( lats ) , max ( lats )
min_lon , max_lon = min ( lons ) , max ( lons )
bbox = ( min_lat , min_lon , max_lat , max_lon )
# Récupérer les éléments dans la zone (routes, bâtiments, parkings)
2025-03-16 16:13:45 +01:00
# Utiliser l'ID de la relation/way pour limiter aux éléments à l'intérieur de la ville
if element_type == " relation " :
# Pour les relations (villes), il faut d'abord obtenir l'ID d'area correspondant
# L'ID d'area est calculé comme 3600000000 + ID de la relation
area_id = 3600000000 + osm_id
area_query = f """
[ out : json ] ;
area ( { area_id } ) - > . searchArea ;
(
way [ highway ] ( area . searchArea ) ;
way [ building ] ( area . searchArea ) ;
way [ amenity = parking ] ( area . searchArea ) ;
) ;
out body ;
> ;
out skel qt ;
"""
elif element_type == " way " :
# Pour les ways (contours), il faut d'abord obtenir l'ID d'area correspondant
# L'ID d'area est calculé comme 2400000000 + ID du way
area_id = 2400000000 + osm_id
area_query = f """
[ out : json ] ;
area ( { area_id } ) - > . searchArea ;
(
way [ highway ] ( area . searchArea ) ;
way [ building ] ( area . searchArea ) ;
way [ amenity = parking ] ( area . searchArea ) ;
) ;
out body ;
> ;
out skel qt ;
"""
else :
# Pour les nodes ou si les méthodes précédentes échouent, utiliser la boîte englobante
# mais avec un rayon plus petit autour du point
area_query = f """
[ out : json ] ;
(
way [ highway ] ( { bbox [ 0 ] } , { bbox [ 1 ] } , { bbox [ 2 ] } , { bbox [ 3 ] } ) ;
way [ building ] ( { bbox [ 0 ] } , { bbox [ 1 ] } , { bbox [ 2 ] } , { bbox [ 3 ] } ) ;
way [ amenity = parking ] ( { bbox [ 0 ] } , { bbox [ 1 ] } , { bbox [ 2 ] } , { bbox [ 3 ] } ) ;
) ;
out body ;
> ;
out skel qt ;
"""
logger . warning ( f " Utilisation d ' une boîte englobante pour { element_type } { osm_id } (moins précis) " )
2025-03-16 12:54:21 +01:00
logger . info ( f " Récupération des routes, bâtiments et parkings pour { name } ... " )
response = requests . post ( overpass_url , data = { " data " : area_query } )
if response . status_code != 200 :
logger . error ( f " Erreur lors de la requête Overpass pour les éléments: { response . status_code } " )
return None
elements_data = response . json ( )
return {
' city_name ' : name ,
' center ' : center ,
' bbox ' : bbox ,
' city_data ' : data ,
' elements_data ' : elements_data ,
' element_type ' : element_type
}
except Exception as e :
logger . error ( f " Erreur lors de la récupération des données pour { element_type } { osm_id } : { str ( e ) } " )
return None
def create_folium_map ( city_data , output_path = None ) :
"""
Crée une carte interactive avec Folium .
Args :
city_data ( dict ) : Dictionnaire contenant les données de la ville
output_path ( str , optional ) : Chemin où sauvegarder l ' image HTML. Si None, utilise le nom de la ville.
Returns :
str : Chemin vers le fichier HTML généré
"""
if not city_data :
logger . error ( " Aucune donnée de ville fournie pour générer la carte " )
return None
try :
city_name = city_data [ ' city_name ' ]
center = city_data [ ' center ' ]
elements_data = city_data [ ' elements_data ' ]
element_type = city_data . get ( ' element_type ' , ' unknown ' )
2025-03-16 16:13:45 +01:00
# Créer une carte centrée sur la ville avec fond Stamen Terrain
m = folium . Map (
location = center ,
zoom_start = 14 ,
tiles = ' Stamen Terrain ' ,
attr = ' Map tiles by <a href= " http://stamen.com " >Stamen Design</a>, under <a href= " http://creativecommons.org/licenses/by/3.0 " >CC BY 3.0</a>. Data by <a href= " http://openstreetmap.org " >OpenStreetMap</a>, under <a href= " http://www.openstreetmap.org/copyright " >ODbL</a>. '
)
# Ajouter d'autres options de fonds de carte
folium . TileLayer (
' Stamen Toner ' ,
attr = ' Map tiles by <a href= " http://stamen.com " >Stamen Design</a>, under <a href= " http://creativecommons.org/licenses/by/3.0 " >CC BY 3.0</a>. Data by <a href= " http://openstreetmap.org " >OpenStreetMap</a>, under <a href= " http://www.openstreetmap.org/copyright " >ODbL</a>. '
) . add_to ( m )
folium . TileLayer (
' Stamen Watercolor ' ,
attr = ' Map tiles by <a href= " http://stamen.com " >Stamen Design</a>, under <a href= " http://creativecommons.org/licenses/by/3.0 " >CC BY 3.0</a>. Data by <a href= " http://openstreetmap.org " >OpenStreetMap</a>, under <a href= " http://www.openstreetmap.org/copyright " >ODbL</a>. '
) . add_to ( m )
folium . TileLayer (
' OpenStreetMap ' ,
attr = ' © <a href= " https://www.openstreetmap.org/copyright " >OpenStreetMap</a> contributors '
) . add_to ( m )
folium . LayerControl ( ) . add_to ( m )
2025-03-16 12:54:21 +01:00
# Ajouter un titre
title_html = f '''
< h3 align = " center " style = " font-size:16px " > < b > Carte de { city_name } < / b > < / h3 >
< p align = " center " style = " font-size:12px " > ID OpenStreetMap : { city_data . get ( ' city_data ' , { } ) . get ( ' elements ' , [ { } ] ) [ 0 ] . get ( ' id ' , ' N/A ' ) } ( Type : { element_type } ) < / p >
'''
m . get_root ( ) . html . add_child ( folium . Element ( title_html ) )
# Ajouter un marqueur pour le centre
folium . Marker (
location = center ,
popup = f " Centre de { city_name } " ,
icon = folium . Icon ( color = ' red ' , icon = ' info-sign ' )
) . add_to ( m )
# Extraire les nœuds pour construire les géométries
nodes = { n [ ' id ' ] : ( n [ ' lat ' ] , n [ ' lon ' ] ) for n in elements_data [ ' elements ' ] if n [ ' type ' ] == ' node ' }
# Compter les éléments pour les statistiques
highways_count = 0
buildings_count = 0
parkings_count = 0
# Traiter les routes, bâtiments et parkings
for element in elements_data [ ' elements ' ] :
if element [ ' type ' ] == ' way ' and ' tags ' in element :
# Récupérer les coordonnées des nœuds
coords = [ ]
for node_id in element [ ' nodes ' ] :
if node_id in nodes :
coords . append ( nodes [ node_id ] )
if not coords :
continue
# Déterminer le type d'élément
if ' highway ' in element [ ' tags ' ] :
# C'est une route
highways_count + = 1
folium . PolyLine (
coords ,
color = ' #555555 ' ,
weight = 2 ,
opacity = 0.7 ,
tooltip = element [ ' tags ' ] . get ( ' name ' , ' Route ' )
) . add_to ( m )
elif ' building ' in element [ ' tags ' ] :
# C'est un bâtiment
buildings_count + = 1
folium . Polygon (
coords ,
color = ' #777777 ' ,
fill = True ,
fill_color = ' #777777 ' ,
fill_opacity = 0.7 ,
tooltip = element [ ' tags ' ] . get ( ' name ' , ' Bâtiment ' )
) . add_to ( m )
elif element [ ' tags ' ] . get ( ' amenity ' ) == ' parking ' :
# C'est un parking
parkings_count + = 1
folium . Polygon (
coords ,
color = ' #999999 ' ,
fill = True ,
fill_color = ' #999999 ' ,
fill_opacity = 0.7 ,
tooltip = element [ ' tags ' ] . get ( ' name ' , ' Parking ' )
) . add_to ( m )
# Ajouter une légende avec les statistiques
legend_html = f '''
< div style = " position: fixed;
bottom : 50 px ; right : 50 px ; width : 200 px ; height : 130 px ;
border : 2 px solid grey ; z - index : 9999 ; font - size : 12 px ;
background - color : white ; padding : 10 px ;
border - radius : 5 px ; " >
< p > < b > Statistiques < / b > < / p >
< p > Routes : { highways_count } < / p >
< p > Bâtiments : { buildings_count } < / p >
< p > Parkings : { parkings_count } < / p >
< / div >
'''
m . get_root ( ) . html . add_child ( folium . Element ( legend_html ) )
# Définir le chemin de sortie
if not output_path :
output_path = f " { city_name . replace ( ' ' , ' _ ' ) } _map.html "
# Sauvegarder la carte
logger . info ( f " Sauvegarde de la carte dans: { output_path } " )
m . save ( output_path )
# Générer également une image statique si possible
try :
import selenium
from selenium import webdriver
from selenium . webdriver . chrome . options import Options
logger . info ( " Génération d ' une image statique de la carte... " )
# Configurer Selenium pour prendre une capture d'écran
chrome_options = Options ( )
chrome_options . add_argument ( " --headless " )
chrome_options . add_argument ( " --no-sandbox " )
chrome_options . add_argument ( " --disable-dev-shm-usage " )
driver = webdriver . Chrome ( options = chrome_options )
driver . set_window_size ( 1200 , 800 )
# Charger la carte HTML
driver . get ( f " file:// { os . path . abspath ( output_path ) } " )
# Attendre que la carte se charge
driver . implicitly_wait ( 5 )
# Prendre une capture d'écran
png_path = output_path . replace ( ' .html ' , ' .png ' )
driver . save_screenshot ( png_path )
driver . quit ( )
logger . info ( f " Image statique générée: { png_path } " )
except Exception as e :
logger . warning ( f " Impossible de générer une image statique: { str ( e ) } " )
return output_path
except Exception as e :
logger . error ( f " Erreur lors de la création de la carte: { str ( e ) } " )
return None
def main ( ) :
"""
Fonction principale qui traite les arguments de ligne de commande
et génère la carte de la ville .
"""
parser = argparse . ArgumentParser ( description = ' Génère une carte interactive d \' une ville à partir de son ID OpenStreetMap. ' )
parser . add_argument ( ' osm_id ' , type = int , help = ' ID OpenStreetMap de la ville ' )
parser . add_argument ( ' -o ' , ' --output ' , type = str , help = ' Chemin où sauvegarder la carte (par défaut: nom_ville_map.html) ' )
parser . add_argument ( ' -v ' , ' --verbose ' , action = ' store_true ' , help = ' Afficher les messages de débogage ' )
args = parser . parse_args ( )
# Configurer le niveau de journalisation
if args . verbose :
logger . setLevel ( logging . DEBUG )
# Récupérer les données de la ville
city_data = get_city_data_from_osm ( args . osm_id )
if not city_data :
logger . error ( f " Impossible de récupérer les données pour l ' ID OSM: { args . osm_id } " )
sys . exit ( 1 )
# Créer la carte
output_path = create_folium_map ( city_data , args . output )
if output_path :
logger . info ( f " Carte générée avec succès: { output_path } " )
else :
logger . error ( " Échec de la génération de la carte " )
sys . exit ( 1 )
if __name__ == " __main__ " :
main ( )