#!/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 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)") 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') # 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 Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.' ) # Ajouter d'autres options de fonds de carte folium.TileLayer( 'Stamen Toner', attr='Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.' ).add_to(m) folium.TileLayer( 'Stamen Watercolor', attr='Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.' ).add_to(m) folium.TileLayer( 'OpenStreetMap', attr='© OpenStreetMap contributors' ).add_to(m) folium.LayerControl().add_to(m) # Ajouter un titre title_html = f'''
ID OpenStreetMap: {city_data.get('city_data', {}).get('elements', [{}])[0].get('id', 'N/A')} (Type: {element_type})
''' 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'''Statistiques
Routes: {highways_count}
Bâtiments: {buildings_count}
Parkings: {parkings_count}