mirror of
https://forge.chapril.org/tykayn/parking-land
synced 2025-10-04 17:04:54 +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
|
# Analyse Urbaine - Générateur de Rapports et Cartes
|
||||||
calcul d'infos sur les nombre et les surfaces estimées sur le périmètre d'une ville selon les données OSM.
|
|
||||||
|
|
||||||
- Bâtiments
|
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.
|
||||||
- Routes
|
|
||||||
- Parking voiture et vélo
|
## Fonctionnalités
|
||||||
- Pistes cyclables
|
|
||||||
- Stations de recharge pour véhicule électrique
|
- 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,
|
"version": 0.6,
|
||||||
"generator": "Overpass API 0.7.62.5 1bd436f1",
|
"generator": "Overpass API 0.7.62.5 1bd436f1",
|
||||||
"osm3s": {
|
"osm3s": {
|
||||||
"timestamp_osm_base": "2025-02-19T10:34:45Z",
|
"timestamp_osm_base": "2025-03-16T11:21:10Z",
|
||||||
"timestamp_areas_base": "2024-12-31T00:49:33Z",
|
"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."
|
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
|
||||||
},
|
},
|
||||||
"elements": [
|
"elements": [
|
||||||
|
@ -356,6 +356,29 @@
|
||||||
"survey:date": "2024-04-26"
|
"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",
|
"type": "way",
|
||||||
"id": 16794777,
|
"id": 16794777,
|
||||||
|
@ -646,6 +669,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -680,6 +704,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed:type": "FR:rural",
|
"maxspeed:type": "FR:rural",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 97",
|
"ref": "D 97",
|
||||||
|
@ -718,6 +743,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"maxspeed:type": "FR:urban",
|
"maxspeed:type": "FR:urban",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -762,6 +788,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -812,6 +839,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -848,6 +876,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "secondary",
|
"highway": "secondary",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"ref": "D 131",
|
"ref": "D 131",
|
||||||
|
@ -3062,6 +3091,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "residential",
|
"highway": "residential",
|
||||||
|
"lane_markings": "no",
|
||||||
"name": "Chemin de Fontenay",
|
"name": "Chemin de Fontenay",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
|
@ -7383,6 +7413,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"highway": "tertiary",
|
"highway": "tertiary",
|
||||||
|
"lanes": "2",
|
||||||
"name": "Rue Marcel Quinet",
|
"name": "Rue Marcel Quinet",
|
||||||
"oneway": "no",
|
"oneway": "no",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -7434,6 +7465,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"destination": "Limours;Briis sous Forges",
|
"destination": "Limours;Briis sous Forges",
|
||||||
"highway": "tertiary_link",
|
"highway": "tertiary_link",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -11902,6 +11934,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "residential",
|
"highway": "residential",
|
||||||
|
"lane_markings": "no",
|
||||||
"name": "Chemin Derrière les Murs",
|
"name": "Chemin Derrière les Murs",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
|
@ -14150,7 +14183,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -14241,6 +14275,7 @@
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
"capacity": "5",
|
"capacity": "5",
|
||||||
"capacity:disabled": "1",
|
"capacity:disabled": "1",
|
||||||
|
"fee": "no",
|
||||||
"parking": "surface"
|
"parking": "surface"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14982,6 +15017,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"bridge": "yes",
|
"bridge": "yes",
|
||||||
"highway": "tertiary",
|
"highway": "tertiary",
|
||||||
|
"lanes": "2",
|
||||||
"layer": "1",
|
"layer": "1",
|
||||||
"maxweight": "3.5",
|
"maxweight": "3.5",
|
||||||
"name": "Rue André Piquet",
|
"name": "Rue André Piquet",
|
||||||
|
@ -21358,6 +21394,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"destination": "Gometz la Ville;Briis-Centre",
|
"destination": "Gometz la Ville;Briis-Centre",
|
||||||
"highway": "tertiary_link",
|
"highway": "tertiary_link",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -21395,6 +21432,7 @@
|
||||||
"cycleway:both": "lane",
|
"cycleway:both": "lane",
|
||||||
"cycleway:both:lane": "exclusive",
|
"cycleway:both:lane": "exclusive",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "2",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"ref": "D 97",
|
"ref": "D 97",
|
||||||
|
@ -22951,6 +22989,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "residential",
|
"highway": "residential",
|
||||||
|
"lane_markings": "no",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"name": "Place de la Ferme",
|
"name": "Place de la Ferme",
|
||||||
"source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2014 + Bing",
|
"source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2014 + Bing",
|
||||||
|
@ -132977,6 +133016,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"highway": "tertiary",
|
"highway": "tertiary",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "30",
|
"maxspeed": "30",
|
||||||
"name": "Rue Marcel Quinet",
|
"name": "Rue Marcel Quinet",
|
||||||
"oneway": "no",
|
"oneway": "no",
|
||||||
|
@ -152513,7 +152553,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "sett"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -154049,7 +154090,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "footway"
|
"highway": "footway",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160835,6 +160877,7 @@
|
||||||
"highway": "service",
|
"highway": "service",
|
||||||
"layer": "-1",
|
"layer": "-1",
|
||||||
"maxheight": "2.5",
|
"maxheight": "2.5",
|
||||||
|
"surface": "dirt",
|
||||||
"tunnel": "building_passage"
|
"tunnel": "building_passage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -161947,6 +161990,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "tertiary",
|
"highway": "tertiary",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"name": "Rue Marcel Quinet",
|
"name": "Rue Marcel Quinet",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -162402,6 +162446,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -162456,6 +162501,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -163240,7 +163286,8 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service",
|
"highway": "service",
|
||||||
"service": "parking_aisle"
|
"service": "parking_aisle",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -163883,7 +163930,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "footway"
|
"highway": "footway",
|
||||||
|
"surface": "concrete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -164876,6 +164924,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "residential",
|
"highway": "residential",
|
||||||
|
"lanes": "1",
|
||||||
"name": "Rue des Écoles",
|
"name": "Rue des Écoles",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref:FR:FANTOIR": "911110120B",
|
"ref:FR:FANTOIR": "911110120B",
|
||||||
|
@ -164946,7 +164995,8 @@
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"name": "Rue de la Fontaine de Ville",
|
"name": "Rue de la Fontaine de Ville",
|
||||||
"ref": "D 131",
|
"ref": "D 131",
|
||||||
"source": "Route500"
|
"source": "Route500",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -165063,7 +165113,8 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "secondary",
|
"highway": "secondary",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 131"
|
"ref": "D 131",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -166508,7 +166559,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -166963,7 +167015,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -169609,7 +169662,8 @@
|
||||||
"maxspeed": "30",
|
"maxspeed": "30",
|
||||||
"name": "Rue de la Fontaine de Ville",
|
"name": "Rue de la Fontaine de Ville",
|
||||||
"ref": "D 131",
|
"ref": "D 131",
|
||||||
"source": "Route500"
|
"source": "Route500",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -171220,7 +171274,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "paving_stones"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -171272,7 +171327,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "service"
|
"highway": "service",
|
||||||
|
"surface": "paving_stones"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -174065,7 +174121,8 @@
|
||||||
"name": "Rue de la Fontaine de Ville",
|
"name": "Rue de la Fontaine de Ville",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 131",
|
"ref": "D 131",
|
||||||
"source": "Route500"
|
"source": "Route500",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -176306,6 +176363,7 @@
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "tertiary_link",
|
"highway": "tertiary_link",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -178255,6 +178313,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "separate",
|
"cycleway:right": "separate",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -178398,6 +178457,7 @@
|
||||||
"cycleway:right": "separate",
|
"cycleway:right": "separate",
|
||||||
"destination": "Arpajon;Fontenay-les-Briis;Hôpital de Bligny",
|
"destination": "Arpajon;Fontenay-les-Briis;Hôpital de Bligny",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"maxspeed:type": "FR:urban",
|
"maxspeed:type": "FR:urban",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -178442,6 +178502,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"cycleway:right": "separate",
|
"cycleway:right": "separate",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
|
@ -178580,6 +178641,7 @@
|
||||||
"cycleway:right": "separate",
|
"cycleway:right": "separate",
|
||||||
"destination": "Limours;Forges-les-Bains",
|
"destination": "Limours;Forges-les-Bains",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed:type": "FR:rural",
|
"maxspeed:type": "FR:rural",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 97",
|
"ref": "D 97",
|
||||||
|
@ -178720,7 +178782,8 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "secondary",
|
"highway": "secondary",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 131"
|
"ref": "D 131",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -178763,7 +178826,8 @@
|
||||||
"name": "Rue de la Fontaine de Ville",
|
"name": "Rue de la Fontaine de Ville",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"ref": "D 131",
|
"ref": "D 131",
|
||||||
"source": "Route500"
|
"source": "Route500",
|
||||||
|
"surface": "asphalt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -180345,15 +180409,12 @@
|
||||||
"type": "way",
|
"type": "way",
|
||||||
"id": 1158192132,
|
"id": 1158192132,
|
||||||
"bounds": {
|
"bounds": {
|
||||||
"minlat": 48.6225876,
|
"minlat": 48.6227456,
|
||||||
"minlon": 2.1314359,
|
"minlon": 2.1314359,
|
||||||
"maxlat": 48.6228562,
|
"maxlat": 48.6228562,
|
||||||
"maxlon": 2.1329391
|
"maxlon": 2.1318718
|
||||||
},
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
10770983277,
|
|
||||||
10770983289,
|
|
||||||
10770983290,
|
|
||||||
6468287427,
|
6468287427,
|
||||||
10770983291,
|
10770983291,
|
||||||
10770983292,
|
10770983292,
|
||||||
|
@ -180362,18 +180423,6 @@
|
||||||
1355424050
|
1355424050
|
||||||
],
|
],
|
||||||
"geometry": [
|
"geometry": [
|
||||||
{
|
|
||||||
"lat": 48.6225876,
|
|
||||||
"lon": 2.1329391
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"lat": 48.6226315,
|
|
||||||
"lon": 2.1325837
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"lat": 48.6227772,
|
|
||||||
"lon": 2.1319269
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"lat": 48.622771,
|
"lat": 48.622771,
|
||||||
"lon": 2.1318718
|
"lon": 2.1318718
|
||||||
|
@ -180400,7 +180449,8 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": {
|
"tags": {
|
||||||
"highway": "footway"
|
"highway": "footway",
|
||||||
|
"surface": "dirt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -180966,6 +181016,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"destination": "Gometz la Ville;Briis-Centre",
|
"destination": "Gometz la Ville;Briis-Centre",
|
||||||
"highway": "tertiary_link",
|
"highway": "tertiary_link",
|
||||||
|
"lanes": "1",
|
||||||
"lit": "yes",
|
"lit": "yes",
|
||||||
"oneway": "yes",
|
"oneway": "yes",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -181082,6 +181133,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"highway": "tertiary",
|
"highway": "tertiary",
|
||||||
|
"lane_markings": "no",
|
||||||
"name": "Rue Marcel Quinet",
|
"name": "Rue Marcel Quinet",
|
||||||
"oneway": "no",
|
"oneway": "no",
|
||||||
"surface": "asphalt"
|
"surface": "asphalt"
|
||||||
|
@ -181650,6 +181702,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181695,6 +181748,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181731,7 +181785,8 @@
|
||||||
"crossing": "uncontrolled",
|
"crossing": "uncontrolled",
|
||||||
"crossing:markings": "surface",
|
"crossing:markings": "surface",
|
||||||
"footway": "crossing",
|
"footway": "crossing",
|
||||||
"highway": "footway"
|
"highway": "footway",
|
||||||
|
"surface": "paving_stones"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -181775,6 +181830,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181821,6 +181877,7 @@
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
"capacity": "4",
|
"capacity": "4",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181866,6 +181923,7 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181912,6 +181970,7 @@
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
"capacity": "2",
|
"capacity": "2",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -181958,6 +182017,7 @@
|
||||||
"access": "yes",
|
"access": "yes",
|
||||||
"amenity": "parking",
|
"amenity": "parking",
|
||||||
"capacity": "1",
|
"capacity": "1",
|
||||||
|
"fee": "no",
|
||||||
"orientation": "parallel",
|
"orientation": "parallel",
|
||||||
"parking": "street_side"
|
"parking": "street_side"
|
||||||
}
|
}
|
||||||
|
@ -182173,6 +182233,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182216,6 +182277,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182533,6 +182595,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182576,6 +182639,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182614,6 +182678,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182657,6 +182722,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lane_markings": "no",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -182705,6 +182771,7 @@
|
||||||
"cycleway:right": "lane",
|
"cycleway:right": "lane",
|
||||||
"highway": "primary",
|
"highway": "primary",
|
||||||
"junction": "roundabout",
|
"junction": "roundabout",
|
||||||
|
"lanes": "1",
|
||||||
"maxspeed": "50",
|
"maxspeed": "50",
|
||||||
"maxspeed:type": "sign",
|
"maxspeed:type": "sign",
|
||||||
"source": "Route500",
|
"source": "Route500",
|
||||||
|
@ -185976,6 +186043,87 @@
|
||||||
"tags": {
|
"tags": {
|
||||||
"building": "yes"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
84
present.py
84
present.py
|
@ -1,15 +1,53 @@
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
def generate_html_from_json(json_file, output_html):
|
def generate_html_from_json(json_file, output_html, template_file='template.html'):
|
||||||
with open(json_file, 'r') as file:
|
"""
|
||||||
|
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)
|
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
|
# Configuration de Jinja2
|
||||||
env = Environment(loader=FileSystemLoader('.'))
|
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
|
# Rendu du template avec les données
|
||||||
|
try:
|
||||||
html_content = template.render(
|
html_content = template.render(
|
||||||
city_name=data.get("city_name", "la ville"),
|
city_name=data.get("city_name", "la ville"),
|
||||||
longueur_route_km=data["longueur_route_km"],
|
longueur_route_km=data["longueur_route_km"],
|
||||||
|
@ -28,11 +66,45 @@ def generate_html_from_json(json_file, output_html):
|
||||||
charging_stations=data["charging_stations"],
|
charging_stations=data["charging_stations"],
|
||||||
charging_stations_with_capacity_count=data["charging_stations_with_capacity_count"],
|
charging_stations_with_capacity_count=data["charging_stations_with_capacity_count"],
|
||||||
charging_stations_capacity_provided=data["charging_stations_capacity_provided"],
|
charging_stations_capacity_provided=data["charging_stations_capacity_provided"],
|
||||||
charging_points=data["charging_points"]
|
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:
|
try:
|
||||||
|
with open(output_html, 'w', encoding='utf-8') as html_file:
|
||||||
html_file.write(html_content)
|
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__":
|
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,
|
"road_cycleway_km": 0.027059960352724084,
|
||||||
"compte_highways": 3526,
|
"compte_highways": 3530,
|
||||||
"surface_route_km2": 1.5981614843897296,
|
"surface_route_km2": 1.5983374668805956,
|
||||||
"building_count": 2515,
|
"building_count": 2516,
|
||||||
"building_area": 2.6535844764989233e-08,
|
"building_area": 2.6538178289989005e-08,
|
||||||
"building_sizes": [
|
"building_sizes": [
|
||||||
0,
|
0,
|
||||||
10,
|
10,
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
],
|
],
|
||||||
"building_size_counts": [
|
"building_size_counts": [
|
||||||
0,
|
0,
|
||||||
2515,
|
2516,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -28,10 +28,10 @@
|
||||||
"roundabout_count": 0,
|
"roundabout_count": 0,
|
||||||
"mini_roundabout_count": 0,
|
"mini_roundabout_count": 0,
|
||||||
"surface_parking_km2": 9820.0,
|
"surface_parking_km2": 9820.0,
|
||||||
"surface_bicycle_parking_km2": 3.6e-05,
|
"surface_bicycle_parking_km2": 4.2e-05,
|
||||||
"parking_with_capacity_count": 41,
|
"parking_with_capacity_count": 43,
|
||||||
"capacity_bicycle_parking_provided": 115,
|
"capacity_bicycle_parking_provided": 125,
|
||||||
"bicycle_parking_surface_from_capacity_provided_in_m2": 230,
|
"bicycle_parking_surface_from_capacity_provided_in_m2": 250,
|
||||||
"charging_stations": 4,
|
"charging_stations": 4,
|
||||||
"charging_stations_with_capacity_count": 4,
|
"charging_stations_with_capacity_count": 4,
|
||||||
"charging_stations_capacity_provided": 7,
|
"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