mirror of
https://forge.chapril.org/tykayn/parking-land
synced 2025-06-20 01:44:42 +02:00
generate map with folium
This commit is contained in:
parent
2399302e4e
commit
8cb321a9c8
23 changed files with 115808 additions and 80 deletions
154
README.md
154
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
|
||||
Ce projet permet de générer des rapports HTML et des cartes visuelles présentant les résultats d'analyse urbaine pour une ville donnée, à partir de données JSON et OpenStreetMap.
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Génération de rapports HTML à partir de données JSON
|
||||
- Visualisation des statistiques urbaines (routes, pistes cyclables, parkings, bâtiments, etc.)
|
||||
- Génération de cartes visuelles des villes à partir de leur ID OpenStreetMap
|
||||
- Calcul automatique de ratios et d'indicateurs
|
||||
- Templates personnalisables
|
||||
- Interface responsive adaptée aux mobiles et ordinateurs
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Python 3.6 ou supérieur
|
||||
- Jinja2 (`pip install jinja2`)
|
||||
- Pour les cartes : OSMnx ou Folium (voir requirements.txt)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clonez ce dépôt :
|
||||
```
|
||||
git clone https://github.com/votre-utilisateur/analyse-urbaine.git
|
||||
cd analyse-urbaine
|
||||
```
|
||||
|
||||
2. Installez les dépendances :
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Pour la conversion HTML vers JPG, installez l'un des outils suivants :
|
||||
- wkhtmltopdf/wkhtmltoimage : https://wkhtmltopdf.org/downloads.html
|
||||
- CutyCapt (pour Linux) : `sudo apt-get install cutycapt`
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Génération d'un rapport HTML
|
||||
|
||||
```bash
|
||||
python present.py <fichier_json> [fichier_html_sortie] [fichier_template]
|
||||
```
|
||||
|
||||
Arguments :
|
||||
- `<fichier_json>` : Chemin vers le fichier JSON contenant les données (obligatoire)
|
||||
- `[fichier_html_sortie]` : Chemin où sauvegarder le fichier HTML (par défaut: resultat.html)
|
||||
- `[fichier_template]` : Nom du fichier template à utiliser (par défaut: template.html)
|
||||
|
||||
### Exemple
|
||||
|
||||
```bash
|
||||
python present.py summary_results.json rapport_ville.html template_ameliore.html
|
||||
```
|
||||
|
||||
### Génération d'une carte de ville
|
||||
|
||||
```bash
|
||||
python generate_city_map.py <osm_id> [options]
|
||||
```
|
||||
|
||||
Arguments :
|
||||
- `<osm_id>` : ID OpenStreetMap de la ville (obligatoire)
|
||||
- `-o, --output` : Chemin où sauvegarder l'image
|
||||
- `--folium` : Utiliser Folium au lieu d'OSMnx (plus léger mais moins détaillé)
|
||||
- `-v, --verbose` : Afficher les messages de débogage
|
||||
|
||||
### Exemple
|
||||
|
||||
```bash
|
||||
python generate_city_map.py 123456 -o carte_ville.jpg
|
||||
```
|
||||
|
||||
### Utilisation avancée
|
||||
|
||||
Pour plus de contrôle, vous pouvez utiliser les scripts individuels :
|
||||
|
||||
1. Génération de carte avec OSMnx (haute qualité) :
|
||||
```bash
|
||||
python map.py <osm_id> [-o output.jpg]
|
||||
```
|
||||
|
||||
2. Génération de carte avec Folium (plus léger) :
|
||||
```bash
|
||||
python map_simple.py <osm_id> [-o output.html]
|
||||
```
|
||||
|
||||
3. Conversion d'une carte HTML en JPG :
|
||||
```bash
|
||||
python html_to_jpg.py <fichier_html> [-o output.jpg] [-m méthode]
|
||||
```
|
||||
|
||||
### Test de la solution
|
||||
|
||||
Pour tester la solution avec des données fictives :
|
||||
|
||||
```bash
|
||||
python test_present.py
|
||||
```
|
||||
|
||||
## Structure des données JSON
|
||||
|
||||
Le fichier JSON doit contenir les clés suivantes :
|
||||
|
||||
```json
|
||||
{
|
||||
"city_name": "Nom de la ville",
|
||||
"longueur_route_km": 228.33,
|
||||
"road_cycleway_km": 12.5,
|
||||
"compte_highways": 3530,
|
||||
"surface_route_km2": 1.59,
|
||||
"building_count": 2516,
|
||||
"building_area": 2.65,
|
||||
"building_sizes": [0, 10, 50, 100, 200, 500, "Infinity"],
|
||||
"building_size_counts": [120, 450, 780, 650, 400, 116],
|
||||
"compte_piste_cyclable": 42,
|
||||
"car_parking_capacity_provided": 491,
|
||||
"roundabout_count": 12,
|
||||
"mini_roundabout_count": 5,
|
||||
"surface_parking_km2": 0.35,
|
||||
"surface_bicycle_parking_km2": 0.042,
|
||||
"charging_stations": 14,
|
||||
"charging_stations_with_capacity_count": 12,
|
||||
"charging_stations_capacity_provided": 28,
|
||||
"charging_points": 32
|
||||
}
|
||||
```
|
||||
|
||||
## Templates disponibles
|
||||
|
||||
- `template.html` : Template de base avec tableau simple
|
||||
- `template_ameliore.html` : Template amélioré avec design moderne, cartes et visualisations
|
||||
|
||||
## Personnalisation
|
||||
|
||||
Vous pouvez créer vos propres templates en vous basant sur les templates existants. Utilisez la syntaxe Jinja2 pour accéder aux données.
|
||||
|
||||
## Trouver l'ID OpenStreetMap d'une ville
|
||||
|
||||
1. Allez sur [OpenStreetMap](https://www.openstreetmap.org/)
|
||||
2. Recherchez la ville souhaitée
|
||||
3. Cliquez sur la ville dans les résultats
|
||||
4. L'URL contiendra l'ID de la relation, par exemple : `https://www.openstreetmap.org/relation/123456`
|
||||
5. L'ID est le nombre après "relation/" (ici, 123456)
|
||||
|
||||
## Licence
|
||||
|
||||
Ce projet est sous licence MIT.
|
BIN
__pycache__/html_to_jpg.cpython-311.pyc
Normal file
BIN
__pycache__/html_to_jpg.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/map.cpython-311.pyc
Normal file
BIN
__pycache__/map.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/map_simple.cpython-311.pyc
Normal file
BIN
__pycache__/map_simple.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/present.cpython-311.pyc
Normal file
BIN
__pycache__/present.cpython-311.pyc
Normal file
Binary file not shown.
112988
carte_ville.html
Normal file
112988
carte_ville.html
Normal file
File diff suppressed because it is too large
Load diff
BIN
carte_ville.jpg
Normal file
BIN
carte_ville.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
158
generate_city_map.py
Normal file
158
generate_city_map.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script principal qui génère une carte d'une ville à partir de son ID OpenStreetMap
|
||||
et la convertit en image JPG en une seule commande.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import importlib.util
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration de la journalisation
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def check_module_available(module_name):
|
||||
"""Vérifie si un module Python est disponible."""
|
||||
return importlib.util.find_spec(module_name) is not None
|
||||
|
||||
def generate_map(osm_id, output_path=None, use_osmnx=True, verbose=False):
|
||||
"""
|
||||
Génère une carte d'une ville à partir de son ID OpenStreetMap.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap de la ville
|
||||
output_path (str, optional): Chemin 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()
|
99
html2jpg.py
Normal file
99
html2jpg.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script simplifié pour convertir un fichier HTML en image JPG.
|
||||
Utilise playwright pour rendre la page web et la capturer en image.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
def convert_html_to_jpg(html_path, output_path=None):
|
||||
"""
|
||||
Convertit un fichier HTML en image JPG en utilisant playwright.
|
||||
|
||||
Args:
|
||||
html_path (str): Chemin vers le fichier HTML à convertir
|
||||
output_path (str, optional): Chemin 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()
|
148
html_to_jpg.py
Normal file
148
html_to_jpg.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour convertir une carte HTML générée par Folium en image JPG.
|
||||
Utilise imgkit qui s'appuie sur wkhtmltoimage.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Configuration de la journalisation
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def convert_html_to_jpg(html_path, output_path=None, method='wkhtmltoimage'):
|
||||
"""
|
||||
Convertit un fichier HTML en image JPG.
|
||||
|
||||
Args:
|
||||
html_path (str): Chemin vers le fichier HTML à convertir
|
||||
output_path (str, optional): Chemin 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()
|
176
map.py
Normal file
176
map.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour générer une carte visuelle d'une ville à partir de son ID OpenStreetMap.
|
||||
Les routes, parkings et bâtiments sont colorés en gris sur un fond vert.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import osmnx as ox
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.cm as cm
|
||||
import networkx as nx
|
||||
import geopandas as gpd
|
||||
from shapely.geometry import Point, Polygon, MultiPolygon
|
||||
import logging
|
||||
|
||||
# Configuration de la journalisation
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration globale d'OSMnx
|
||||
ox.config(use_cache=True, log_console=False)
|
||||
|
||||
def get_city_by_osm_id(osm_id):
|
||||
"""
|
||||
Récupère les données d'une ville à partir de son ID OpenStreetMap.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap de la ville
|
||||
|
||||
Returns:
|
||||
dict: Un dictionnaire contenant les différentes géométries de la ville
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Récupération des données pour la ville avec l'ID OSM: {osm_id}")
|
||||
|
||||
# Récupérer la géométrie de la ville
|
||||
city_boundary = ox.geocode_to_gdf(f"relation/{osm_id}")
|
||||
|
||||
if city_boundary.empty:
|
||||
logger.error(f"Aucune donnée trouvée pour l'ID OSM: {osm_id}")
|
||||
return None
|
||||
|
||||
# Récupérer le nom de la ville
|
||||
city_name = city_boundary.iloc[0].get('name', f"Ville_{osm_id}")
|
||||
logger.info(f"Ville identifiée: {city_name}")
|
||||
|
||||
# Récupérer le réseau routier
|
||||
logger.info("Récupération du réseau routier...")
|
||||
road_network = ox.graph_from_polygon(city_boundary.iloc[0].geometry, network_type='drive')
|
||||
|
||||
# Récupérer les bâtiments
|
||||
logger.info("Récupération des bâtiments...")
|
||||
buildings = ox.features_from_polygon(city_boundary.iloc[0].geometry, tags={'building': True})
|
||||
|
||||
# Récupérer les parkings
|
||||
logger.info("Récupération des parkings...")
|
||||
parkings = ox.features_from_polygon(city_boundary.iloc[0].geometry, tags={'amenity': 'parking'})
|
||||
|
||||
return {
|
||||
'city_name': city_name,
|
||||
'boundary': city_boundary,
|
||||
'road_network': road_network,
|
||||
'buildings': buildings,
|
||||
'parkings': parkings
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la récupération des données: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_city_map(city_data, output_path=None):
|
||||
"""
|
||||
Génère une carte visuelle de la ville avec les routes, parkings et bâtiments.
|
||||
|
||||
Args:
|
||||
city_data (dict): Dictionnaire contenant les données de la ville
|
||||
output_path (str, optional): Chemin 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()
|
383
map_simple.py
Normal file
383
map_simple.py
Normal file
|
@ -0,0 +1,383 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Version simplifiée du script pour générer une carte visuelle d'une ville à partir de son ID OpenStreetMap.
|
||||
Utilise l'API Overpass et Folium pour créer une carte interactive.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import requests
|
||||
import folium
|
||||
from folium.plugins import MarkerCluster
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration de la journalisation
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_city_data_from_osm(osm_id):
|
||||
"""
|
||||
Récupère les données d'une ville à partir de son ID OpenStreetMap via l'API Overpass.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap de la ville
|
||||
|
||||
Returns:
|
||||
dict: Un dictionnaire contenant les données de la ville
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Récupération des données pour l'ID OSM: {osm_id}")
|
||||
|
||||
# Essayer d'abord en tant que relation
|
||||
city_data = _try_get_osm_data(osm_id, "relation")
|
||||
|
||||
# Si ça ne fonctionne pas, essayer en tant que way (chemin)
|
||||
if not city_data:
|
||||
logger.info(f"Essai en tant que chemin (way) pour l'ID OSM: {osm_id}")
|
||||
city_data = _try_get_osm_data(osm_id, "way")
|
||||
|
||||
# Si ça ne fonctionne toujours pas, essayer en tant que node (nœud)
|
||||
if not city_data:
|
||||
logger.info(f"Essai en tant que nœud (node) pour l'ID OSM: {osm_id}")
|
||||
city_data = _try_get_osm_data(osm_id, "node")
|
||||
|
||||
# Si aucune méthode ne fonctionne
|
||||
if not city_data:
|
||||
logger.error(f"Aucune donnée trouvée pour l'ID OSM: {osm_id}")
|
||||
return None
|
||||
|
||||
return city_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la récupération des données: {str(e)}")
|
||||
return None
|
||||
|
||||
def _try_get_osm_data(osm_id, element_type):
|
||||
"""
|
||||
Essaie de récupérer les données OSM pour un type d'élément spécifique.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap
|
||||
element_type (str): Le type d'élément ('relation', 'way', 'node')
|
||||
|
||||
Returns:
|
||||
dict: Un dictionnaire contenant les données ou None en cas d'échec
|
||||
"""
|
||||
try:
|
||||
# Requête pour obtenir les informations de base
|
||||
if element_type == "relation":
|
||||
# Pour les relations (villes, quartiers, etc.)
|
||||
query = f"""
|
||||
[out:json];
|
||||
relation({osm_id});
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
elif element_type == "way":
|
||||
# Pour les chemins (routes, contours, etc.)
|
||||
query = f"""
|
||||
[out:json];
|
||||
way({osm_id});
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
elif element_type == "node":
|
||||
# Pour les nœuds (points d'intérêt, etc.)
|
||||
query = f"""
|
||||
[out:json];
|
||||
node({osm_id});
|
||||
out body;
|
||||
"""
|
||||
else:
|
||||
logger.error(f"Type d'élément non reconnu: {element_type}")
|
||||
return None
|
||||
|
||||
# Envoyer la requête à l'API Overpass
|
||||
overpass_url = "https://overpass-api.de/api/interpreter"
|
||||
response = requests.post(overpass_url, data={"data": query})
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Erreur lors de la requête Overpass: {response.status_code}")
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
|
||||
if not data.get('elements'):
|
||||
logger.warning(f"Aucune donnée trouvée pour l'ID OSM {osm_id} en tant que {element_type}")
|
||||
return None
|
||||
|
||||
# Extraire les informations de base
|
||||
element_info = next((e for e in data['elements'] if e.get('id') == osm_id), None)
|
||||
if not element_info:
|
||||
logger.warning(f"Élément {element_type} non trouvé pour l'ID OSM: {osm_id}")
|
||||
return None
|
||||
|
||||
# Récupérer le nom et les coordonnées
|
||||
name = element_info.get('tags', {}).get('name', f"Lieu_{osm_id}")
|
||||
logger.info(f"Lieu identifié: {name}")
|
||||
|
||||
# Déterminer les coordonnées centrales
|
||||
if element_type == "node":
|
||||
# Pour un nœud, utiliser directement ses coordonnées
|
||||
center_lat = element_info.get('lat')
|
||||
center_lon = element_info.get('lon')
|
||||
center = (center_lat, center_lon)
|
||||
|
||||
# Pour un nœud, définir une zone autour
|
||||
bbox = (center_lat - 0.01, center_lon - 0.01, center_lat + 0.01, center_lon + 0.01)
|
||||
else:
|
||||
# Pour les relations et chemins, calculer à partir des nœuds
|
||||
nodes = [e for e in data['elements'] if e.get('type') == 'node']
|
||||
if not nodes:
|
||||
logger.error(f"Aucun nœud trouvé pour {element_type} {osm_id}")
|
||||
return None
|
||||
|
||||
lats = [n.get('lat', 0) for n in nodes if 'lat' in n]
|
||||
lons = [n.get('lon', 0) for n in nodes if 'lon' in n]
|
||||
|
||||
if not lats or not lons:
|
||||
logger.error(f"Coordonnées manquantes pour {element_type} {osm_id}")
|
||||
return None
|
||||
|
||||
center_lat = sum(lats) / len(lats)
|
||||
center_lon = sum(lons) / len(lons)
|
||||
center = (center_lat, center_lon)
|
||||
|
||||
# Calculer la boîte englobante
|
||||
min_lat, max_lat = min(lats), max(lats)
|
||||
min_lon, max_lon = min(lons), max(lons)
|
||||
bbox = (min_lat, min_lon, max_lat, max_lon)
|
||||
|
||||
# Récupérer les éléments dans la zone (routes, bâtiments, parkings)
|
||||
# Utiliser la boîte englobante pour la requête
|
||||
area_query = f"""
|
||||
[out:json];
|
||||
(
|
||||
way[highway]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
way[building]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
way[amenity=parking]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
|
||||
logger.info(f"Récupération des routes, bâtiments et parkings pour {name}...")
|
||||
response = requests.post(overpass_url, data={"data": area_query})
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Erreur lors de la requête Overpass pour les éléments: {response.status_code}")
|
||||
return None
|
||||
|
||||
elements_data = response.json()
|
||||
|
||||
return {
|
||||
'city_name': name,
|
||||
'center': center,
|
||||
'bbox': bbox,
|
||||
'city_data': data,
|
||||
'elements_data': elements_data,
|
||||
'element_type': element_type
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la récupération des données pour {element_type} {osm_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
def create_folium_map(city_data, output_path=None):
|
||||
"""
|
||||
Crée une carte interactive avec Folium.
|
||||
|
||||
Args:
|
||||
city_data (dict): Dictionnaire contenant les données de la ville
|
||||
output_path (str, optional): Chemin 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'''
|
||||
<h3 align="center" style="font-size:16px"><b>Carte de {city_name}</b></h3>
|
||||
<p align="center" style="font-size:12px">ID OpenStreetMap: {city_data.get('city_data', {}).get('elements', [{}])[0].get('id', 'N/A')} (Type: {element_type})</p>
|
||||
'''
|
||||
m.get_root().html.add_child(folium.Element(title_html))
|
||||
|
||||
# Ajouter un marqueur pour le centre
|
||||
folium.Marker(
|
||||
location=center,
|
||||
popup=f"Centre de {city_name}",
|
||||
icon=folium.Icon(color='red', icon='info-sign')
|
||||
).add_to(m)
|
||||
|
||||
# Extraire les nœuds pour construire les géométries
|
||||
nodes = {n['id']: (n['lat'], n['lon']) for n in elements_data['elements'] if n['type'] == 'node'}
|
||||
|
||||
# Compter les éléments pour les statistiques
|
||||
highways_count = 0
|
||||
buildings_count = 0
|
||||
parkings_count = 0
|
||||
|
||||
# Traiter les routes, bâtiments et parkings
|
||||
for element in elements_data['elements']:
|
||||
if element['type'] == 'way' and 'tags' in element:
|
||||
# Récupérer les coordonnées des nœuds
|
||||
coords = []
|
||||
for node_id in element['nodes']:
|
||||
if node_id in nodes:
|
||||
coords.append(nodes[node_id])
|
||||
|
||||
if not coords:
|
||||
continue
|
||||
|
||||
# Déterminer le type d'élément
|
||||
if 'highway' in element['tags']:
|
||||
# C'est une route
|
||||
highways_count += 1
|
||||
folium.PolyLine(
|
||||
coords,
|
||||
color='#555555',
|
||||
weight=2,
|
||||
opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Route')
|
||||
).add_to(m)
|
||||
|
||||
elif 'building' in element['tags']:
|
||||
# C'est un bâtiment
|
||||
buildings_count += 1
|
||||
folium.Polygon(
|
||||
coords,
|
||||
color='#777777',
|
||||
fill=True,
|
||||
fill_color='#777777',
|
||||
fill_opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Bâtiment')
|
||||
).add_to(m)
|
||||
|
||||
elif element['tags'].get('amenity') == 'parking':
|
||||
# C'est un parking
|
||||
parkings_count += 1
|
||||
folium.Polygon(
|
||||
coords,
|
||||
color='#999999',
|
||||
fill=True,
|
||||
fill_color='#999999',
|
||||
fill_opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Parking')
|
||||
).add_to(m)
|
||||
|
||||
# Ajouter une légende avec les statistiques
|
||||
legend_html = f'''
|
||||
<div style="position: fixed;
|
||||
bottom: 50px; right: 50px; width: 200px; height: 130px;
|
||||
border:2px solid grey; z-index:9999; font-size:12px;
|
||||
background-color: white; padding: 10px;
|
||||
border-radius: 5px;">
|
||||
<p><b>Statistiques</b></p>
|
||||
<p>Routes: {highways_count}</p>
|
||||
<p>Bâtiments: {buildings_count}</p>
|
||||
<p>Parkings: {parkings_count}</p>
|
||||
</div>
|
||||
'''
|
||||
m.get_root().html.add_child(folium.Element(legend_html))
|
||||
|
||||
# Définir le chemin de sortie
|
||||
if not output_path:
|
||||
output_path = f"{city_name.replace(' ', '_')}_map.html"
|
||||
|
||||
# Sauvegarder la carte
|
||||
logger.info(f"Sauvegarde de la carte dans: {output_path}")
|
||||
m.save(output_path)
|
||||
|
||||
# Générer également une image statique si possible
|
||||
try:
|
||||
import selenium
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
|
||||
logger.info("Génération d'une image statique de la carte...")
|
||||
|
||||
# Configurer Selenium pour prendre une capture d'écran
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
|
||||
driver = webdriver.Chrome(options=chrome_options)
|
||||
driver.set_window_size(1200, 800)
|
||||
|
||||
# Charger la carte HTML
|
||||
driver.get(f"file://{os.path.abspath(output_path)}")
|
||||
|
||||
# Attendre que la carte se charge
|
||||
driver.implicitly_wait(5)
|
||||
|
||||
# Prendre une capture d'écran
|
||||
png_path = output_path.replace('.html', '.png')
|
||||
driver.save_screenshot(png_path)
|
||||
driver.quit()
|
||||
|
||||
logger.info(f"Image statique générée: {png_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de générer une image statique: {str(e)}")
|
||||
|
||||
return output_path
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la création de la carte: {str(e)}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale qui traite les arguments de ligne de commande
|
||||
et génère la carte de la ville.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description='Génère une carte interactive d\'une ville à partir de son ID OpenStreetMap.')
|
||||
parser.add_argument('osm_id', type=int, help='ID OpenStreetMap de la ville')
|
||||
parser.add_argument('-o', '--output', type=str, help='Chemin où sauvegarder la carte (par défaut: nom_ville_map.html)')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='Afficher les messages de débogage')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Configurer le niveau de journalisation
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Récupérer les données de la ville
|
||||
city_data = get_city_data_from_osm(args.osm_id)
|
||||
|
||||
if not city_data:
|
||||
logger.error(f"Impossible de récupérer les données pour l'ID OSM: {args.osm_id}")
|
||||
sys.exit(1)
|
||||
|
||||
# Créer la carte
|
||||
output_path = create_folium_map(city_data, args.output)
|
||||
|
||||
if output_path:
|
||||
logger.info(f"Carte générée avec succès: {output_path}")
|
||||
else:
|
||||
logger.error("Échec de la génération de la carte")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
126
present.py
126
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_json> [fichier_html_sortie] [fichier_template]")
|
||||
print(" <fichier_json>: Chemin vers le fichier JSON contenant les données")
|
||||
print(" [fichier_html_sortie]: Chemin où sauvegarder le fichier HTML (par défaut: resultat.html)")
|
||||
print(" [fichier_template]: Nom du fichier template à utiliser (par défaut: template.html)")
|
||||
return
|
||||
|
||||
json_file = sys.argv[1]
|
||||
output_html = sys.argv[2] if len(sys.argv) > 2 else "resultat.html"
|
||||
template_file = sys.argv[3] if len(sys.argv) > 3 else "template.html"
|
||||
|
||||
generate_html_from_json(json_file, output_html, template_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_html_from_json('summary_results.json', 'summary_results.html')
|
||||
main()
|
||||
|
|
11
requirements.txt
Normal file
11
requirements.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
jinja2>=3.0.0
|
||||
osmnx>=1.3.0
|
||||
matplotlib>=3.5.0
|
||||
networkx>=2.8.0
|
||||
geopandas>=0.12.0
|
||||
shapely>=2.0.0
|
||||
folium>=0.14.0
|
||||
requests>=2.28.0
|
||||
selenium>=4.1.0
|
||||
imgkit>=1.2.2
|
||||
playwright>=1.30.0
|
139
resultat_template.html
Normal file
139
resultat_template.html
Normal file
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Résumé des résultats</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
td:nth-of-type(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
table {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Résumé des résultats pour Ville Test</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Mesure</th>
|
||||
<th>Valeur</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de route (km) 🛣️</td>
|
||||
<td>228.33</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de piste cyclable (km) 🚴♂️</td>
|
||||
<td>12.50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de routes 🚗</td>
|
||||
<td>3530</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des routes (km²) 🌍</td>
|
||||
<td>1.59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de pistes cyclables 🚲</td>
|
||||
<td>42</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de ronds-points ⭕</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de mini ronds-points 🔵</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings (km²) 🅿️</td>
|
||||
<td>0.35</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
|
||||
<td>0.04</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
|
||||
<td>491</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments 🏢</td>
|
||||
<td>2516</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aire des bâtiments (m²) 📏</td>
|
||||
<td>2.65</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de moins de 10 m² 🏠</td>
|
||||
<td>120</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de 10 à 50 m² 🏡</td>
|
||||
<td>450</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de 50 à 100 m² 🏢</td>
|
||||
<td>780</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de 100 à 200 m² 🏬</td>
|
||||
<td>650</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de 200 à 500 m² 🏣</td>
|
||||
<td>400</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments de plus de 500 m² 🏛️</td>
|
||||
<td>116</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de stations de recharge ⚡</td>
|
||||
<td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de stations de recharge avec capacité renseignée ⚡💼</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capacité totale des stations de recharge ⚡📦</td>
|
||||
<td>28</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de points de charge 🔌</td>
|
||||
<td>32</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
408
resultat_template_ameliore.html.html
Normal file
408
resultat_template_ameliore.html.html
Normal file
|
@ -0,0 +1,408 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analyse urbaine - Ville Test</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #3498db;
|
||||
--secondary-color: #2ecc71;
|
||||
--accent-color: #e74c3c;
|
||||
--text-color: #333;
|
||||
--light-bg: #f9f9f9;
|
||||
--card-bg: #fff;
|
||||
--border-color: #ddd;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-bg);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-style: italic;
|
||||
margin-top: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: var(--primary-color);
|
||||
margin-top: 0;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.ratio-bad {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.ratio-medium {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.ratio-good {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>Analyse urbaine de Ville Test</h1>
|
||||
<p class="subtitle">Données d'infrastructure et d'aménagement urbain</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="dashboard">
|
||||
<!-- Carte des routes -->
|
||||
<div class="card">
|
||||
<h2>🛣️ Réseau routier</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Longueur totale</span>
|
||||
<span class="stat-value">228.33 km</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de routes</span>
|
||||
<span class="stat-value">3530</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface totale</span>
|
||||
<span class="stat-value">1.59 km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Ronds-points</span>
|
||||
<span class="stat-value">17</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carte des pistes cyclables -->
|
||||
<div class="card">
|
||||
<h2>🚴♂️ Mobilité douce</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Longueur pistes cyclables</span>
|
||||
<span class="stat-value">12.50 km</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de pistes</span>
|
||||
<span class="stat-value">42</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>Ratio cyclable/routier:</p>
|
||||
<div class="progress-container">
|
||||
<div
|
||||
class="progress-bar ratio-medium"
|
||||
style='width: 5.47 %'></div>
|
||||
</div>
|
||||
<p>5.47% du réseau routier</p>
|
||||
</div>
|
||||
|
||||
<!-- Carte des parkings -->
|
||||
<div class="card">
|
||||
<h2>🅿️ Stationnement</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface parkings voiture</span>
|
||||
<span class="stat-value">0.35 km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Capacité renseignée</span>
|
||||
<span class="stat-value">491 places</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface parkings vélo</span>
|
||||
<span class="stat-value">0.04200 km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Ratio parking/route</span>
|
||||
<span class="stat-value ">22.01%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carte des bâtiments -->
|
||||
<div class="card">
|
||||
<h2>🏢 Bâtiments</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre total</span>
|
||||
<span class="stat-value">2516</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface totale</span>
|
||||
<span class="stat-value">2.65 km²</span>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Taille</th>
|
||||
<th>Nombre</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
< 10 m²</td>
|
||||
<td>120</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10-50 m²</td>
|
||||
<td>450</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>50-100 m²</td>
|
||||
<td>780</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>100-200 m²</td>
|
||||
<td>650</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>200-500 m²</td>
|
||||
<td>400</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>> 500 m²</td>
|
||||
<td>116</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Carte des bornes de recharge -->
|
||||
<div class="card">
|
||||
<h2>⚡ Bornes de recharge</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de stations</span>
|
||||
<span class="stat-value">14</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Capacité totale</span>
|
||||
<span class="stat-value">28</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Points de charge</span>
|
||||
<span class="stat-value">32</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Stations avec capacité</span>
|
||||
<span class="stat-value">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>📊 Tableau récapitulatif</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Mesure</th>
|
||||
<th>Valeur</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de route (km) 🛣️</td>
|
||||
<td>228.33</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de piste cyclable (km) 🚴♂️</td>
|
||||
<td>12.50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de routes 🚗</td>
|
||||
<td>3530</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des routes (km²) 🌍</td>
|
||||
<td>1.59</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de pistes cyclables 🚲</td>
|
||||
<td>42</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de ronds-points ⭕</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de mini ronds-points 🔵</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings (km²) 🅿️</td>
|
||||
<td>0.35</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
|
||||
<td>0.04200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
|
||||
<td>491</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments 🏢</td>
|
||||
<td>2516</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aire des bâtiments (km²) 📏</td>
|
||||
<td>2.65000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de stations de recharge ⚡</td>
|
||||
<td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capacité totale des stations de recharge ⚡📦</td>
|
||||
<td>28</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de points de charge 🔌</td>
|
||||
<td>32</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>Rapport généré le 16/03/2025 12:35:11</p>
|
||||
<p>Analyse des infrastructures urbaines et de mobilité</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
335
simple_map.py
Normal file
335
simple_map.py
Normal file
|
@ -0,0 +1,335 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script très simplifié pour générer une carte d'une ville à partir de son ID OpenStreetMap.
|
||||
Utilise directement l'API Overpass et Folium pour créer une carte HTML.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import requests
|
||||
import folium
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def get_osm_data(osm_id, element_type="relation"):
|
||||
"""
|
||||
Récupère les données OpenStreetMap pour un élément donné.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap
|
||||
element_type (str): Le type d'élément ('relation', 'way', 'node')
|
||||
|
||||
Returns:
|
||||
dict: Les données récupérées ou None en cas d'erreur
|
||||
"""
|
||||
print(f"Récupération des données pour {element_type}/{osm_id}...")
|
||||
|
||||
# Construire la requête Overpass
|
||||
if element_type == "relation":
|
||||
query = f"""
|
||||
[out:json];
|
||||
relation({osm_id});
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
elif element_type == "way":
|
||||
query = f"""
|
||||
[out:json];
|
||||
way({osm_id});
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
elif element_type == "node":
|
||||
query = f"""
|
||||
[out:json];
|
||||
node({osm_id});
|
||||
out body;
|
||||
"""
|
||||
else:
|
||||
print(f"Type d'élément non reconnu: {element_type}")
|
||||
return None
|
||||
|
||||
# Envoyer la requête à l'API Overpass
|
||||
try:
|
||||
overpass_url = "https://overpass-api.de/api/interpreter"
|
||||
response = requests.post(overpass_url, data={"data": query})
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Erreur lors de la requête Overpass: {response.status_code}")
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
|
||||
if not data.get('elements'):
|
||||
print(f"Aucune donnée trouvée pour {element_type}/{osm_id}")
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la récupération des données: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_bbox_from_data(data):
|
||||
"""
|
||||
Calcule la boîte englobante à partir des données OSM.
|
||||
|
||||
Args:
|
||||
data (dict): Les données OSM
|
||||
|
||||
Returns:
|
||||
tuple: (min_lat, min_lon, max_lat, max_lon) ou None
|
||||
"""
|
||||
try:
|
||||
nodes = [e for e in data['elements'] if e.get('type') == 'node']
|
||||
if not nodes:
|
||||
print("Aucun nœud trouvé dans les données")
|
||||
return None
|
||||
|
||||
lats = [n.get('lat', 0) for n in nodes if 'lat' in n]
|
||||
lons = [n.get('lon', 0) for n in nodes if 'lon' in n]
|
||||
|
||||
if not lats or not lons:
|
||||
print("Coordonnées manquantes dans les données")
|
||||
return None
|
||||
|
||||
min_lat, max_lat = min(lats), max(lats)
|
||||
min_lon, max_lon = min(lons), max(lons)
|
||||
|
||||
return (min_lat, min_lon, max_lat, max_lon)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du calcul de la boîte englobante: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_elements_in_bbox(bbox):
|
||||
"""
|
||||
Récupère les routes, bâtiments et parkings dans une boîte englobante.
|
||||
|
||||
Args:
|
||||
bbox (tuple): (min_lat, min_lon, max_lat, max_lon)
|
||||
|
||||
Returns:
|
||||
dict: Les données récupérées ou None en cas d'erreur
|
||||
"""
|
||||
print(f"Récupération des éléments dans la zone...")
|
||||
|
||||
# Construire la requête Overpass
|
||||
query = f"""
|
||||
[out:json];
|
||||
(
|
||||
way[highway]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
way[building]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
way[amenity=parking]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
|
||||
# Envoyer la requête à l'API Overpass
|
||||
try:
|
||||
overpass_url = "https://overpass-api.de/api/interpreter"
|
||||
response = requests.post(overpass_url, data={"data": query})
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Erreur lors de la requête Overpass: {response.status_code}")
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
|
||||
if not data.get('elements'):
|
||||
print("Aucun élément trouvé dans la zone")
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la récupération des éléments: {str(e)}")
|
||||
return None
|
||||
|
||||
def create_map(osm_id, output_path=None):
|
||||
"""
|
||||
Crée une carte pour un élément OpenStreetMap.
|
||||
|
||||
Args:
|
||||
osm_id (int): L'identifiant OpenStreetMap
|
||||
output_path (str): Chemin 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'''
|
||||
<h3 align="center" style="font-size:16px"><b>Carte de {name}</b></h3>
|
||||
<p align="center" style="font-size:12px">ID OpenStreetMap: {osm_id} (Type: {element_type})</p>
|
||||
'''
|
||||
m.get_root().html.add_child(folium.Element(title_html))
|
||||
|
||||
# Ajouter un marqueur pour le centre
|
||||
folium.Marker(
|
||||
location=center,
|
||||
popup=f"Centre de {name}",
|
||||
icon=folium.Icon(color='red', icon='info-sign')
|
||||
).add_to(m)
|
||||
|
||||
# Extraire les nœuds pour construire les géométries
|
||||
nodes = {n['id']: (n['lat'], n['lon']) for n in elements_data['elements'] if n['type'] == 'node'}
|
||||
|
||||
# Compter les éléments pour les statistiques
|
||||
highways_count = 0
|
||||
buildings_count = 0
|
||||
parkings_count = 0
|
||||
|
||||
# Traiter les routes, bâtiments et parkings
|
||||
for element in elements_data['elements']:
|
||||
if element['type'] == 'way' and 'tags' in element:
|
||||
# Récupérer les coordonnées des nœuds
|
||||
coords = []
|
||||
for node_id in element['nodes']:
|
||||
if node_id in nodes:
|
||||
coords.append(nodes[node_id])
|
||||
|
||||
if not coords:
|
||||
continue
|
||||
|
||||
# Déterminer le type d'élément
|
||||
if 'highway' in element['tags']:
|
||||
# C'est une route
|
||||
highways_count += 1
|
||||
folium.PolyLine(
|
||||
coords,
|
||||
color='#555555',
|
||||
weight=2,
|
||||
opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Route')
|
||||
).add_to(m)
|
||||
|
||||
elif 'building' in element['tags']:
|
||||
# C'est un bâtiment
|
||||
buildings_count += 1
|
||||
folium.Polygon(
|
||||
coords,
|
||||
color='#777777',
|
||||
fill=True,
|
||||
fill_color='#777777',
|
||||
fill_opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Bâtiment')
|
||||
).add_to(m)
|
||||
|
||||
elif element['tags'].get('amenity') == 'parking':
|
||||
# C'est un parking
|
||||
parkings_count += 1
|
||||
folium.Polygon(
|
||||
coords,
|
||||
color='#999999',
|
||||
fill=True,
|
||||
fill_color='#999999',
|
||||
fill_opacity=0.7,
|
||||
tooltip=element['tags'].get('name', 'Parking')
|
||||
).add_to(m)
|
||||
|
||||
# Ajouter une légende avec les statistiques
|
||||
legend_html = f'''
|
||||
<div style="position: fixed;
|
||||
bottom: 50px; right: 50px; width: 200px; height: 130px;
|
||||
border:2px solid grey; z-index:9999; font-size:12px;
|
||||
background-color: white; padding: 10px;
|
||||
border-radius: 5px;">
|
||||
<p><b>Statistiques</b></p>
|
||||
<p>Routes: {highways_count}</p>
|
||||
<p>Bâtiments: {buildings_count}</p>
|
||||
<p>Parkings: {parkings_count}</p>
|
||||
</div>
|
||||
'''
|
||||
m.get_root().html.add_child(folium.Element(legend_html))
|
||||
|
||||
# Définir le chemin de sortie
|
||||
if not output_path:
|
||||
output_path = f"{name.replace(' ', '_')}_map.html"
|
||||
elif not output_path.lower().endswith('.html'):
|
||||
output_path = f"{os.path.splitext(output_path)[0]}.html"
|
||||
|
||||
# Sauvegarder la carte
|
||||
print(f"Sauvegarde de la carte dans: {output_path}")
|
||||
m.save(output_path)
|
||||
|
||||
print(f"Carte générée avec succès: {output_path}")
|
||||
print(f"Pour visualiser la carte, ouvrez le fichier HTML dans un navigateur web:")
|
||||
print(f" file://{os.path.abspath(output_path)}")
|
||||
|
||||
return output_path
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale qui traite les arguments de ligne de commande
|
||||
et génère la carte.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description='Génère une carte pour un élément OpenStreetMap.')
|
||||
parser.add_argument('osm_id', type=int, help='ID OpenStreetMap')
|
||||
parser.add_argument('-o', '--output', type=str, help='Chemin où sauvegarder la carte HTML')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Créer la carte
|
||||
create_map(args.osm_id, args.output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"longueur_route_km": 228.30878348424707,
|
||||
"longueur_route_km": 228.33392384008508,
|
||||
"road_cycleway_km": 0.027059960352724084,
|
||||
"compte_highways": 3526,
|
||||
"surface_route_km2": 1.5981614843897296,
|
||||
"building_count": 2515,
|
||||
"building_area": 2.6535844764989233e-08,
|
||||
"compte_highways": 3530,
|
||||
"surface_route_km2": 1.5983374668805956,
|
||||
"building_count": 2516,
|
||||
"building_area": 2.6538178289989005e-08,
|
||||
"building_sizes": [
|
||||
0,
|
||||
10,
|
||||
|
@ -16,7 +16,7 @@
|
|||
],
|
||||
"building_size_counts": [
|
||||
0,
|
||||
2515,
|
||||
2516,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@ -28,10 +28,10 @@
|
|||
"roundabout_count": 0,
|
||||
"mini_roundabout_count": 0,
|
||||
"surface_parking_km2": 9820.0,
|
||||
"surface_bicycle_parking_km2": 3.6e-05,
|
||||
"parking_with_capacity_count": 41,
|
||||
"capacity_bicycle_parking_provided": 115,
|
||||
"bicycle_parking_surface_from_capacity_provided_in_m2": 230,
|
||||
"surface_bicycle_parking_km2": 4.2e-05,
|
||||
"parking_with_capacity_count": 43,
|
||||
"capacity_bicycle_parking_provided": 125,
|
||||
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
|
||||
"charging_stations": 4,
|
||||
"charging_stations_with_capacity_count": 4,
|
||||
"charging_stations_capacity_provided": 7,
|
||||
|
|
409
template_ameliore.html.j2
Normal file
409
template_ameliore.html.j2
Normal file
|
@ -0,0 +1,409 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analyse urbaine - {{ city_name }}</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #3498db;
|
||||
--secondary-color: #2ecc71;
|
||||
--accent-color: #e74c3c;
|
||||
--text-color: #333;
|
||||
--light-bg: #f9f9f9;
|
||||
--card-bg: #fff;
|
||||
--border-color: #ddd;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-bg);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-style: italic;
|
||||
margin-top: 10px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: var(--primary-color);
|
||||
margin-top: 0;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.ratio-bad {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.ratio-medium {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.ratio-good {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>Analyse urbaine de {{ city_name }}</h1>
|
||||
<p class="subtitle">Données d'infrastructure et d'aménagement urbain</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="dashboard">
|
||||
<!-- Carte des routes -->
|
||||
<div class="card">
|
||||
<h2>🛣️ Réseau routier</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Longueur totale</span>
|
||||
<span class="stat-value">{{ "%.2f"|format(longueur_route_km) }} km</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de routes</span>
|
||||
<span class="stat-value">{{ compte_highways }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface totale</span>
|
||||
<span class="stat-value">{{ "%.2f"|format(surface_route_km2) }} km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Ronds-points</span>
|
||||
<span class="stat-value">{{ roundabout_count + mini_roundabout_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carte des pistes cyclables -->
|
||||
<div class="card">
|
||||
<h2>🚴♂️ Mobilité douce</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Longueur pistes cyclables</span>
|
||||
<span class="stat-value">{{ "%.2f"|format(road_cycleway_km) }} km</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de pistes</span>
|
||||
<span class="stat-value">{{ compte_piste_cyclable }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>Ratio cyclable/routier:</p>
|
||||
<div class="progress-container">
|
||||
<div
|
||||
class="progress-bar {% if ratio_cyclable < 5 %}ratio-bad{% elif ratio_cyclable < 15 %}ratio-medium{% else %}ratio-good{% endif %}"
|
||||
style='width: {{ "%.2f"|format(ratio_cyclable) }} %'></div>
|
||||
</div>
|
||||
<p>{{ "%.2f"|format(ratio_cyclable) }}% du réseau routier</p>
|
||||
</div>
|
||||
|
||||
<!-- Carte des parkings -->
|
||||
<div class="card">
|
||||
<h2>🅿️ Stationnement</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface parkings voiture</span>
|
||||
<span class="stat-value">{{ "%.2f"|format(surface_parking_km2) }} km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Capacité renseignée</span>
|
||||
<span class="stat-value">{{ car_parking_capacity_provided }} places</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface parkings vélo</span>
|
||||
<span class="stat-value">{{ "%.5f"|format(surface_bicycle_parking_km2) }} km²</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Ratio parking/route</span>
|
||||
<span class="stat-value {% if ratio_parking_surface > 50 %}highlight{% endif %}">{{
|
||||
"%.2f"|format(ratio_parking_surface) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carte des bâtiments -->
|
||||
<div class="card">
|
||||
<h2>🏢 Bâtiments</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre total</span>
|
||||
<span class="stat-value">{{ building_count }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Surface totale</span>
|
||||
<span class="stat-value">{{ "%.2f"|format(building_area) }} km²</span>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Taille</th>
|
||||
<th>Nombre</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
< 10 m²</td>
|
||||
<td>{{ building_size_counts[0] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10-50 m²</td>
|
||||
<td>{{ building_size_counts[1] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>50-100 m²</td>
|
||||
<td>{{ building_size_counts[2] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>100-200 m²</td>
|
||||
<td>{{ building_size_counts[3] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>200-500 m²</td>
|
||||
<td>{{ building_size_counts[4] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>> 500 m²</td>
|
||||
<td>{{ building_size_counts[5] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Carte des bornes de recharge -->
|
||||
<div class="card">
|
||||
<h2>⚡ Bornes de recharge</h2>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Nombre de stations</span>
|
||||
<span class="stat-value">{{ charging_stations }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Capacité totale</span>
|
||||
<span class="stat-value">{{ charging_stations_capacity_provided }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Points de charge</span>
|
||||
<span class="stat-value">{{ charging_points }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Stations avec capacité</span>
|
||||
<span class="stat-value">{{ charging_stations_with_capacity_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>📊 Tableau récapitulatif</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Mesure</th>
|
||||
<th>Valeur</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de route (km) 🛣️</td>
|
||||
<td>{{ "%.2f"|format(longueur_route_km) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Longueur de piste cyclable (km) 🚴♂️</td>
|
||||
<td>{{ "%.2f"|format(road_cycleway_km) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de routes 🚗</td>
|
||||
<td>{{ compte_highways }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des routes (km²) 🌍</td>
|
||||
<td>{{ "%.2f"|format(surface_route_km2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de pistes cyclables 🚲</td>
|
||||
<td>{{ compte_piste_cyclable }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de ronds-points ⭕</td>
|
||||
<td>{{ roundabout_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de mini ronds-points 🔵</td>
|
||||
<td>{{ mini_roundabout_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings (km²) 🅿️</td>
|
||||
<td>{{ "%.2f"|format(surface_parking_km2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Surface des parkings à vélos (km²) 🚲🅿️</td>
|
||||
<td>{{ "%.5f"|format(surface_bicycle_parking_km2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de parkings avec capacité renseignée 🚗💼</td>
|
||||
<td>{{ car_parking_capacity_provided }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de bâtiments 🏢</td>
|
||||
<td>{{ building_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aire des bâtiments (km²) 📏</td>
|
||||
<td>{{ "%.5f"|format(building_area) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de stations de recharge ⚡</td>
|
||||
<td>{{ charging_stations }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Capacité totale des stations de recharge ⚡📦</td>
|
||||
<td>{{ charging_stations_capacity_provided }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nombre de points de charge 🔌</td>
|
||||
<td>{{ charging_points }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>Rapport généré le {{ date_generation }}</p>
|
||||
<p>Analyse des infrastructures urbaines et de mobilité</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
40
test_data.json
Normal file
40
test_data.json
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"city_name": "Ville Test",
|
||||
"longueur_route_km": 228.33,
|
||||
"road_cycleway_km": 12.5,
|
||||
"compte_highways": 3530,
|
||||
"surface_route_km2": 1.59,
|
||||
"building_count": 2516,
|
||||
"building_area": 2.65,
|
||||
"building_sizes": [
|
||||
0,
|
||||
10,
|
||||
50,
|
||||
100,
|
||||
200,
|
||||
500,
|
||||
Infinity
|
||||
],
|
||||
"building_size_counts": [
|
||||
120,
|
||||
450,
|
||||
780,
|
||||
650,
|
||||
400,
|
||||
116
|
||||
],
|
||||
"compte_piste_cyclable": 42,
|
||||
"car_parking_capacity_provided": 491,
|
||||
"roundabout_count": 12,
|
||||
"mini_roundabout_count": 5,
|
||||
"surface_parking_km2": 0.35,
|
||||
"surface_bicycle_parking_km2": 0.042,
|
||||
"parking_with_capacity_count": 43,
|
||||
"capacity_bicycle_parking_provided": 125,
|
||||
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
|
||||
"charging_stations": 14,
|
||||
"charging_stations_with_capacity_count": 12,
|
||||
"charging_stations_capacity_provided": 28,
|
||||
"charging_points": 32,
|
||||
"date_generation": "16/03/2025 12:35:11"
|
||||
}
|
74
test_present.py
Normal file
74
test_present.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script de test pour vérifier le bon fonctionnement de la génération de rapport HTML
|
||||
à partir des données JSON de décompte urbain.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
from present import generate_html_from_json
|
||||
|
||||
def create_test_data():
|
||||
"""Crée un fichier JSON de test avec des données fictives."""
|
||||
test_data = {
|
||||
"city_name": "Ville Test",
|
||||
"longueur_route_km": 228.33,
|
||||
"road_cycleway_km": 12.5,
|
||||
"compte_highways": 3530,
|
||||
"surface_route_km2": 1.59,
|
||||
"building_count": 2516,
|
||||
"building_area": 2.65,
|
||||
"building_sizes": [0, 10, 50, 100, 200, 500, float('inf')],
|
||||
"building_size_counts": [120, 450, 780, 650, 400, 116],
|
||||
"compte_piste_cyclable": 42,
|
||||
"car_parking_capacity_provided": 491,
|
||||
"roundabout_count": 12,
|
||||
"mini_roundabout_count": 5,
|
||||
"surface_parking_km2": 0.35,
|
||||
"surface_bicycle_parking_km2": 0.042,
|
||||
"parking_with_capacity_count": 43,
|
||||
"capacity_bicycle_parking_provided": 125,
|
||||
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
|
||||
"charging_stations": 14,
|
||||
"charging_stations_with_capacity_count": 12,
|
||||
"charging_stations_capacity_provided": 28,
|
||||
"charging_points": 32,
|
||||
"date_generation": datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
}
|
||||
|
||||
with open('test_data.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(test_data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
return 'test_data.json'
|
||||
|
||||
def test_template(template_file):
|
||||
"""Teste la génération HTML avec un template spécifique."""
|
||||
test_json = create_test_data()
|
||||
output_html = f"resultat_{os.path.splitext(template_file)[0]}.html"
|
||||
|
||||
success = generate_html_from_json(test_json, output_html, template_file)
|
||||
|
||||
if success:
|
||||
print(f"✅ Test réussi avec {template_file} ! Fichier généré : {output_html}")
|
||||
else:
|
||||
print(f"❌ Échec du test avec {template_file}")
|
||||
|
||||
return success
|
||||
|
||||
def main():
|
||||
"""Fonction principale qui exécute les tests."""
|
||||
print("🧪 Démarrage des tests de génération de rapports HTML...")
|
||||
|
||||
# Test avec le template original
|
||||
test_template('template.html')
|
||||
|
||||
# Test avec le template amélioré
|
||||
test_template('template_ameliore.html.j2')
|
||||
|
||||
print("🏁 Tests terminés.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue