2025-03-16 12:54:21 +01:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script très simplifié pour générer une carte d ' une ville à partir de son ID OpenStreetMap.
Utilise directement l ' API Overpass et Folium pour créer une carte HTML.
"""
import os
import sys
import argparse
import requests
import folium
import json
from datetime import datetime
def get_osm_data ( osm_id , element_type = " relation " ) :
"""
Récupère les données OpenStreetMap pour un élément donné .
Args :
osm_id ( int ) : L ' identifiant OpenStreetMap
element_type ( str ) : Le type d ' élément ( ' relation ' , ' way ' , ' node ' )
Returns :
dict : Les données récupérées ou None en cas d ' erreur
"""
print ( f " Récupération des données pour { element_type } / { osm_id } ... " )
# Construire la requête Overpass
if element_type == " relation " :
query = f """
[ out : json ] ;
relation ( { osm_id } ) ;
out body ;
> ;
out skel qt ;
"""
elif element_type == " way " :
query = f """
[ out : json ] ;
way ( { osm_id } ) ;
out body ;
> ;
out skel qt ;
"""
elif element_type == " node " :
query = f """
[ out : json ] ;
node ( { osm_id } ) ;
out body ;
"""
else :
print ( f " Type d ' élément non reconnu: { element_type } " )
return None
# Envoyer la requête à l'API Overpass
try :
overpass_url = " https://overpass-api.de/api/interpreter "
response = requests . post ( overpass_url , data = { " data " : query } )
if response . status_code != 200 :
print ( f " Erreur lors de la requête Overpass: { response . status_code } " )
return None
data = response . json ( )
if not data . get ( ' elements ' ) :
print ( f " Aucune donnée trouvée pour { element_type } / { osm_id } " )
return None
return data
except Exception as e :
print ( f " Erreur lors de la récupération des données: { str ( e ) } " )
return None
def get_bbox_from_data ( data ) :
"""
Calcule la boîte englobante à partir des données OSM .
Args :
data ( dict ) : Les données OSM
Returns :
tuple : ( min_lat , min_lon , max_lat , max_lon ) ou None
"""
try :
nodes = [ e for e in data [ ' elements ' ] if e . get ( ' type ' ) == ' node ' ]
if not nodes :
print ( " Aucun nœud trouvé dans les données " )
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 :
print ( " Coordonnées manquantes dans les données " )
return None
min_lat , max_lat = min ( lats ) , max ( lats )
min_lon , max_lon = min ( lons ) , max ( lons )
return ( min_lat , min_lon , max_lat , max_lon )
except Exception as e :
print ( f " Erreur lors du calcul de la boîte englobante: { str ( e ) } " )
return None
2025-03-16 16:13:45 +01:00
def get_elements_in_area ( osm_id , element_type , bbox = None ) :
2025-03-16 12:54:21 +01:00
"""
2025-03-16 16:13:45 +01:00
Récupère les routes , bâtiments et parkings dans une zone définie par un élément OSM .
2025-03-16 12:54:21 +01:00
Args :
2025-03-16 16:13:45 +01:00
osm_id ( int ) : L ' identifiant OpenStreetMap
element_type ( str ) : Le type d ' élément ( ' relation ' , ' way ' , ' node ' )
bbox ( tuple , optional ) : ( min_lat , min_lon , max_lat , max_lon ) à utiliser si l ' area ne fonctionne pas
2025-03-16 12:54:21 +01:00
Returns :
dict : Les données récupérées ou None en cas d ' erreur
"""
print ( f " Récupération des éléments dans la zone... " )
# Construire la requête Overpass
2025-03-16 16:13:45 +01:00
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
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
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
if not bbox :
print ( " Erreur: bbox requis pour les nodes " )
return None
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 ;
"""
print ( f " Utilisation d ' une boîte englobante pour { element_type } { osm_id } (moins précis) " )
2025-03-16 12:54:21 +01:00
# Envoyer la requête à l'API Overpass
try :
overpass_url = " https://overpass-api.de/api/interpreter "
response = requests . post ( overpass_url , data = { " data " : query } )
if response . status_code != 200 :
print ( f " Erreur lors de la requête Overpass: { response . status_code } " )
return None
data = response . json ( )
if not data . get ( ' elements ' ) :
print ( " Aucun élément trouvé dans la zone " )
return None
return data
except Exception as e :
print ( f " Erreur lors de la récupération des éléments: { str ( e ) } " )
return None
def create_map ( osm_id , output_path = None ) :
"""
Crée une carte pour un élément OpenStreetMap .
Args :
osm_id ( int ) : L ' identifiant OpenStreetMap
output_path ( str ) : Chemin où sauvegarder la carte HTML
Returns :
str : Chemin vers le fichier HTML généré ou None en cas d ' erreur
"""
# Essayer d'abord en tant que relation
data = get_osm_data ( osm_id , " relation " )
element_type = " relation "
# Si ça ne fonctionne pas, essayer en tant que way
if not data :
print ( " Essai en tant que chemin (way)... " )
data = get_osm_data ( osm_id , " way " )
element_type = " way "
# Si ça ne fonctionne toujours pas, essayer en tant que node
if not data :
print ( " Essai en tant que nœud (node)... " )
data = get_osm_data ( osm_id , " node " )
element_type = " node "
# Si aucune méthode ne fonctionne
if not data :
print ( f " Impossible de récupérer les données pour l ' ID OSM: { osm_id } " )
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 :
print ( f " Élément non trouvé dans les données " )
return None
# Récupérer le nom
name = element_info . get ( ' tags ' , { } ) . get ( ' name ' , f " Lieu_ { osm_id } " )
print ( f " Lieu identifié: { name } " )
2025-03-16 16:13:45 +01:00
# Calculer la boîte englobante pour l'affichage et comme fallback
2025-03-16 12:54:21 +01:00
bbox = get_bbox_from_data ( data )
if not bbox :
print ( " Impossible de calculer la boîte englobante " )
return None
2025-03-16 16:13:45 +01:00
# Récupérer les éléments dans la zone définie par l'élément OSM
elements_data = get_elements_in_area ( osm_id , element_type , bbox )
2025-03-16 12:54:21 +01:00
if not elements_data :
print ( " Impossible de récupérer les éléments dans la zone " )
return None
# Calculer le centre de la carte
center_lat = ( bbox [ 0 ] + bbox [ 2 ] ) / 2
center_lon = ( bbox [ 1 ] + bbox [ 3 ] ) / 2
center = ( center_lat , center_lon )
2025-03-16 16:13:45 +01:00
# Créer la carte avec fond Stamen Terrain
2025-03-16 12:54:21 +01:00
print ( " Création de la carte... " )
2025-03-16 16:13:45 +01:00
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 { name } < / b > < / h3 >
< p align = " center " style = " font-size:12px " > ID OpenStreetMap : { osm_id } ( 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 { 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 " { name . replace ( ' ' , ' _ ' ) } _map.html "
elif not output_path . lower ( ) . endswith ( ' .html ' ) :
output_path = f " { os . path . splitext ( output_path ) [ 0 ] } .html "
# Sauvegarder la carte
print ( f " Sauvegarde de la carte dans: { output_path } " )
m . save ( output_path )
print ( f " Carte générée avec succès: { output_path } " )
print ( f " Pour visualiser la carte, ouvrez le fichier HTML dans un navigateur web: " )
print ( f " file:// { os . path . abspath ( output_path ) } " )
return output_path
def main ( ) :
"""
Fonction principale qui traite les arguments de ligne de commande
et génère la carte .
"""
parser = argparse . ArgumentParser ( description = ' Génère une carte pour un élément OpenStreetMap. ' )
parser . add_argument ( ' osm_id ' , type = int , help = ' ID OpenStreetMap ' )
parser . add_argument ( ' -o ' , ' --output ' , type = str , help = ' Chemin où sauvegarder la carte HTML ' )
args = parser . parse_args ( )
# Créer la carte
create_map ( args . osm_id , args . output )
if __name__ == " __main__ " :
main ( )