mirror of
https://forge.chapril.org/tykayn/parking-land
synced 2025-06-20 01:44:42 +02:00
442 lines
No EOL
17 KiB
Python
442 lines
No EOL
17 KiB
Python
#!/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 <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)
|
|
|
|
# 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() |