parking-land/map_simple.py

383 lines
14 KiB
Python
Raw Normal View History

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)
# Utiliser la boîte englobante pour la requête
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.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 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')
# Créer une carte centrée sur la ville
m = folium.Map(location=center, zoom_start=14, tiles='OpenStreetMap')
# 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: 50px; right: 50px; width: 200px; height: 130px;
border:2px solid grey; z-index:9999; font-size:12px;
background-color: white; padding: 10px;
border-radius: 5px;">
<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()