parking-land/simple_map.py

399 lines
No EOL
13 KiB
Python

#!/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 <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='&copy; <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 {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: 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"{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()