generate map with folium

This commit is contained in:
Tykayn 2025-03-16 12:54:21 +01:00 committed by tykayn
parent 2399302e4e
commit 8cb321a9c8
23 changed files with 115808 additions and 80 deletions

154
README.md
View file

@ -1,8 +1,148 @@
# parking land
calcul d'infos sur les nombre et les surfaces estimées sur le périmètre d'une ville selon les données OSM.
# Analyse Urbaine - Générateur de Rapports et Cartes
- Bâtiments
- Routes
- Parking voiture et vélo
- Pistes cyclables
- Stations de recharge pour véhicule électrique
Ce projet permet de générer des rapports HTML et des cartes visuelles présentant les résultats d'analyse urbaine pour une ville donnée, à partir de données JSON et OpenStreetMap.
## Fonctionnalités
- Génération de rapports HTML à partir de données JSON
- Visualisation des statistiques urbaines (routes, pistes cyclables, parkings, bâtiments, etc.)
- Génération de cartes visuelles des villes à partir de leur ID OpenStreetMap
- Calcul automatique de ratios et d'indicateurs
- Templates personnalisables
- Interface responsive adaptée aux mobiles et ordinateurs
## Prérequis
- Python 3.6 ou supérieur
- Jinja2 (`pip install jinja2`)
- Pour les cartes : OSMnx ou Folium (voir requirements.txt)
## Installation
1. Clonez ce dépôt :
```
git clone https://github.com/votre-utilisateur/analyse-urbaine.git
cd analyse-urbaine
```
2. Installez les dépendances :
```
pip install -r requirements.txt
```
3. Pour la conversion HTML vers JPG, installez l'un des outils suivants :
- wkhtmltopdf/wkhtmltoimage : https://wkhtmltopdf.org/downloads.html
- CutyCapt (pour Linux) : `sudo apt-get install cutycapt`
## Utilisation
### Génération d'un rapport HTML
```bash
python present.py <fichier_json> [fichier_html_sortie] [fichier_template]
```
Arguments :
- `<fichier_json>` : Chemin vers le fichier JSON contenant les données (obligatoire)
- `[fichier_html_sortie]` : Chemin où sauvegarder le fichier HTML (par défaut: resultat.html)
- `[fichier_template]` : Nom du fichier template à utiliser (par défaut: template.html)
### Exemple
```bash
python present.py summary_results.json rapport_ville.html template_ameliore.html
```
### Génération d'une carte de ville
```bash
python generate_city_map.py <osm_id> [options]
```
Arguments :
- `<osm_id>` : ID OpenStreetMap de la ville (obligatoire)
- `-o, --output` : Chemin où sauvegarder l'image
- `--folium` : Utiliser Folium au lieu d'OSMnx (plus léger mais moins détaillé)
- `-v, --verbose` : Afficher les messages de débogage
### Exemple
```bash
python generate_city_map.py 123456 -o carte_ville.jpg
```
### Utilisation avancée
Pour plus de contrôle, vous pouvez utiliser les scripts individuels :
1. Génération de carte avec OSMnx (haute qualité) :
```bash
python map.py <osm_id> [-o output.jpg]
```
2. Génération de carte avec Folium (plus léger) :
```bash
python map_simple.py <osm_id> [-o output.html]
```
3. Conversion d'une carte HTML en JPG :
```bash
python html_to_jpg.py <fichier_html> [-o output.jpg] [-m méthode]
```
### Test de la solution
Pour tester la solution avec des données fictives :
```bash
python test_present.py
```
## Structure des données JSON
Le fichier JSON doit contenir les clés suivantes :
```json
{
"city_name": "Nom de la ville",
"longueur_route_km": 228.33,
"road_cycleway_km": 12.5,
"compte_highways": 3530,
"surface_route_km2": 1.59,
"building_count": 2516,
"building_area": 2.65,
"building_sizes": [0, 10, 50, 100, 200, 500, "Infinity"],
"building_size_counts": [120, 450, 780, 650, 400, 116],
"compte_piste_cyclable": 42,
"car_parking_capacity_provided": 491,
"roundabout_count": 12,
"mini_roundabout_count": 5,
"surface_parking_km2": 0.35,
"surface_bicycle_parking_km2": 0.042,
"charging_stations": 14,
"charging_stations_with_capacity_count": 12,
"charging_stations_capacity_provided": 28,
"charging_points": 32
}
```
## Templates disponibles
- `template.html` : Template de base avec tableau simple
- `template_ameliore.html` : Template amélioré avec design moderne, cartes et visualisations
## Personnalisation
Vous pouvez créer vos propres templates en vous basant sur les templates existants. Utilisez la syntaxe Jinja2 pour accéder aux données.
## Trouver l'ID OpenStreetMap d'une ville
1. Allez sur [OpenStreetMap](https://www.openstreetmap.org/)
2. Recherchez la ville souhaitée
3. Cliquez sur la ville dans les résultats
4. L'URL contiendra l'ID de la relation, par exemple : `https://www.openstreetmap.org/relation/123456`
5. L'ID est le nombre après "relation/" (ici, 123456)
## Licence
Ce projet est sous licence MIT.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

112988
carte_ville.html Normal file

File diff suppressed because it is too large Load diff

BIN
carte_ville.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

158
generate_city_map.py Normal file
View file

@ -0,0 +1,158 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script principal qui génère une carte d'une ville à partir de son ID OpenStreetMap
et la convertit en image JPG en une seule commande.
"""
import os
import sys
import argparse
import logging
import importlib.util
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 check_module_available(module_name):
"""Vérifie si un module Python est disponible."""
return importlib.util.find_spec(module_name) is not None
def generate_map(osm_id, output_path=None, use_osmnx=True, verbose=False):
"""
Génère une carte d'une ville à partir de son ID OpenStreetMap.
Args:
osm_id (int): L'identifiant OpenStreetMap de la ville
output_path (str, optional): Chemin sauvegarder l'image
use_osmnx (bool): Utiliser OSMnx (True) ou Folium (False)
verbose (bool): Afficher les messages de débogage
Returns:
str: Chemin vers l'image générée ou None en cas d'erreur
"""
try:
# Configurer le niveau de journalisation
if verbose:
logger.setLevel(logging.DEBUG)
# Vérifier si l'ID OSM est valide
if osm_id <= 0:
logger.error(f"ID OpenStreetMap invalide: {osm_id}")
return None
# Déterminer quelle méthode utiliser
osmnx_available = check_module_available('osmnx')
if use_osmnx and osmnx_available:
try:
logger.info("Utilisation d'OSMnx pour générer la carte...")
# Importer map.py
import map
# Vérifier si la fonction config existe dans osmnx
if not hasattr(map.ox, 'config'):
logger.warning("La fonction 'config' n'existe pas dans OSMnx. Utilisation de Folium à la place.")
raise AttributeError("Module 'osmnx' has no attribute 'config'")
# Générer la carte avec OSMnx
city_data = map.get_city_by_osm_id(osm_id)
if not city_data:
logger.warning(f"Impossible de récupérer les données pour l'ID OSM: {osm_id} avec OSMnx. Essai avec Folium...")
raise ValueError("Aucune donnée trouvée")
return map.generate_city_map(city_data, output_path)
except Exception as e:
logger.warning(f"Erreur avec OSMnx: {str(e)}. Utilisation de Folium à la place.")
use_osmnx = False
if not use_osmnx or not osmnx_available:
if use_osmnx and not osmnx_available:
logger.warning("OSMnx n'est pas disponible. Utilisation de Folium à la place.")
logger.info("Utilisation de Folium pour générer la carte...")
# Importer map_simple.py
import map_simple
# Générer la carte avec Folium
city_data = map_simple.get_city_data_from_osm(osm_id)
if not city_data:
logger.error(f"Impossible de récupérer les données pour l'ID OSM: {osm_id}")
return None
html_path = map_simple.create_folium_map(city_data, output_path)
if not html_path:
logger.error("Échec de la génération de la carte HTML")
return None
# Si un chemin de sortie spécifique est demandé avec une extension .jpg ou .png
if output_path and (output_path.lower().endswith('.jpg') or output_path.lower().endswith('.png')):
# Essayer de convertir en image
try:
# Importer html_to_jpg.py
import html_to_jpg
# Convertir en image
logger.info(f"Conversion de la carte HTML en {os.path.splitext(output_path)[1][1:].upper()}...")
# Essayer différentes méthodes de conversion
for method in ['imgkit','wkhtmltoimage', 'cutycapt']:
try:
result = html_to_jpg.convert_html_to_jpg(html_path, output_path, method)
if result:
return result
except Exception as e:
logger.debug(f"Échec de la méthode {method}: {str(e)}")
logger.warning(f"Impossible de convertir en {os.path.splitext(output_path)[1][1:].upper()}. Utilisation du fichier HTML à la place.")
return html_path
except ImportError:
logger.warning("Module html_to_jpg non disponible. Utilisation du fichier HTML à la place.")
return html_path
else:
# Si aucune conversion n'est demandée, retourner le chemin HTML
return html_path
except Exception as e:
logger.error(f"Erreur lors de la génération 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 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 l\'image')
parser.add_argument('--folium', action='store_true', help='Utiliser Folium au lieu d\'OSMnx')
parser.add_argument('-v', '--verbose', action='store_true', help='Afficher les messages de débogage')
args = parser.parse_args()
# Générer la carte
output_path = generate_map(args.osm_id, args.output, not args.folium, args.verbose)
if output_path:
logger.info(f"Carte générée avec succès: {output_path}")
# Afficher des instructions pour visualiser la carte
if output_path.lower().endswith('.html'):
logger.info(f"Pour visualiser la carte, ouvrez le fichier HTML dans un navigateur web:")
logger.info(f" file://{os.path.abspath(output_path)}")
else:
logger.error("Échec de la génération de la carte")
sys.exit(1)
if __name__ == "__main__":
main()

99
html2jpg.py Normal file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script simplifié pour convertir un fichier HTML en image JPG.
Utilise playwright pour rendre la page web et la capturer en image.
"""
import os
import sys
import argparse
from pathlib import Path
def convert_html_to_jpg(html_path, output_path=None):
"""
Convertit un fichier HTML en image JPG en utilisant playwright.
Args:
html_path (str): Chemin vers le fichier HTML à convertir
output_path (str, optional): Chemin sauvegarder l'image JPG
Returns:
str: Chemin vers l'image JPG générée ou None en cas d'erreur
"""
if not os.path.exists(html_path):
print(f"Erreur: Le fichier HTML n'existe pas: {html_path}")
return None
# Définir le chemin de sortie si non spécifié
if not output_path:
output_path = os.path.splitext(html_path)[0] + '.jpg'
try:
# Vérifier si playwright est installé
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("Erreur: playwright n'est pas installé.")
print("Installez-le avec: pip install playwright")
print("Puis exécutez: playwright install")
return None
# Obtenir le chemin absolu du fichier HTML
abs_path = os.path.abspath(html_path)
file_url = f"file://{abs_path}"
print(f"Conversion de {html_path} en {output_path}...")
# Utiliser playwright pour capturer la page
with sync_playwright() as p:
# Lancer un navigateur
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1200, "height": 800})
# Naviguer vers la page HTML
page.goto(file_url)
# Attendre que la page soit chargée
page.wait_for_load_state("networkidle")
# Attendre un peu plus pour les cartes qui peuvent prendre du temps à se charger
page.wait_for_timeout(2000)
# Prendre une capture d'écran
page.screenshot(path=output_path, full_page=True)
# Fermer le navigateur
browser.close()
print(f"Conversion réussie: {output_path}")
return output_path
except Exception as e:
print(f"Erreur lors de la conversion: {str(e)}")
# Proposer des solutions alternatives
print("Suggestions pour résoudre le problème:")
print("1. Installez playwright: pip install playwright")
print("2. Exécutez: playwright install")
print("3. Utilisez un navigateur pour ouvrir le fichier HTML et faites une capture d'écran manuellement")
return None
def main():
"""
Fonction principale qui traite les arguments de ligne de commande
et convertit le fichier HTML en JPG.
"""
parser = argparse.ArgumentParser(description='Convertit un fichier HTML en image JPG.')
parser.add_argument('html_path', type=str, help='Chemin vers le fichier HTML à convertir')
parser.add_argument('-o', '--output', type=str, help='Chemin où sauvegarder l\'image JPG')
args = parser.parse_args()
# Convertir le fichier HTML en JPG
convert_html_to_jpg(args.html_path, args.output)
if __name__ == "__main__":
main()

148
html_to_jpg.py Normal file
View file

@ -0,0 +1,148 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour convertir une carte HTML générée par Folium en image JPG.
Utilise imgkit qui s'appuie sur wkhtmltoimage.
"""
import os
import sys
import argparse
import logging
import subprocess
from pathlib import Path
# Configuration de la journalisation
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def convert_html_to_jpg(html_path, output_path=None, method='wkhtmltoimage'):
"""
Convertit un fichier HTML en image JPG.
Args:
html_path (str): Chemin vers le fichier HTML à convertir
output_path (str, optional): Chemin sauvegarder l'image JPG. Si None, utilise le même nom que le HTML.
method (str): Méthode de conversion ('wkhtmltoimage', 'imgkit', 'cutycapt')
Returns:
str: Chemin vers l'image JPG générée ou None en cas d'erreur
"""
if not os.path.exists(html_path):
logger.error(f"Le fichier HTML n'existe pas: {html_path}")
return None
# Définir le chemin de sortie si non spécifié
if not output_path:
output_path = os.path.splitext(html_path)[0] + '.jpg'
try:
if method == 'wkhtmltoimage':
# Utiliser wkhtmltoimage (doit être installé sur le système)
logger.info(f"Conversion avec wkhtmltoimage: {html_path} -> {output_path}")
cmd = [
'wkhtmltoimage',
'--quality', '90',
'--width', '1200',
'--height', '800',
'--javascript-delay', '2000', # Attendre 2 secondes pour le chargement JavaScript
html_path,
output_path
]
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode != 0:
logger.error(f"Erreur lors de la conversion: {process.stderr}")
return None
elif method == 'imgkit':
# Utiliser imgkit (doit être installé via pip)
try:
import imgkit
logger.info(f"Conversion avec imgkit: {html_path} -> {output_path}")
options = {
'quality': 90,
'width': 1200,
'height': 800,
'javascript-delay': 2000
}
imgkit.from_file(html_path, output_path, options=options)
except ImportError:
logger.error("imgkit n'est pas installé. Installez-le avec 'pip install imgkit'")
return None
elif method == 'cutycapt':
# Utiliser cutycapt (doit être installé sur le système)
logger.info(f"Conversion avec cutycapt: {html_path} -> {output_path}")
cmd = [
'cutycapt',
'--url', f"file://{os.path.abspath(html_path)}",
'--out', output_path,
'--min-width', '1200',
'--min-height', '800',
'--delay', '2000' # Attendre 2 secondes
]
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode != 0:
logger.error(f"Erreur lors de la conversion: {process.stderr}")
return None
else:
logger.error(f"Méthode de conversion non reconnue: {method}")
return None
# Vérifier si le fichier a été créé
if os.path.exists(output_path):
logger.info(f"Conversion réussie: {output_path}")
return output_path
else:
logger.error("Le fichier de sortie n'a pas été créé")
return None
except Exception as e:
logger.error(f"Erreur lors de la conversion: {str(e)}")
# Proposer des solutions alternatives
logger.info("Suggestions pour résoudre le problème:")
logger.info("1. Installez wkhtmltopdf/wkhtmltoimage: https://wkhtmltopdf.org/downloads.html")
logger.info("2. Installez imgkit: pip install imgkit")
logger.info("3. Utilisez un navigateur pour ouvrir le fichier HTML et faites une capture d'écran manuellement")
return None
def main():
"""
Fonction principale qui traite les arguments de ligne de commande
et convertit le fichier HTML en JPG.
"""
parser = argparse.ArgumentParser(description='Convertit un fichier HTML en image JPG.')
parser.add_argument('html_path', type=str, help='Chemin vers le fichier HTML à convertir')
parser.add_argument('-o', '--output', type=str, help='Chemin où sauvegarder l\'image JPG')
parser.add_argument('-m', '--method', type=str, choices=['wkhtmltoimage', 'imgkit', 'cutycapt'],
default='wkhtmltoimage', help='Méthode de conversion à utiliser')
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)
# Convertir le fichier HTML en JPG
output_path = convert_html_to_jpg(args.html_path, args.output, args.method)
if output_path:
logger.info(f"Conversion réussie: {output_path}")
else:
logger.error("Échec de la conversion")
sys.exit(1)
if __name__ == "__main__":
main()

176
map.py Normal file
View file

@ -0,0 +1,176 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour générer une carte visuelle d'une ville à partir de son ID OpenStreetMap.
Les routes, parkings et bâtiments sont colorés en gris sur un fond vert.
"""
import os
import sys
import argparse
import osmnx as ox
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import networkx as nx
import geopandas as gpd
from shapely.geometry import Point, Polygon, MultiPolygon
import logging
# Configuration de la journalisation
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Configuration globale d'OSMnx
ox.config(use_cache=True, log_console=False)
def get_city_by_osm_id(osm_id):
"""
Récupère les données d'une ville à partir de son ID OpenStreetMap.
Args:
osm_id (int): L'identifiant OpenStreetMap de la ville
Returns:
dict: Un dictionnaire contenant les différentes géométries de la ville
"""
try:
logger.info(f"Récupération des données pour la ville avec l'ID OSM: {osm_id}")
# Récupérer la géométrie de la ville
city_boundary = ox.geocode_to_gdf(f"relation/{osm_id}")
if city_boundary.empty:
logger.error(f"Aucune donnée trouvée pour l'ID OSM: {osm_id}")
return None
# Récupérer le nom de la ville
city_name = city_boundary.iloc[0].get('name', f"Ville_{osm_id}")
logger.info(f"Ville identifiée: {city_name}")
# Récupérer le réseau routier
logger.info("Récupération du réseau routier...")
road_network = ox.graph_from_polygon(city_boundary.iloc[0].geometry, network_type='drive')
# Récupérer les bâtiments
logger.info("Récupération des bâtiments...")
buildings = ox.features_from_polygon(city_boundary.iloc[0].geometry, tags={'building': True})
# Récupérer les parkings
logger.info("Récupération des parkings...")
parkings = ox.features_from_polygon(city_boundary.iloc[0].geometry, tags={'amenity': 'parking'})
return {
'city_name': city_name,
'boundary': city_boundary,
'road_network': road_network,
'buildings': buildings,
'parkings': parkings
}
except Exception as e:
logger.error(f"Erreur lors de la récupération des données: {str(e)}")
return None
def generate_city_map(city_data, output_path=None):
"""
Génère une carte visuelle de la ville avec les routes, parkings et bâtiments.
Args:
city_data (dict): Dictionnaire contenant les données de la ville
output_path (str, optional): Chemin sauvegarder l'image. Si None, utilise le nom de la ville.
Returns:
str: Chemin vers l'image générée
"""
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']
# Créer une figure de grande taille
fig, ax = plt.subplots(figsize=(15, 15), dpi=300)
# Définir les couleurs
background_color = '#8CDD81' # Vert clair pour le fond
road_color = '#555555' # Gris pour les routes
building_color = '#777777' # Gris pour les bâtiments
parking_color = '#999999' # Gris clair pour les parkings
# Dessiner le fond (limite de la ville)
city_data['boundary'].plot(ax=ax, facecolor=background_color, edgecolor='none', alpha=0.8)
# Dessiner les routes
if 'road_network' in city_data and city_data['road_network']:
logger.info("Dessin du réseau routier...")
ox.plot_graph(city_data['road_network'], ax=ax, node_size=0,
edge_color=road_color, edge_linewidth=0.5, edge_alpha=0.7)
# Dessiner les bâtiments
if 'buildings' in city_data and not city_data['buildings'].empty:
logger.info("Dessin des bâtiments...")
city_data['buildings'].plot(ax=ax, facecolor=building_color, edgecolor='none', alpha=0.7)
# Dessiner les parkings
if 'parkings' in city_data and not city_data['parkings'].empty:
logger.info("Dessin des parkings...")
city_data['parkings'].plot(ax=ax, facecolor=parking_color, edgecolor='none', alpha=0.7)
# Configurer les aspects visuels
ax.set_title(f"Carte de {city_name}", fontsize=16)
ax.set_axis_off()
# Ajuster les limites de la carte
plt.tight_layout()
# Définir le chemin de sortie
if not output_path:
output_path = f"{city_name.replace(' ', '_')}_city_map.jpg"
# Sauvegarder l'image
logger.info(f"Sauvegarde de la carte dans: {output_path}")
plt.savefig(output_path, dpi=300, bbox_inches='tight')
plt.close()
return output_path
except Exception as e:
logger.error(f"Erreur lors de la génération 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 visuelle 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 l\'image (par défaut: nom_ville_city_map.jpg)')
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_by_osm_id(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)
# Générer la carte
output_path = generate_city_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()

383
map_simple.py Normal file
View file

@ -0,0 +1,383 @@
#!/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 la boîte englobante pour la requête
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.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 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
m = folium.Map(location=center, zoom_start=14, tiles='OpenStreetMap')
# 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()

View file

@ -2,8 +2,8 @@
"version": 0.6,
"generator": "Overpass API 0.7.62.5 1bd436f1",
"osm3s": {
"timestamp_osm_base": "2025-02-19T10:34:45Z",
"timestamp_areas_base": "2024-12-31T00:49:33Z",
"timestamp_osm_base": "2025-03-16T11:21:10Z",
"timestamp_areas_base": "2025-02-06T02:17:44Z",
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
},
"elements": [
@ -356,6 +356,29 @@
"survey:date": "2024-04-26"
}
},
{
"type": "node",
"id": 12600445762,
"lat": 48.618899,
"lon": 2.1266619,
"tags": {
"access": "yes",
"amenity": "bicycle_parking",
"capacity": "6",
"covered": "yes",
"fee": "no"
}
},
{
"type": "node",
"id": 12600445767,
"lat": 48.6193101,
"lon": 2.126613,
"tags": {
"amenity": "bicycle_parking",
"capacity": "4"
}
},
{
"type": "way",
"id": 16794777,
@ -646,6 +669,7 @@
"tags": {
"cycleway:right": "lane",
"highway": "primary",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"oneway": "yes",
@ -680,6 +704,7 @@
"tags": {
"cycleway:right": "lane",
"highway": "primary",
"lane_markings": "no",
"maxspeed:type": "FR:rural",
"oneway": "yes",
"ref": "D 97",
@ -718,6 +743,7 @@
"tags": {
"cycleway:right": "lane",
"highway": "primary",
"lanes": "1",
"lit": "yes",
"maxspeed:type": "FR:urban",
"oneway": "yes",
@ -762,6 +788,7 @@
"tags": {
"cycleway:right": "lane",
"highway": "primary",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"oneway": "yes",
@ -812,6 +839,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -848,6 +876,7 @@
],
"tags": {
"highway": "secondary",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"ref": "D 131",
@ -3062,6 +3091,7 @@
],
"tags": {
"highway": "residential",
"lane_markings": "no",
"name": "Chemin de Fontenay",
"surface": "asphalt"
}
@ -7383,6 +7413,7 @@
"tags": {
"access": "yes",
"highway": "tertiary",
"lanes": "2",
"name": "Rue Marcel Quinet",
"oneway": "no",
"surface": "asphalt"
@ -7434,6 +7465,7 @@
"tags": {
"destination": "Limours;Briis sous Forges",
"highway": "tertiary_link",
"lanes": "1",
"lit": "yes",
"oneway": "yes",
"surface": "asphalt"
@ -11902,6 +11934,7 @@
],
"tags": {
"highway": "residential",
"lane_markings": "no",
"name": "Chemin Derrière les Murs",
"surface": "asphalt"
}
@ -14150,7 +14183,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "asphalt"
}
},
{
@ -14241,6 +14275,7 @@
"amenity": "parking",
"capacity": "5",
"capacity:disabled": "1",
"fee": "no",
"parking": "surface"
}
},
@ -14982,6 +15017,7 @@
"tags": {
"bridge": "yes",
"highway": "tertiary",
"lanes": "2",
"layer": "1",
"maxweight": "3.5",
"name": "Rue André Piquet",
@ -21358,6 +21394,7 @@
"tags": {
"destination": "Gometz la Ville;Briis-Centre",
"highway": "tertiary_link",
"lanes": "1",
"lit": "yes",
"oneway": "yes",
"surface": "asphalt"
@ -21395,6 +21432,7 @@
"cycleway:both": "lane",
"cycleway:both:lane": "exclusive",
"highway": "primary",
"lanes": "2",
"maxspeed": "50",
"maxspeed:type": "sign",
"ref": "D 97",
@ -22951,6 +22989,7 @@
],
"tags": {
"highway": "residential",
"lane_markings": "no",
"lit": "yes",
"name": "Place de la Ferme",
"source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2014 + Bing",
@ -132977,6 +133016,7 @@
"tags": {
"access": "yes",
"highway": "tertiary",
"lane_markings": "no",
"maxspeed": "30",
"name": "Rue Marcel Quinet",
"oneway": "no",
@ -152513,7 +152553,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "sett"
}
},
{
@ -154049,7 +154090,8 @@
}
],
"tags": {
"highway": "footway"
"highway": "footway",
"surface": "asphalt"
}
},
{
@ -160835,6 +160877,7 @@
"highway": "service",
"layer": "-1",
"maxheight": "2.5",
"surface": "dirt",
"tunnel": "building_passage"
}
},
@ -161947,6 +161990,7 @@
],
"tags": {
"highway": "tertiary",
"lanes": "1",
"lit": "yes",
"name": "Rue Marcel Quinet",
"oneway": "yes",
@ -162402,6 +162446,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"parking": "street_side"
}
},
@ -162456,6 +162501,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"parking": "street_side"
}
},
@ -163240,7 +163286,8 @@
],
"tags": {
"highway": "service",
"service": "parking_aisle"
"service": "parking_aisle",
"surface": "asphalt"
}
},
{
@ -163883,7 +163930,8 @@
}
],
"tags": {
"highway": "footway"
"highway": "footway",
"surface": "concrete"
}
},
{
@ -164876,6 +164924,7 @@
],
"tags": {
"highway": "residential",
"lanes": "1",
"name": "Rue des Écoles",
"oneway": "yes",
"ref:FR:FANTOIR": "911110120B",
@ -164946,7 +164995,8 @@
"lit": "yes",
"name": "Rue de la Fontaine de Ville",
"ref": "D 131",
"source": "Route500"
"source": "Route500",
"surface": "asphalt"
}
},
{
@ -165063,7 +165113,8 @@
"tags": {
"highway": "secondary",
"oneway": "yes",
"ref": "D 131"
"ref": "D 131",
"surface": "asphalt"
}
},
{
@ -166508,7 +166559,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "asphalt"
}
},
{
@ -166963,7 +167015,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "asphalt"
}
},
{
@ -169609,7 +169662,8 @@
"maxspeed": "30",
"name": "Rue de la Fontaine de Ville",
"ref": "D 131",
"source": "Route500"
"source": "Route500",
"surface": "asphalt"
}
},
{
@ -171220,7 +171274,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "paving_stones"
}
},
{
@ -171272,7 +171327,8 @@
}
],
"tags": {
"highway": "service"
"highway": "service",
"surface": "paving_stones"
}
},
{
@ -174065,7 +174121,8 @@
"name": "Rue de la Fontaine de Ville",
"oneway": "yes",
"ref": "D 131",
"source": "Route500"
"source": "Route500",
"surface": "asphalt"
}
},
{
@ -176306,6 +176363,7 @@
],
"tags": {
"highway": "tertiary_link",
"lanes": "1",
"lit": "yes",
"oneway": "yes",
"surface": "asphalt"
@ -178255,6 +178313,7 @@
"tags": {
"cycleway:right": "separate",
"highway": "primary",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"oneway": "yes",
@ -178398,6 +178457,7 @@
"cycleway:right": "separate",
"destination": "Arpajon;Fontenay-les-Briis;Hôpital de Bligny",
"highway": "primary",
"lanes": "1",
"lit": "yes",
"maxspeed:type": "FR:urban",
"oneway": "yes",
@ -178442,6 +178502,7 @@
"tags": {
"cycleway:right": "separate",
"highway": "primary",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"oneway": "yes",
@ -178580,6 +178641,7 @@
"cycleway:right": "separate",
"destination": "Limours;Forges-les-Bains",
"highway": "primary",
"lanes": "1",
"maxspeed:type": "FR:rural",
"oneway": "yes",
"ref": "D 97",
@ -178720,7 +178782,8 @@
"tags": {
"highway": "secondary",
"oneway": "yes",
"ref": "D 131"
"ref": "D 131",
"surface": "asphalt"
}
},
{
@ -178763,7 +178826,8 @@
"name": "Rue de la Fontaine de Ville",
"oneway": "yes",
"ref": "D 131",
"source": "Route500"
"source": "Route500",
"surface": "asphalt"
}
},
{
@ -180345,15 +180409,12 @@
"type": "way",
"id": 1158192132,
"bounds": {
"minlat": 48.6225876,
"minlat": 48.6227456,
"minlon": 2.1314359,
"maxlat": 48.6228562,
"maxlon": 2.1329391
"maxlon": 2.1318718
},
"nodes": [
10770983277,
10770983289,
10770983290,
6468287427,
10770983291,
10770983292,
@ -180362,18 +180423,6 @@
1355424050
],
"geometry": [
{
"lat": 48.6225876,
"lon": 2.1329391
},
{
"lat": 48.6226315,
"lon": 2.1325837
},
{
"lat": 48.6227772,
"lon": 2.1319269
},
{
"lat": 48.622771,
"lon": 2.1318718
@ -180400,7 +180449,8 @@
}
],
"tags": {
"highway": "footway"
"highway": "footway",
"surface": "dirt"
}
},
{
@ -180966,6 +181016,7 @@
"tags": {
"destination": "Gometz la Ville;Briis-Centre",
"highway": "tertiary_link",
"lanes": "1",
"lit": "yes",
"oneway": "yes",
"surface": "asphalt"
@ -181082,6 +181133,7 @@
"tags": {
"access": "yes",
"highway": "tertiary",
"lane_markings": "no",
"name": "Rue Marcel Quinet",
"oneway": "no",
"surface": "asphalt"
@ -181650,6 +181702,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181695,6 +181748,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181731,7 +181785,8 @@
"crossing": "uncontrolled",
"crossing:markings": "surface",
"footway": "crossing",
"highway": "footway"
"highway": "footway",
"surface": "paving_stones"
}
},
{
@ -181775,6 +181830,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181821,6 +181877,7 @@
"access": "yes",
"amenity": "parking",
"capacity": "4",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181866,6 +181923,7 @@
"tags": {
"access": "yes",
"amenity": "parking",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181912,6 +181970,7 @@
"access": "yes",
"amenity": "parking",
"capacity": "2",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -181958,6 +182017,7 @@
"access": "yes",
"amenity": "parking",
"capacity": "1",
"fee": "no",
"orientation": "parallel",
"parking": "street_side"
}
@ -182173,6 +182233,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182216,6 +182277,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182533,6 +182595,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182576,6 +182639,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182614,6 +182678,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182657,6 +182722,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lane_markings": "no",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -182705,6 +182771,7 @@
"cycleway:right": "lane",
"highway": "primary",
"junction": "roundabout",
"lanes": "1",
"maxspeed": "50",
"maxspeed:type": "sign",
"source": "Route500",
@ -185976,6 +186043,87 @@
"tags": {
"building": "yes"
}
},
{
"type": "way",
"id": 1361127227,
"bounds": {
"minlat": 48.6188775,
"minlon": 2.1266103,
"maxlat": 48.6189258,
"maxlon": 2.1267087
},
"nodes": [
12600445763,
12600445764,
12600445765,
12600445766,
12600445763
],
"geometry": [
{
"lat": 48.6189258,
"lon": 2.1266266
},
{
"lat": 48.6189023,
"lon": 2.1266103
},
{
"lat": 48.6188775,
"lon": 2.1266925
},
{
"lat": 48.618901,
"lon": 2.1267087
},
{
"lat": 48.6189258,
"lon": 2.1266266
}
],
"tags": {
"amenity": "bicycle_parking",
"bicycle_parking": "shed",
"building": "yes"
}
},
{
"type": "way",
"id": 1366754121,
"bounds": {
"minlat": 48.6225876,
"minlon": 2.1318718,
"maxlat": 48.6227772,
"maxlon": 2.1329391
},
"nodes": [
10770983277,
10770983289,
10770983290,
6468287427
],
"geometry": [
{
"lat": 48.6225876,
"lon": 2.1329391
},
{
"lat": 48.6226315,
"lon": 2.1325837
},
{
"lat": 48.6227772,
"lon": 2.1319269
},
{
"lat": 48.622771,
"lon": 2.1318718
}
],
"tags": {
"highway": "footway"
}
}
]
}

View file

@ -1,38 +1,110 @@
import json
import os
import sys
from jinja2 import Environment, FileSystemLoader
def generate_html_from_json(json_file, output_html):
with open(json_file, 'r') as file:
data = json.load(file)
def generate_html_from_json(json_file, output_html, template_file='template.html'):
"""
Génère une page HTML à partir d'un fichier JSON contenant les données de décompte
pour une ville donnée en utilisant un template Jinja2.
Args:
json_file (str): Chemin vers le fichier JSON contenant les données
output_html (str): Chemin sauvegarder le fichier HTML généré
template_file (str): Nom du fichier template à utiliser (par défaut: template.html)
"""
# Vérifier si le fichier JSON existe
if not os.path.exists(json_file):
print(f"Erreur: Le fichier {json_file} n'existe pas.")
return False
try:
with open(json_file, 'r', encoding='utf-8') as file:
data = json.load(file)
except json.JSONDecodeError:
print(f"Erreur: Le fichier {json_file} n'est pas un JSON valide.")
return False
except Exception as e:
print(f"Erreur lors de la lecture du fichier: {str(e)}")
return False
# Configuration de Jinja2
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('template.html')
# Vérifier si le template existe
if not os.path.exists(template_file):
print(f"Erreur: Le fichier template {template_file} n'existe pas.")
return False
try:
template = env.get_template(template_file)
except Exception as e:
print(f"Erreur lors du chargement du template: {str(e)}")
return False
# Calculer quelques statistiques supplémentaires
ratio_cyclable = data["road_cycleway_km"] / data["longueur_route_km"] * 100 if data["longueur_route_km"] > 0 else 0
ratio_parking_surface = data["surface_parking_km2"] / data["surface_route_km2"] * 100 if data["surface_route_km2"] > 0 else 0
# Rendu du template avec les données
html_content = template.render(
city_name=data.get("city_name", "la ville"),
longueur_route_km=data["longueur_route_km"],
road_cycleway_km=data["road_cycleway_km"],
compte_highways=data["compte_highways"],
surface_route_km2=data["surface_route_km2"],
compte_piste_cyclable=data["compte_piste_cyclable"],
roundabout_count=data["roundabout_count"],
mini_roundabout_count=data["mini_roundabout_count"],
building_count=data["building_count"],
building_area=data["building_area"],
surface_parking_km2=data["surface_parking_km2"],
surface_bicycle_parking_km2=data["surface_bicycle_parking_km2"],
car_parking_capacity_provided=data["car_parking_capacity_provided"],
building_size_counts=data["building_size_counts"],
charging_stations=data["charging_stations"],
charging_stations_with_capacity_count=data["charging_stations_with_capacity_count"],
charging_stations_capacity_provided=data["charging_stations_capacity_provided"],
charging_points=data["charging_points"]
)
try:
html_content = template.render(
city_name=data.get("city_name", "la ville"),
longueur_route_km=data["longueur_route_km"],
road_cycleway_km=data["road_cycleway_km"],
compte_highways=data["compte_highways"],
surface_route_km2=data["surface_route_km2"],
compte_piste_cyclable=data["compte_piste_cyclable"],
roundabout_count=data["roundabout_count"],
mini_roundabout_count=data["mini_roundabout_count"],
building_count=data["building_count"],
building_area=data["building_area"],
surface_parking_km2=data["surface_parking_km2"],
surface_bicycle_parking_km2=data["surface_bicycle_parking_km2"],
car_parking_capacity_provided=data["car_parking_capacity_provided"],
building_size_counts=data["building_size_counts"],
charging_stations=data["charging_stations"],
charging_stations_with_capacity_count=data["charging_stations_with_capacity_count"],
charging_stations_capacity_provided=data["charging_stations_capacity_provided"],
charging_points=data["charging_points"],
# Statistiques supplémentaires
ratio_cyclable=ratio_cyclable,
ratio_parking_surface=ratio_parking_surface,
date_generation=data.get("date_generation", "Non spécifiée")
)
except KeyError as e:
print(f"Erreur: Clé manquante dans les données JSON: {str(e)}")
return False
except Exception as e:
print(f"Erreur lors du rendu du template: {str(e)}")
return False
with open(output_html, 'w') as html_file:
html_file.write(html_content)
try:
with open(output_html, 'w', encoding='utf-8') as html_file:
html_file.write(html_content)
print(f"Le fichier HTML a été généré avec succès: {output_html}")
return True
except Exception as e:
print(f"Erreur lors de l'écriture du fichier HTML: {str(e)}")
return False
def main():
"""
Fonction principale qui traite les arguments de ligne de commande
et génère le fichier HTML.
"""
if len(sys.argv) < 2:
print("Usage: python present.py <fichier_json> [fichier_html_sortie] [fichier_template]")
print(" <fichier_json>: Chemin vers le fichier JSON contenant les données")
print(" [fichier_html_sortie]: Chemin où sauvegarder le fichier HTML (par défaut: resultat.html)")
print(" [fichier_template]: Nom du fichier template à utiliser (par défaut: template.html)")
return
json_file = sys.argv[1]
output_html = sys.argv[2] if len(sys.argv) > 2 else "resultat.html"
template_file = sys.argv[3] if len(sys.argv) > 3 else "template.html"
generate_html_from_json(json_file, output_html, template_file)
if __name__ == "__main__":
generate_html_from_json('summary_results.json', 'summary_results.html')
main()

11
requirements.txt Normal file
View file

@ -0,0 +1,11 @@
jinja2>=3.0.0
osmnx>=1.3.0
matplotlib>=3.5.0
networkx>=2.8.0
geopandas>=0.12.0
shapely>=2.0.0
folium>=0.14.0
requests>=2.28.0
selenium>=4.1.0
imgkit>=1.2.2
playwright>=1.30.0

139
resultat_template.html Normal file
View file

@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Résumé des résultats</title>
<style>
body {
font-family: Arial, sans-serif;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
td:nth-of-type(2) {
text-align: right;
}
@media (max-width: 600px) {
table {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>Résumé des résultats pour Ville Test</h1>
<table>
<tr>
<th>Mesure</th>
<th>Valeur</th>
</tr>
<tr>
<td>Longueur de route (km) 🛣️</td>
<td>228.33</td>
</tr>
<tr>
<td>Longueur de piste cyclable (km) 🚴‍♂️</td>
<td>12.50</td>
</tr>
<tr>
<td>Nombre de routes 🚗</td>
<td>3530</td>
</tr>
<tr>
<td>Surface des routes (km²) 🌍</td>
<td>1.59</td>
</tr>
<tr>
<td>Nombre de pistes cyclables 🚲</td>
<td>42</td>
</tr>
<tr>
<td>Nombre de ronds-points ⭕</td>
<td>12</td>
</tr>
<tr>
<td>Nombre de mini ronds-points 🔵</td>
<td>5</td>
</tr>
<tr>
<td>Surface des parkings (km²) 🅿️</td>
<td>0.35</td>
</tr>
<tr>
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
<td>0.04</td>
</tr>
<tr>
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
<td>491</td>
</tr>
<tr>
<td>Nombre de bâtiments 🏢</td>
<td>2516</td>
</tr>
<tr>
<td>Aire des bâtiments (m²) 📏</td>
<td>2.65</td>
</tr>
<tr>
<td>Nombre de bâtiments de moins de 10 m² 🏠</td>
<td>120</td>
</tr>
<tr>
<td>Nombre de bâtiments de 10 à 50 m² 🏡</td>
<td>450</td>
</tr>
<tr>
<td>Nombre de bâtiments de 50 à 100 m² 🏢</td>
<td>780</td>
</tr>
<tr>
<td>Nombre de bâtiments de 100 à 200 m² 🏬</td>
<td>650</td>
</tr>
<tr>
<td>Nombre de bâtiments de 200 à 500 m² 🏣</td>
<td>400</td>
</tr>
<tr>
<td>Nombre de bâtiments de plus de 500 m² 🏛️</td>
<td>116</td>
</tr>
<tr>
<td>Nombre de stations de recharge ⚡</td>
<td>14</td>
</tr>
<tr>
<td>Nombre de stations de recharge avec capacité renseignée ⚡💼</td>
<td>12</td>
</tr>
<tr>
<td>Capacité totale des stations de recharge ⚡📦</td>
<td>28</td>
</tr>
<tr>
<td>Nombre de points de charge 🔌</td>
<td>32</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,408 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analyse urbaine - Ville Test</title>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--accent-color: #e74c3c;
--text-color: #333;
--light-bg: #f9f9f9;
--card-bg: #fff;
--border-color: #ddd;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--light-bg);
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
padding: 20px 0;
text-align: center;
margin-bottom: 30px;
border-radius: 0 0 10px 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
margin: 0;
font-size: 2.5em;
}
.subtitle {
font-style: italic;
margin-top: 10px;
opacity: 0.9;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background-color: var(--card-bg);
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.card h2 {
color: var(--primary-color);
margin-top: 0;
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
font-size: 1.5em;
}
.stat-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-label {
font-size: 0.9em;
color: #666;
}
.stat-value {
font-size: 1.4em;
font-weight: bold;
color: var(--primary-color);
}
.highlight {
color: var(--accent-color);
}
.positive {
color: var(--secondary-color);
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th,
td {
border: 1px solid var(--border-color);
padding: 12px;
text-align: left;
}
th {
background-color: var(--primary-color);
color: white;
}
tr:nth-child(even) {
background-color: rgba(0, 0, 0, 0.02);
}
.progress-container {
width: 100%;
background-color: #e0e0e0;
border-radius: 5px;
margin: 10px 0;
}
.progress-bar {
height: 10px;
border-radius: 5px;
background-color: var(--secondary-color);
}
.ratio-bad {
background-color: var(--accent-color);
}
.ratio-medium {
background-color: #f39c12;
}
.ratio-good {
background-color: var(--secondary-color);
}
footer {
text-align: center;
margin-top: 40px;
padding: 20px;
background-color: var(--primary-color);
color: white;
border-radius: 10px 10px 0 0;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
.stat-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>Analyse urbaine de Ville Test</h1>
<p class="subtitle">Données d'infrastructure et d'aménagement urbain</p>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Carte des routes -->
<div class="card">
<h2>🛣️ Réseau routier</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Longueur totale</span>
<span class="stat-value">228.33 km</span>
</div>
<div class="stat-item">
<span class="stat-label">Nombre de routes</span>
<span class="stat-value">3530</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface totale</span>
<span class="stat-value">1.59 km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Ronds-points</span>
<span class="stat-value">17</span>
</div>
</div>
</div>
<!-- Carte des pistes cyclables -->
<div class="card">
<h2>🚴‍♂️ Mobilité douce</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Longueur pistes cyclables</span>
<span class="stat-value">12.50 km</span>
</div>
<div class="stat-item">
<span class="stat-label">Nombre de pistes</span>
<span class="stat-value">42</span>
</div>
</div>
<p>Ratio cyclable/routier:</p>
<div class="progress-container">
<div
class="progress-bar ratio-medium"
style='width: 5.47 %'></div>
</div>
<p>5.47% du réseau routier</p>
</div>
<!-- Carte des parkings -->
<div class="card">
<h2>🅿️ Stationnement</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Surface parkings voiture</span>
<span class="stat-value">0.35 km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Capacité renseignée</span>
<span class="stat-value">491 places</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface parkings vélo</span>
<span class="stat-value">0.04200 km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Ratio parking/route</span>
<span class="stat-value ">22.01%</span>
</div>
</div>
</div>
<!-- Carte des bâtiments -->
<div class="card">
<h2>🏢 Bâtiments</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Nombre total</span>
<span class="stat-value">2516</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface totale</span>
<span class="stat-value">2.65 km²</span>
</div>
</div>
<table>
<tr>
<th>Taille</th>
<th>Nombre</th>
</tr>
<tr>
<td>
< 10 m²</td>
<td>120</td>
</tr>
<tr>
<td>10-50 m²</td>
<td>450</td>
</tr>
<tr>
<td>50-100 m²</td>
<td>780</td>
</tr>
<tr>
<td>100-200 m²</td>
<td>650</td>
</tr>
<tr>
<td>200-500 m²</td>
<td>400</td>
</tr>
<tr>
<td>> 500 m²</td>
<td>116</td>
</tr>
</table>
</div>
<!-- Carte des bornes de recharge -->
<div class="card">
<h2>⚡ Bornes de recharge</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Nombre de stations</span>
<span class="stat-value">14</span>
</div>
<div class="stat-item">
<span class="stat-label">Capacité totale</span>
<span class="stat-value">28</span>
</div>
<div class="stat-item">
<span class="stat-label">Points de charge</span>
<span class="stat-value">32</span>
</div>
<div class="stat-item">
<span class="stat-label">Stations avec capacité</span>
<span class="stat-value">12</span>
</div>
</div>
</div>
</div>
<div class="card">
<h2>📊 Tableau récapitulatif</h2>
<table>
<tr>
<th>Mesure</th>
<th>Valeur</th>
</tr>
<tr>
<td>Longueur de route (km) 🛣️</td>
<td>228.33</td>
</tr>
<tr>
<td>Longueur de piste cyclable (km) 🚴‍♂️</td>
<td>12.50</td>
</tr>
<tr>
<td>Nombre de routes 🚗</td>
<td>3530</td>
</tr>
<tr>
<td>Surface des routes (km²) 🌍</td>
<td>1.59</td>
</tr>
<tr>
<td>Nombre de pistes cyclables 🚲</td>
<td>42</td>
</tr>
<tr>
<td>Nombre de ronds-points ⭕</td>
<td>12</td>
</tr>
<tr>
<td>Nombre de mini ronds-points 🔵</td>
<td>5</td>
</tr>
<tr>
<td>Surface des parkings (km²) 🅿️</td>
<td>0.35</td>
</tr>
<tr>
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
<td>0.04200</td>
</tr>
<tr>
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
<td>491</td>
</tr>
<tr>
<td>Nombre de bâtiments 🏢</td>
<td>2516</td>
</tr>
<tr>
<td>Aire des bâtiments (km²) 📏</td>
<td>2.65000</td>
</tr>
<tr>
<td>Nombre de stations de recharge ⚡</td>
<td>14</td>
</tr>
<tr>
<td>Capacité totale des stations de recharge ⚡📦</td>
<td>28</td>
</tr>
<tr>
<td>Nombre de points de charge 🔌</td>
<td>32</td>
</tr>
</table>
</div>
</div>
<footer>
<div class="container">
<p>Rapport généré le 16/03/2025 12:35:11</p>
<p>Analyse des infrastructures urbaines et de mobilité</p>
</div>
</footer>
</body>
</html>

335
simple_map.py Normal file
View file

@ -0,0 +1,335 @@
#!/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_bbox(bbox):
"""
Récupère les routes, bâtiments et parkings dans une boîte englobante.
Args:
bbox (tuple): (min_lat, min_lon, max_lat, max_lon)
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
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;
"""
# 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 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
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
elements_data = get_elements_in_bbox(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
print("Création de la carte...")
m = folium.Map(location=center, zoom_start=14, tiles='OpenStreetMap')
# 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()

View file

@ -1,10 +1,10 @@
{
"longueur_route_km": 228.30878348424707,
"longueur_route_km": 228.33392384008508,
"road_cycleway_km": 0.027059960352724084,
"compte_highways": 3526,
"surface_route_km2": 1.5981614843897296,
"building_count": 2515,
"building_area": 2.6535844764989233e-08,
"compte_highways": 3530,
"surface_route_km2": 1.5983374668805956,
"building_count": 2516,
"building_area": 2.6538178289989005e-08,
"building_sizes": [
0,
10,
@ -16,7 +16,7 @@
],
"building_size_counts": [
0,
2515,
2516,
0,
0,
0,
@ -28,10 +28,10 @@
"roundabout_count": 0,
"mini_roundabout_count": 0,
"surface_parking_km2": 9820.0,
"surface_bicycle_parking_km2": 3.6e-05,
"parking_with_capacity_count": 41,
"capacity_bicycle_parking_provided": 115,
"bicycle_parking_surface_from_capacity_provided_in_m2": 230,
"surface_bicycle_parking_km2": 4.2e-05,
"parking_with_capacity_count": 43,
"capacity_bicycle_parking_provided": 125,
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
"charging_stations": 4,
"charging_stations_with_capacity_count": 4,
"charging_stations_capacity_provided": 7,

409
template_ameliore.html.j2 Normal file
View file

@ -0,0 +1,409 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analyse urbaine - {{ city_name }}</title>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--accent-color: #e74c3c;
--text-color: #333;
--light-bg: #f9f9f9;
--card-bg: #fff;
--border-color: #ddd;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--light-bg);
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--primary-color);
color: white;
padding: 20px 0;
text-align: center;
margin-bottom: 30px;
border-radius: 0 0 10px 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
margin: 0;
font-size: 2.5em;
}
.subtitle {
font-style: italic;
margin-top: 10px;
opacity: 0.9;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background-color: var(--card-bg);
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.card h2 {
color: var(--primary-color);
margin-top: 0;
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
font-size: 1.5em;
}
.stat-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-label {
font-size: 0.9em;
color: #666;
}
.stat-value {
font-size: 1.4em;
font-weight: bold;
color: var(--primary-color);
}
.highlight {
color: var(--accent-color);
}
.positive {
color: var(--secondary-color);
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th,
td {
border: 1px solid var(--border-color);
padding: 12px;
text-align: left;
}
th {
background-color: var(--primary-color);
color: white;
}
tr:nth-child(even) {
background-color: rgba(0, 0, 0, 0.02);
}
.progress-container {
width: 100%;
background-color: #e0e0e0;
border-radius: 5px;
margin: 10px 0;
}
.progress-bar {
height: 10px;
border-radius: 5px;
background-color: var(--secondary-color);
}
.ratio-bad {
background-color: var(--accent-color);
}
.ratio-medium {
background-color: #f39c12;
}
.ratio-good {
background-color: var(--secondary-color);
}
footer {
text-align: center;
margin-top: 40px;
padding: 20px;
background-color: var(--primary-color);
color: white;
border-radius: 10px 10px 0 0;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
.stat-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>Analyse urbaine de {{ city_name }}</h1>
<p class="subtitle">Données d'infrastructure et d'aménagement urbain</p>
</div>
</header>
<div class="container">
<div class="dashboard">
<!-- Carte des routes -->
<div class="card">
<h2>🛣️ Réseau routier</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Longueur totale</span>
<span class="stat-value">{{ "%.2f"|format(longueur_route_km) }} km</span>
</div>
<div class="stat-item">
<span class="stat-label">Nombre de routes</span>
<span class="stat-value">{{ compte_highways }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface totale</span>
<span class="stat-value">{{ "%.2f"|format(surface_route_km2) }} km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Ronds-points</span>
<span class="stat-value">{{ roundabout_count + mini_roundabout_count }}</span>
</div>
</div>
</div>
<!-- Carte des pistes cyclables -->
<div class="card">
<h2>🚴‍♂️ Mobilité douce</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Longueur pistes cyclables</span>
<span class="stat-value">{{ "%.2f"|format(road_cycleway_km) }} km</span>
</div>
<div class="stat-item">
<span class="stat-label">Nombre de pistes</span>
<span class="stat-value">{{ compte_piste_cyclable }}</span>
</div>
</div>
<p>Ratio cyclable/routier:</p>
<div class="progress-container">
<div
class="progress-bar {% if ratio_cyclable < 5 %}ratio-bad{% elif ratio_cyclable < 15 %}ratio-medium{% else %}ratio-good{% endif %}"
style='width: {{ "%.2f"|format(ratio_cyclable) }} %'></div>
</div>
<p>{{ "%.2f"|format(ratio_cyclable) }}% du réseau routier</p>
</div>
<!-- Carte des parkings -->
<div class="card">
<h2>🅿️ Stationnement</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Surface parkings voiture</span>
<span class="stat-value">{{ "%.2f"|format(surface_parking_km2) }} km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Capacité renseignée</span>
<span class="stat-value">{{ car_parking_capacity_provided }} places</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface parkings vélo</span>
<span class="stat-value">{{ "%.5f"|format(surface_bicycle_parking_km2) }} km²</span>
</div>
<div class="stat-item">
<span class="stat-label">Ratio parking/route</span>
<span class="stat-value {% if ratio_parking_surface > 50 %}highlight{% endif %}">{{
"%.2f"|format(ratio_parking_surface) }}%</span>
</div>
</div>
</div>
<!-- Carte des bâtiments -->
<div class="card">
<h2>🏢 Bâtiments</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Nombre total</span>
<span class="stat-value">{{ building_count }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Surface totale</span>
<span class="stat-value">{{ "%.2f"|format(building_area) }} km²</span>
</div>
</div>
<table>
<tr>
<th>Taille</th>
<th>Nombre</th>
</tr>
<tr>
<td>
< 10 m²</td>
<td>{{ building_size_counts[0] }}</td>
</tr>
<tr>
<td>10-50 m²</td>
<td>{{ building_size_counts[1] }}</td>
</tr>
<tr>
<td>50-100 m²</td>
<td>{{ building_size_counts[2] }}</td>
</tr>
<tr>
<td>100-200 m²</td>
<td>{{ building_size_counts[3] }}</td>
</tr>
<tr>
<td>200-500 m²</td>
<td>{{ building_size_counts[4] }}</td>
</tr>
<tr>
<td>> 500 m²</td>
<td>{{ building_size_counts[5] }}</td>
</tr>
</table>
</div>
<!-- Carte des bornes de recharge -->
<div class="card">
<h2>⚡ Bornes de recharge</h2>
<div class="stat-grid">
<div class="stat-item">
<span class="stat-label">Nombre de stations</span>
<span class="stat-value">{{ charging_stations }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Capacité totale</span>
<span class="stat-value">{{ charging_stations_capacity_provided }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Points de charge</span>
<span class="stat-value">{{ charging_points }}</span>
</div>
<div class="stat-item">
<span class="stat-label">Stations avec capacité</span>
<span class="stat-value">{{ charging_stations_with_capacity_count }}</span>
</div>
</div>
</div>
</div>
<div class="card">
<h2>📊 Tableau récapitulatif</h2>
<table>
<tr>
<th>Mesure</th>
<th>Valeur</th>
</tr>
<tr>
<td>Longueur de route (km) 🛣️</td>
<td>{{ "%.2f"|format(longueur_route_km) }}</td>
</tr>
<tr>
<td>Longueur de piste cyclable (km) 🚴‍♂️</td>
<td>{{ "%.2f"|format(road_cycleway_km) }}</td>
</tr>
<tr>
<td>Nombre de routes 🚗</td>
<td>{{ compte_highways }}</td>
</tr>
<tr>
<td>Surface des routes (km²) 🌍</td>
<td>{{ "%.2f"|format(surface_route_km2) }}</td>
</tr>
<tr>
<td>Nombre de pistes cyclables 🚲</td>
<td>{{ compte_piste_cyclable }}</td>
</tr>
<tr>
<td>Nombre de ronds-points ⭕</td>
<td>{{ roundabout_count }}</td>
</tr>
<tr>
<td>Nombre de mini ronds-points 🔵</td>
<td>{{ mini_roundabout_count }}</td>
</tr>
<tr>
<td>Surface des parkings (km²) 🅿️</td>
<td>{{ "%.2f"|format(surface_parking_km2) }}</td>
</tr>
<tr>
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
<td>{{ "%.5f"|format(surface_bicycle_parking_km2) }}</td>
</tr>
<tr>
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
<td>{{ car_parking_capacity_provided }}</td>
</tr>
<tr>
<td>Nombre de bâtiments 🏢</td>
<td>{{ building_count }}</td>
</tr>
<tr>
<td>Aire des bâtiments (km²) 📏</td>
<td>{{ "%.5f"|format(building_area) }}</td>
</tr>
<tr>
<td>Nombre de stations de recharge ⚡</td>
<td>{{ charging_stations }}</td>
</tr>
<tr>
<td>Capacité totale des stations de recharge ⚡📦</td>
<td>{{ charging_stations_capacity_provided }}</td>
</tr>
<tr>
<td>Nombre de points de charge 🔌</td>
<td>{{ charging_points }}</td>
</tr>
</table>
</div>
</div>
<footer>
<div class="container">
<p>Rapport généré le {{ date_generation }}</p>
<p>Analyse des infrastructures urbaines et de mobilité</p>
</div>
</footer>
</body>
</html>

40
test_data.json Normal file
View file

@ -0,0 +1,40 @@
{
"city_name": "Ville Test",
"longueur_route_km": 228.33,
"road_cycleway_km": 12.5,
"compte_highways": 3530,
"surface_route_km2": 1.59,
"building_count": 2516,
"building_area": 2.65,
"building_sizes": [
0,
10,
50,
100,
200,
500,
Infinity
],
"building_size_counts": [
120,
450,
780,
650,
400,
116
],
"compte_piste_cyclable": 42,
"car_parking_capacity_provided": 491,
"roundabout_count": 12,
"mini_roundabout_count": 5,
"surface_parking_km2": 0.35,
"surface_bicycle_parking_km2": 0.042,
"parking_with_capacity_count": 43,
"capacity_bicycle_parking_provided": 125,
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
"charging_stations": 14,
"charging_stations_with_capacity_count": 12,
"charging_stations_capacity_provided": 28,
"charging_points": 32,
"date_generation": "16/03/2025 12:35:11"
}

74
test_present.py Normal file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script de test pour vérifier le bon fonctionnement de la génération de rapport HTML
à partir des données JSON de décompte urbain.
"""
import os
import json
import datetime
from present import generate_html_from_json
def create_test_data():
"""Crée un fichier JSON de test avec des données fictives."""
test_data = {
"city_name": "Ville Test",
"longueur_route_km": 228.33,
"road_cycleway_km": 12.5,
"compte_highways": 3530,
"surface_route_km2": 1.59,
"building_count": 2516,
"building_area": 2.65,
"building_sizes": [0, 10, 50, 100, 200, 500, float('inf')],
"building_size_counts": [120, 450, 780, 650, 400, 116],
"compte_piste_cyclable": 42,
"car_parking_capacity_provided": 491,
"roundabout_count": 12,
"mini_roundabout_count": 5,
"surface_parking_km2": 0.35,
"surface_bicycle_parking_km2": 0.042,
"parking_with_capacity_count": 43,
"capacity_bicycle_parking_provided": 125,
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
"charging_stations": 14,
"charging_stations_with_capacity_count": 12,
"charging_stations_capacity_provided": 28,
"charging_points": 32,
"date_generation": datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")
}
with open('test_data.json', 'w', encoding='utf-8') as f:
json.dump(test_data, f, indent=2, ensure_ascii=False)
return 'test_data.json'
def test_template(template_file):
"""Teste la génération HTML avec un template spécifique."""
test_json = create_test_data()
output_html = f"resultat_{os.path.splitext(template_file)[0]}.html"
success = generate_html_from_json(test_json, output_html, template_file)
if success:
print(f"✅ Test réussi avec {template_file} ! Fichier généré : {output_html}")
else:
print(f"❌ Échec du test avec {template_file}")
return success
def main():
"""Fonction principale qui exécute les tests."""
print("🧪 Démarrage des tests de génération de rapports HTML...")
# Test avec le template original
test_template('template.html')
# Test avec le template amélioré
test_template('template_ameliore.html.j2')
print("🏁 Tests terminés.")
if __name__ == "__main__":
main()