#!/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 def get_elements_in_area(osm_id, element_type, bbox=None): """ Récupère les routes, bâtiments et parkings dans une zone définie par un élément OSM. Args: 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 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 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)") # 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}") # Calculer la boîte englobante pour l'affichage et comme fallback bbox = get_bbox_from_data(data) if not bbox: print("Impossible de calculer la boîte englobante") return None # 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) 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) # Créer la carte avec fond Stamen Terrain print("Création de la carte...") 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'''

Carte de {name}

ID OpenStreetMap: {osm_id} (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 {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}

''' 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()