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