From 8cb321a9c87c8ea73026570916f5bf2668384963 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 16 Mar 2025 12:54:21 +0100 Subject: [PATCH] generate map with folium --- README.md | 154 +- __pycache__/html_to_jpg.cpython-311.pyc | Bin 0 -> 6754 bytes __pycache__/map.cpython-311.pyc | Bin 0 -> 8435 bytes __pycache__/map_simple.cpython-311.pyc | Bin 0 -> 17479 bytes __pycache__/present.cpython-311.pyc | Bin 0 -> 6621 bytes carte_ville.html | 112988 +++++++++++++++++++++ carte_ville.jpg | Bin 0 -> 16025 bytes generate_city_map.py | 158 + html2jpg.py | 99 + html_to_jpg.py | 148 + map.py | 176 + map_simple.py | 383 + overpass_data.json | 220 +- present.py | 126 +- requirements.txt | 11 + resultat_template.html | 139 + resultat_template_ameliore.html.html | 408 + simple_map.py | 335 + summary_results.json | 20 +- template.html => template.html.j2 | 0 template_ameliore.html.j2 | 409 + test_data.json | 40 + test_present.py | 74 + 23 files changed, 115808 insertions(+), 80 deletions(-) create mode 100644 __pycache__/html_to_jpg.cpython-311.pyc create mode 100644 __pycache__/map.cpython-311.pyc create mode 100644 __pycache__/map_simple.cpython-311.pyc create mode 100644 __pycache__/present.cpython-311.pyc create mode 100644 carte_ville.html create mode 100644 carte_ville.jpg create mode 100644 generate_city_map.py create mode 100644 html2jpg.py create mode 100644 html_to_jpg.py create mode 100644 map.py create mode 100644 map_simple.py create mode 100644 requirements.txt create mode 100644 resultat_template.html create mode 100644 resultat_template_ameliore.html.html create mode 100644 simple_map.py rename template.html => template.html.j2 (100%) create mode 100644 template_ameliore.html.j2 create mode 100644 test_data.json create mode 100644 test_present.py 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 0000000000000000000000000000000000000000..d1aaad58938e72ab14c0f533ac880db4172d766d GIT binary patch literal 6754 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 literal 0 HcmV?d00001 diff --git a/__pycache__/present.cpython-311.pyc b/__pycache__/present.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b50de8e43fe8ce9e714296ec7a9a7091da29d376 GIT binary patch literal 6621 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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 0000000000000000000000000000000000000000..86f07a8ea2d4b246e39b4e88aecc0498d4eaea0e GIT binary patch literal 16025 zcmeIwYfw~W7zgn8-1fe>cXoA5U_mx;B4iaJQo$gWK5%P6WSUY?QF93@aBWI%Vr_|n zg@R1XZW;m+vFy5J7r?m1L?K$hYM@AnI2mLjAh4aS#%7vnrkZJ{56^Sn=hJ!5JJ0_; zKgWRM3KB#`Xd)1XVJMb1wmLcd3(kP^B3hcd*mTbLdh zlGVAzMwT+Hp;a1pe$q2wYi1ssBXgB2mU^x9Rz2vWUb8kZ=#k)!kB3J*5vhrae@dH> zxG8CKO6s=lJJNRUdf~;FvaxPn}Z@pc5?08wZ>D`m3-m5xYU1M&v zSkKs+K015u^S1Vm&aN-I`z~Dk=G*>(LHpH_Yop(fT_3+OG4<2!pYKfHotd3;=E4y1 zTde!Z{*+5d=fX*nAeqiw7@kcxAtWhxe}*VDmboQE?6EqRB?&7wHng&p0dbSkt(oUJ zGEenYuPJA=Uz7d4V0r&4+5KRDL$Aq5o6AzhByRY-v=u&#L0b zzN;|@bv>(7b}5_c4BbRmDbGD}PM^(D9&)#|2 zcawJWpm&y=eQj^x5r)Par5^El+IQs3eU%3H-jls<){>jiCt|G4g}Z%^ryklfTDE*c zV_f%8lH4ouddQoJUso!A99bb%x-$G47Y;}644LeCV7MV!c3C{8RD3m*aiQEcX=`^4 zcAyT0-$S~%(G&dGl(d*mQCq1izUB~XnUaIFEiIjj`o3oK^TXQdp`K?-7P77P2If!g z2paz2tV^xUVztcZ>-N;&{&fD{V*X5KA9q=uj+>TZJCL6ET!u=kuoYBbw|48ymh@TH z&E`9E&2!QGUHxNK6RqKr7!3Na?;vUJhl;!Qy+s2N+mnoC2?;(`sWqb_tRw82v5uo6 z3eHS=mS# {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