diff --git a/README.md b/README.md index 4e27019..3d327c9 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file +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_html_sortie] [fichier_template] +``` + +Arguments : +- `` : 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 [options] +``` + +Arguments : +- `` : 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 [-o output.jpg] + ``` + +2. Génération de carte avec Folium (plus léger) : + ```bash + python map_simple.py [-o output.html] + ``` + +3. Conversion d'une carte HTML en JPG : + ```bash + python html_to_jpg.py [-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. \ No newline at end of file diff --git a/__pycache__/html_to_jpg.cpython-311.pyc b/__pycache__/html_to_jpg.cpython-311.pyc new file mode 100644 index 0000000..d1aaad5 Binary files /dev/null and b/__pycache__/html_to_jpg.cpython-311.pyc differ diff --git a/__pycache__/map.cpython-311.pyc b/__pycache__/map.cpython-311.pyc new file mode 100644 index 0000000..fa8920b Binary files /dev/null and b/__pycache__/map.cpython-311.pyc differ diff --git a/__pycache__/map_simple.cpython-311.pyc b/__pycache__/map_simple.cpython-311.pyc new file mode 100644 index 0000000..b46ad15 Binary files /dev/null and b/__pycache__/map_simple.cpython-311.pyc differ diff --git a/__pycache__/present.cpython-311.pyc b/__pycache__/present.cpython-311.pyc new file mode 100644 index 0000000..b50de8e Binary files /dev/null and b/__pycache__/present.cpython-311.pyc differ diff --git a/carte_ville.html b/carte_ville.html new file mode 100644 index 0000000..be1f763 --- /dev/null +++ b/carte_ville.html @@ -0,0 +1,112988 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Carte de Briis-sous-Forges

+

ID OpenStreetMap: 1209165 (Type: relation)

+ + +
+

Statistiques

+

Routes: 2062

+

Bâtiments: 4828

+

Parkings: 163

+
+ + +
+ + + + \ No newline at end of file diff --git a/carte_ville.jpg b/carte_ville.jpg new file mode 100644 index 0000000..86f07a8 Binary files /dev/null and b/carte_ville.jpg differ diff --git a/generate_city_map.py b/generate_city_map.py new file mode 100644 index 0000000..3002d51 --- /dev/null +++ b/generate_city_map.py @@ -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 où 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() \ No newline at end of file diff --git a/html2jpg.py b/html2jpg.py new file mode 100644 index 0000000..6fe501d --- /dev/null +++ b/html2jpg.py @@ -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 où 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() \ No newline at end of file diff --git a/html_to_jpg.py b/html_to_jpg.py new file mode 100644 index 0000000..cd3079d --- /dev/null +++ b/html_to_jpg.py @@ -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 où 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() \ No newline at end of file diff --git a/map.py b/map.py new file mode 100644 index 0000000..0d6d814 --- /dev/null +++ b/map.py @@ -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 où 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() \ No newline at end of file diff --git a/map_simple.py b/map_simple.py new file mode 100644 index 0000000..25ba9d4 --- /dev/null +++ b/map_simple.py @@ -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 où sauvegarder l'image HTML. Si None, utilise le nom de la ville. + + Returns: + str: Chemin vers le fichier HTML généré + """ + if not city_data: + logger.error("Aucune donnée de ville fournie pour générer la carte") + return None + + try: + city_name = city_data['city_name'] + center = city_data['center'] + elements_data = city_data['elements_data'] + element_type = city_data.get('element_type', 'unknown') + + # Créer une carte centrée sur la ville + m = folium.Map(location=center, zoom_start=14, tiles='OpenStreetMap') + + # Ajouter un titre + title_html = f''' +

Carte de {city_name}

+

ID OpenStreetMap: {city_data.get('city_data', {}).get('elements', [{}])[0].get('id', 'N/A')} (Type: {element_type})

+ ''' + m.get_root().html.add_child(folium.Element(title_html)) + + # Ajouter un marqueur pour le centre + folium.Marker( + location=center, + popup=f"Centre de {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''' +
+

Statistiques

+

Routes: {highways_count}

+

Bâtiments: {buildings_count}

+

Parkings: {parkings_count}

+
+ ''' + m.get_root().html.add_child(folium.Element(legend_html)) + + # Définir le chemin de sortie + if not output_path: + output_path = f"{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() \ No newline at end of file diff --git a/overpass_data.json b/overpass_data.json index 008d3a6..d36c71a 100644 --- a/overpass_data.json +++ b/overpass_data.json @@ -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" + } } ] } \ No newline at end of file diff --git a/present.py b/present.py index 7ce1b0f..06f80c8 100644 --- a/present.py +++ b/present.py @@ -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 où 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_html_sortie] [fichier_template]") + print(" : 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() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce18ea6 --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/resultat_template.html b/resultat_template.html new file mode 100644 index 0000000..43b9bf6 --- /dev/null +++ b/resultat_template.html @@ -0,0 +1,139 @@ + + + + + + + Résumé des résultats + + + + +

Résumé des résultats pour Ville Test

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MesureValeur
Longueur de route (km) 🛣️228.33
Longueur de piste cyclable (km) 🚴‍♂️12.50
Nombre de routes 🚗3530
Surface des routes (km²) 🌍1.59
Nombre de pistes cyclables 🚲42
Nombre de ronds-points ⭕12
Nombre de mini ronds-points 🔵5
Surface des parkings (km²) 🅿️0.35
Surface des parkings à vélos (km²) 🚲🅿️0.04
Nombre de parkings avec capacité renseignée 🚗💼491
Nombre de bâtiments 🏢2516
Aire des bâtiments (m²) 📏2.65
Nombre de bâtiments de moins de 10 m² 🏠120
Nombre de bâtiments de 10 à 50 m² 🏡450
Nombre de bâtiments de 50 à 100 m² 🏢780
Nombre de bâtiments de 100 à 200 m² 🏬650
Nombre de bâtiments de 200 à 500 m² 🏣400
Nombre de bâtiments de plus de 500 m² 🏛️116
Nombre de stations de recharge ⚡14
Nombre de stations de recharge avec capacité renseignée ⚡💼12
Capacité totale des stations de recharge ⚡📦28
Nombre de points de charge 🔌32
+ + + \ No newline at end of file diff --git a/resultat_template_ameliore.html.html b/resultat_template_ameliore.html.html new file mode 100644 index 0000000..a91ed51 --- /dev/null +++ b/resultat_template_ameliore.html.html @@ -0,0 +1,408 @@ + + + + + + + Analyse urbaine - Ville Test + + + + +
+
+

Analyse urbaine de Ville Test

+

Données d'infrastructure et d'aménagement urbain

+
+
+ +
+
+ +
+

🛣️ Réseau routier

+
+
+ Longueur totale + 228.33 km +
+
+ Nombre de routes + 3530 +
+
+ Surface totale + 1.59 km² +
+
+ Ronds-points + 17 +
+
+
+ + +
+

🚴‍♂️ Mobilité douce

+
+
+ Longueur pistes cyclables + 12.50 km +
+
+ Nombre de pistes + 42 +
+
+

Ratio cyclable/routier:

+
+
+
+

5.47% du réseau routier

+
+ + +
+

🅿️ Stationnement

+
+
+ Surface parkings voiture + 0.35 km² +
+
+ Capacité renseignée + 491 places +
+
+ Surface parkings vélo + 0.04200 km² +
+
+ Ratio parking/route + 22.01% +
+
+
+ + +
+

🏢 Bâtiments

+
+
+ Nombre total + 2516 +
+
+ Surface totale + 2.65 km² +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TailleNombre
+ < 10 m²120
10-50 m²450
50-100 m²780
100-200 m²650
200-500 m²400
> 500 m²116
+
+ + +
+

⚡ Bornes de recharge

+
+
+ Nombre de stations + 14 +
+
+ Capacité totale + 28 +
+
+ Points de charge + 32 +
+
+ Stations avec capacité + 12 +
+
+
+
+ +
+

📊 Tableau récapitulatif

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MesureValeur
Longueur de route (km) 🛣️228.33
Longueur de piste cyclable (km) 🚴‍♂️12.50
Nombre de routes 🚗3530
Surface des routes (km²) 🌍1.59
Nombre de pistes cyclables 🚲42
Nombre de ronds-points ⭕12
Nombre de mini ronds-points 🔵5
Surface des parkings (km²) 🅿️0.35
Surface des parkings à vélos (km²) 🚲🅿️0.04200
Nombre de parkings avec capacité renseignée 🚗💼491
Nombre de bâtiments 🏢2516
Aire des bâtiments (km²) 📏2.65000
Nombre de stations de recharge ⚡14
Capacité totale des stations de recharge ⚡📦28
Nombre de points de charge 🔌32
+
+
+ +
+
+

Rapport généré le 16/03/2025 12:35:11

+

Analyse des infrastructures urbaines et de mobilité

+
+
+ + + \ No newline at end of file diff --git a/simple_map.py b/simple_map.py new file mode 100644 index 0000000..b66ebf1 --- /dev/null +++ b/simple_map.py @@ -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 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 + 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''' +

Carte de {name}

+

ID OpenStreetMap: {osm_id} (Type: {element_type})

+ ''' + m.get_root().html.add_child(folium.Element(title_html)) + + # Ajouter un marqueur pour le centre + folium.Marker( + location=center, + popup=f"Centre de {name}", + icon=folium.Icon(color='red', icon='info-sign') + ).add_to(m) + + # Extraire les nœuds pour construire les géométries + nodes = {n['id']: (n['lat'], n['lon']) for n in elements_data['elements'] if n['type'] == 'node'} + + # Compter les éléments pour les statistiques + highways_count = 0 + buildings_count = 0 + parkings_count = 0 + + # Traiter les routes, bâtiments et parkings + for element in elements_data['elements']: + if element['type'] == 'way' and 'tags' in element: + # Récupérer les coordonnées des nœuds + coords = [] + for node_id in element['nodes']: + if node_id in nodes: + coords.append(nodes[node_id]) + + if not coords: + continue + + # Déterminer le type d'élément + if 'highway' in element['tags']: + # C'est une route + highways_count += 1 + folium.PolyLine( + coords, + color='#555555', + weight=2, + opacity=0.7, + tooltip=element['tags'].get('name', 'Route') + ).add_to(m) + + elif 'building' in element['tags']: + # C'est un bâtiment + buildings_count += 1 + folium.Polygon( + coords, + color='#777777', + fill=True, + fill_color='#777777', + fill_opacity=0.7, + tooltip=element['tags'].get('name', 'Bâtiment') + ).add_to(m) + + elif element['tags'].get('amenity') == 'parking': + # C'est un parking + parkings_count += 1 + folium.Polygon( + coords, + color='#999999', + fill=True, + fill_color='#999999', + fill_opacity=0.7, + tooltip=element['tags'].get('name', 'Parking') + ).add_to(m) + + # Ajouter une légende avec les statistiques + legend_html = f''' +
+

Statistiques

+

Routes: {highways_count}

+

Bâtiments: {buildings_count}

+

Parkings: {parkings_count}

+
+ ''' + m.get_root().html.add_child(folium.Element(legend_html)) + + # Définir le chemin de sortie + if not output_path: + output_path = f"{name.replace(' ', '_')}_map.html" + elif not output_path.lower().endswith('.html'): + output_path = f"{os.path.splitext(output_path)[0]}.html" + + # Sauvegarder la carte + print(f"Sauvegarde de la carte dans: {output_path}") + m.save(output_path) + + print(f"Carte générée avec succès: {output_path}") + print(f"Pour visualiser la carte, ouvrez le fichier HTML dans un navigateur web:") + print(f" file://{os.path.abspath(output_path)}") + + return output_path + +def main(): + """ + Fonction principale qui traite les arguments de ligne de commande + et génère la carte. + """ + parser = argparse.ArgumentParser(description='Génère une carte pour un élément OpenStreetMap.') + parser.add_argument('osm_id', type=int, help='ID OpenStreetMap') + parser.add_argument('-o', '--output', type=str, help='Chemin où sauvegarder la carte HTML') + + args = parser.parse_args() + + # Créer la carte + create_map(args.osm_id, args.output) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/summary_results.json b/summary_results.json index e11dd9a..d04548a 100644 --- a/summary_results.json +++ b/summary_results.json @@ -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, diff --git a/template.html b/template.html.j2 similarity index 100% rename from template.html rename to template.html.j2 diff --git a/template_ameliore.html.j2 b/template_ameliore.html.j2 new file mode 100644 index 0000000..33d6fd3 --- /dev/null +++ b/template_ameliore.html.j2 @@ -0,0 +1,409 @@ + + + + + + + Analyse urbaine - {{ city_name }} + + + + +
+
+

Analyse urbaine de {{ city_name }}

+

Données d'infrastructure et d'aménagement urbain

+
+
+ +
+
+ +
+

🛣️ Réseau routier

+
+
+ Longueur totale + {{ "%.2f"|format(longueur_route_km) }} km +
+
+ Nombre de routes + {{ compte_highways }} +
+
+ Surface totale + {{ "%.2f"|format(surface_route_km2) }} km² +
+
+ Ronds-points + {{ roundabout_count + mini_roundabout_count }} +
+
+
+ + +
+

🚴‍♂️ Mobilité douce

+
+
+ Longueur pistes cyclables + {{ "%.2f"|format(road_cycleway_km) }} km +
+
+ Nombre de pistes + {{ compte_piste_cyclable }} +
+
+

Ratio cyclable/routier:

+
+
+
+

{{ "%.2f"|format(ratio_cyclable) }}% du réseau routier

+
+ + +
+

🅿️ Stationnement

+
+
+ Surface parkings voiture + {{ "%.2f"|format(surface_parking_km2) }} km² +
+
+ Capacité renseignée + {{ car_parking_capacity_provided }} places +
+
+ Surface parkings vélo + {{ "%.5f"|format(surface_bicycle_parking_km2) }} km² +
+
+ Ratio parking/route + {{ + "%.2f"|format(ratio_parking_surface) }}% +
+
+
+ + +
+

🏢 Bâtiments

+
+
+ Nombre total + {{ building_count }} +
+
+ Surface totale + {{ "%.2f"|format(building_area) }} km² +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TailleNombre
+ < 10 m²{{ building_size_counts[0] }}
10-50 m²{{ building_size_counts[1] }}
50-100 m²{{ building_size_counts[2] }}
100-200 m²{{ building_size_counts[3] }}
200-500 m²{{ building_size_counts[4] }}
> 500 m²{{ building_size_counts[5] }}
+
+ + +
+

⚡ Bornes de recharge

+
+
+ Nombre de stations + {{ charging_stations }} +
+
+ Capacité totale + {{ charging_stations_capacity_provided }} +
+
+ Points de charge + {{ charging_points }} +
+
+ Stations avec capacité + {{ charging_stations_with_capacity_count }} +
+
+
+
+ +
+

📊 Tableau récapitulatif

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MesureValeur
Longueur de route (km) 🛣️{{ "%.2f"|format(longueur_route_km) }}
Longueur de piste cyclable (km) 🚴‍♂️{{ "%.2f"|format(road_cycleway_km) }}
Nombre de routes 🚗{{ compte_highways }}
Surface des routes (km²) 🌍{{ "%.2f"|format(surface_route_km2) }}
Nombre de pistes cyclables 🚲{{ compte_piste_cyclable }}
Nombre de ronds-points ⭕{{ roundabout_count }}
Nombre de mini ronds-points 🔵{{ mini_roundabout_count }}
Surface des parkings (km²) 🅿️{{ "%.2f"|format(surface_parking_km2) }}
Surface des parkings à vélos (km²) 🚲🅿️{{ "%.5f"|format(surface_bicycle_parking_km2) }}
Nombre de parkings avec capacité renseignée 🚗💼{{ car_parking_capacity_provided }}
Nombre de bâtiments 🏢{{ building_count }}
Aire des bâtiments (km²) 📏{{ "%.5f"|format(building_area) }}
Nombre de stations de recharge ⚡{{ charging_stations }}
Capacité totale des stations de recharge ⚡📦{{ charging_stations_capacity_provided }}
Nombre de points de charge 🔌{{ charging_points }}
+
+
+ + + + + \ No newline at end of file diff --git a/test_data.json b/test_data.json new file mode 100644 index 0000000..999e5e3 --- /dev/null +++ b/test_data.json @@ -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" +} \ No newline at end of file diff --git a/test_present.py b/test_present.py new file mode 100644 index 0000000..4fd06a4 --- /dev/null +++ b/test_present.py @@ -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() \ No newline at end of file