add computing from osm history
This commit is contained in:
parent
da60f964ab
commit
66bbce5e85
13 changed files with 3921 additions and 0 deletions
8
counting_osm_objects/.gitignore
vendored
Normal file
8
counting_osm_objects/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
osm_data/*pbf
|
||||
osm_data/*geojson
|
||||
polygons/*.poly
|
||||
test_data/*
|
||||
test_temp/*
|
||||
test_results/*
|
||||
osm_config.txt
|
||||
__pycache__
|
151
counting_osm_objects/README.md
Normal file
151
counting_osm_objects/README.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Counting OSM Objects
|
||||
|
||||
Ce répertoire contient des scripts pour compter et analyser les objets OpenStreetMap dans différentes zones
|
||||
administratives françaises.
|
||||
|
||||
Pour fonctionner vous aurez besoin du fichier historisé de la france, pour cela connectez vous à geofabrik avec votre
|
||||
compte osm (oui c'est relou).
|
||||
|
||||
## Scripts disponibles
|
||||
|
||||
activez le venv python et les dépendances
|
||||
|
||||
```shell
|
||||
python -m venv bin/venv activate
|
||||
source bin/venv/bin/activate
|
||||
pip install plotly pandas
|
||||
```
|
||||
|
||||
```shell
|
||||
# extraire un historique pour une ville à partir de l'historique de france et du polygone de la ville
|
||||
# ici pour paris on peut utiliser l'historique de l'ile de france
|
||||
wget https://osm-commerces.cipherbliss.com/admin/export_csv
|
||||
|
||||
py get_all_polys.py # ce qui utilise l'export csv des villes de osm mon commerce
|
||||
osmium extract -p polygons/commune_75056.poly -H osm_data/ile-de-france-internal.osh.pbf -s complete_ways -O -o commune_75056.osh.pbf
|
||||
```
|
||||
|
||||
```shell
|
||||
# générer les historiques de la commune 76216 pour tous ses thèmes et tous ses graphes
|
||||
py loop_thematics_history_in_zone_to_counts.py --input osm_data/commune_76216.osh.pbf --output-dir test_results --temp-dir test_temp --max-dates 100 --poly polygons/commune_76216.poly
|
||||
```
|
||||
|
||||
```shell
|
||||
# générer un graphe pour un thème dans une ville
|
||||
py generate_graph.py --insee 91111 test_results/commune_75056_building.csv
|
||||
Graphique sauvegardé: test_results/commune_75056_building_monthly_graph.png
|
||||
Graphique de complétion sauvegardé: test_results/commune_75056_building_monthly_graph_completion.png
|
||||
Graphiques générés avec succès!
|
||||
|
||||
```
|
||||
|
||||
### historize_zone.py
|
||||
|
||||
Ce script principal permet de lancer l'analyse historique d'une ville dans OpenStreetMap. Il:
|
||||
|
||||
1. Demande à l'utilisateur quelle ville il souhaite traiter
|
||||
2. Trouve le code INSEE de la ville demandée
|
||||
3. Vérifie si le polygone de la ville existe, sinon le récupère
|
||||
4. Traite les données historiques OSM pour cette ville en utilisant loop_thematics_history_in_zone_to_counts.py
|
||||
|
||||
#### Utilisation
|
||||
|
||||
```bash
|
||||
python historize_zone.py [--input fichier_historique.osh.pbf]
|
||||
```
|
||||
|
||||
Si le fichier d'historique n'est pas spécifié, le script utilisera par défaut le fichier
|
||||
`osm_data/france-internal.osh.pbf`.
|
||||
|
||||
#### Exemples
|
||||
|
||||
Lancer l'analyse avec le fichier d'historique par défaut:
|
||||
|
||||
```bash
|
||||
python historize_zone.py
|
||||
```
|
||||
|
||||
Lancer l'analyse avec un fichier d'historique spécifique:
|
||||
|
||||
```bash
|
||||
python historize_zone.py --input osm_data/ile-de-france-internal.osh.pbf
|
||||
```
|
||||
|
||||
### loop_thematics_history_in_zone_to_counts.py
|
||||
|
||||
Ce script compte les objets OSM par thématique sur une zone donnée à différentes dates. Il:
|
||||
|
||||
1. Filtre les données historiques OSM à différentes dates (mensuelles sur les 10 dernières années)
|
||||
2. Compte les objets correspondant à chaque thématique à chaque date
|
||||
3. Calcule le pourcentage de complétion des attributs importants pour chaque thème
|
||||
4. Sauvegarde les résultats dans des fichiers CSV
|
||||
5. Génère des graphiques montrant l'évolution dans le temps
|
||||
|
||||
#### Utilisation
|
||||
|
||||
```bash
|
||||
python loop_thematics_history_in_zone_to_counts.py --input fichier.osh.pbf --poly polygons/commune_XXXXX.poly
|
||||
```
|
||||
|
||||
Ce script est généralement appelé par historize_zone.py et ne nécessite pas d'être exécuté directement.
|
||||
|
||||
### get_poly.py
|
||||
|
||||
Ce script permet de récupérer le polygone d'une commune française à partir de son code INSEE. Il interroge l'API
|
||||
Overpass Turbo pour obtenir les limites administratives de la commune et sauvegarde le polygone dans un fichier au
|
||||
format .poly (compatible avec Osmosis).
|
||||
|
||||
#### Utilisation
|
||||
|
||||
```bash
|
||||
python get_poly.py [code_insee]
|
||||
```
|
||||
|
||||
Si le code INSEE n'est pas fourni en argument, le script le demandera interactivement.
|
||||
|
||||
#### Exemples
|
||||
|
||||
Récupérer le polygone de la commune d'Étampes (code INSEE 91111) :
|
||||
|
||||
```bash
|
||||
python get_poly.py 91111
|
||||
```
|
||||
|
||||
Le polygone sera sauvegardé dans le fichier `polygons/commune_91111.poly`.
|
||||
|
||||
#### Format de sortie
|
||||
|
||||
Le fichier de sortie est au format .poly, qui est utilisé par Osmosis et d'autres outils OpenStreetMap. Il contient :
|
||||
|
||||
- Le nom de la commune
|
||||
- Un numéro de section
|
||||
- Les coordonnées des points du polygone (longitude, latitude)
|
||||
- Des marqueurs "END" pour fermer le polygone et le fichier
|
||||
|
||||
Exemple de contenu :
|
||||
|
||||
```
|
||||
commune_91111
|
||||
1
|
||||
2.1326337 48.6556426
|
||||
2.1323684 48.6554398
|
||||
...
|
||||
2.1326337 48.6556426
|
||||
END
|
||||
END
|
||||
```
|
||||
|
||||
## Dépendances
|
||||
|
||||
- Python 3.6+
|
||||
- Modules Python : argparse, urllib, json
|
||||
- Pour compare_osm_objects.sh : PostgreSQL, curl, jq
|
||||
|
||||
## Installation
|
||||
|
||||
Aucune installation spécifique n'est nécessaire pour ces scripts. Assurez-vous simplement que les dépendances sont
|
||||
installées.
|
||||
|
||||
## Licence
|
||||
|
||||
Ces scripts sont distribués sous la même licence que le projet Osmose-Backend.
|
1050
counting_osm_objects/counting.sh
Executable file
1050
counting_osm_objects/counting.sh
Executable file
File diff suppressed because it is too large
Load diff
2
counting_osm_objects/dashboard_zone.py
Normal file
2
counting_osm_objects/dashboard_zone.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# on crée une page web présentant un tableau de bord pour la zone donnée
|
||||
# le dashboard contient le nom de la ville et des graphiques pour l'évolution de chaque thématique
|
395
counting_osm_objects/generate_graph.py
Executable file
395
counting_osm_objects/generate_graph.py
Executable file
|
@ -0,0 +1,395 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour générer un graphique montrant l'évolution du nombre d'objets OSM
|
||||
à partir d'un fichier CSV
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Génère un graphique à partir des données CSV d'objets OSM."
|
||||
)
|
||||
parser.add_argument(
|
||||
"csv_file", help="Chemin vers le fichier CSV contenant les données"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", "-o", help="Chemin de sortie pour le graphique (PNG)", default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
"--insee", "-i", help="Code INSEE de la commune à analyser", default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
"--period",
|
||||
"-p",
|
||||
help="Période à analyser (annual, monthly, daily)",
|
||||
default="monthly",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_data(csv_file, insee_code=None, period="monthly"):
|
||||
"""
|
||||
Charge les données depuis le fichier CSV.
|
||||
|
||||
Args:
|
||||
csv_file: Chemin vers le fichier CSV
|
||||
insee_code: Code INSEE de la commune à filtrer (optionnel)
|
||||
period: Période à analyser (annual, monthly, daily)
|
||||
|
||||
Returns:
|
||||
DataFrame pandas contenant les données filtrées
|
||||
"""
|
||||
# Charger le CSV avec gestion des erreurs pour les lignes mal formatées
|
||||
try:
|
||||
df = pd.read_csv(csv_file, error_bad_lines=False, warn_bad_lines=True)
|
||||
except TypeError: # Pour les versions plus récentes de pandas
|
||||
df = pd.read_csv(csv_file, on_bad_lines="skip")
|
||||
|
||||
# Vérifier si le CSV a la structure attendue
|
||||
if "date" in df.columns:
|
||||
# Format de CSV avec colonne 'date' directement
|
||||
try:
|
||||
df["date"] = pd.to_datetime(df["date"])
|
||||
except:
|
||||
# Si la conversion échoue, essayer différents formats
|
||||
try:
|
||||
if df["date"].iloc[0].count("-") == 2: # Format YYYY-MM-DD
|
||||
df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")
|
||||
elif df["date"].iloc[0].count("-") == 1: # Format YYYY-MM
|
||||
df["date"] = pd.to_datetime(df["date"], format="%Y-%m")
|
||||
else:
|
||||
df["date"] = pd.to_datetime(df["date"])
|
||||
except:
|
||||
print("Erreur: Impossible de convertir la colonne 'date'.")
|
||||
sys.exit(1)
|
||||
elif "periode" in df.columns:
|
||||
# Ancien format avec colonne 'periode'
|
||||
# Filtrer par période
|
||||
df = df[df["periode"] == period]
|
||||
|
||||
# Filtrer par code INSEE si spécifié
|
||||
if insee_code and "code_insee" in df.columns:
|
||||
df = df[df["code_insee"] == insee_code]
|
||||
|
||||
# Convertir les dates en objets datetime
|
||||
if period == "annual" and "annee" in df.columns:
|
||||
df["date"] = pd.to_datetime(df["annee"].astype(str), format="%Y")
|
||||
elif period == "monthly":
|
||||
# Vérifier si la première colonne contient déjà un format de date mensuel (YYYY-MM)
|
||||
first_col = df.columns[0]
|
||||
first_val = str(df.iloc[0, 0]) if not df.empty else ""
|
||||
|
||||
if first_col == "annee_mois" or (len(first_val) >= 7 and "-" in first_val):
|
||||
# Si la première colonne est 'annee_mois' ou contient une date au format YYYY-MM
|
||||
df["date"] = pd.to_datetime(df.iloc[:, 0].astype(str), format="%Y-%m")
|
||||
elif "annee" in df.columns:
|
||||
# Sinon, utiliser la colonne 'annee' et ajouter un mois fictif (janvier)
|
||||
df["date"] = pd.to_datetime(
|
||||
df["annee"].astype(str) + "-01", format="%Y-%m"
|
||||
)
|
||||
elif period == "daily":
|
||||
# Vérifier si la première colonne contient déjà un format de date quotidien (YYYY-MM-DD)
|
||||
first_col = df.columns[0]
|
||||
first_val = str(df.iloc[0, 0]) if not df.empty else ""
|
||||
|
||||
if first_col == "jour" or (
|
||||
len(first_val) >= 10 and first_val.count("-") == 2
|
||||
):
|
||||
# Si la première colonne est 'jour' ou contient une date au format YYYY-MM-DD
|
||||
df["date"] = pd.to_datetime(
|
||||
df.iloc[:, 0].astype(str), format="%Y-%m-%d"
|
||||
)
|
||||
elif "annee" in df.columns:
|
||||
# Sinon, utiliser la colonne 'annee' et ajouter un jour fictif (1er janvier)
|
||||
df["date"] = pd.to_datetime(
|
||||
df["annee"].astype(str) + "-01-01", format="%Y-%m-%d"
|
||||
)
|
||||
else:
|
||||
# Si aucune colonne de date n'est trouvée, essayer d'utiliser la première colonne
|
||||
try:
|
||||
df["date"] = pd.to_datetime(df.iloc[:, 0])
|
||||
except:
|
||||
print("Erreur: Impossible de trouver ou convertir une colonne de date.")
|
||||
sys.exit(1)
|
||||
|
||||
# Filtrer par code INSEE si spécifié et si la colonne 'zone' contient des codes INSEE
|
||||
if insee_code and "zone" in df.columns and not "code_insee" in df.columns:
|
||||
# Vérifier si la zone contient le code INSEE
|
||||
if any(
|
||||
zone.endswith(insee_code) for zone in df["zone"] if isinstance(zone, str)
|
||||
):
|
||||
df = df[df["zone"].str.endswith(insee_code)]
|
||||
|
||||
# Trier par date
|
||||
df = df.sort_values("date")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def generate_graph(df, output_path=None):
|
||||
"""
|
||||
Génère un graphique montrant l'évolution du nombre d'objets dans le temps.
|
||||
|
||||
Args:
|
||||
df: DataFrame pandas contenant les données
|
||||
output_path: Chemin de sortie pour le graphique (optionnel)
|
||||
"""
|
||||
# Créer une figure avec une taille adaptée
|
||||
plt.figure(figsize=(12, 8))
|
||||
|
||||
# Déterminer la colonne pour les types d'objets (type_objet ou theme)
|
||||
type_column = "type_objet" if "type_objet" in df.columns else "theme"
|
||||
label_objet = "objet"
|
||||
# Obtenir la liste des types d'objets uniques
|
||||
if type_column in df.columns:
|
||||
object_types = df[type_column].unique()
|
||||
|
||||
# Créer un graphique pour chaque type d'objet
|
||||
for obj_type in object_types:
|
||||
# Filtrer les données pour ce type d'objet
|
||||
obj_data = df[df[type_column] == obj_type]
|
||||
|
||||
# Filtrer les valeurs nulles
|
||||
obj_data_filtered = obj_data[obj_data["nombre_total"].notna()]
|
||||
|
||||
if not obj_data_filtered.empty:
|
||||
# Tracer la ligne pour le nombre total d'objets (sans marqueurs, avec courbe lissée)
|
||||
line, = plt.plot(
|
||||
obj_data_filtered["date"],
|
||||
obj_data_filtered["nombre_total"],
|
||||
linestyle="-",
|
||||
label=f"{obj_type}",
|
||||
)
|
||||
label_objet = obj_type
|
||||
|
||||
# Ajouter un remplissage pastel sous la courbe
|
||||
plt.fill_between(
|
||||
obj_data_filtered["date"],
|
||||
obj_data_filtered["nombre_total"],
|
||||
alpha=0.3,
|
||||
color=line.get_color()
|
||||
)
|
||||
else:
|
||||
# Si aucune colonne de type n'est trouvée, tracer simplement le nombre total
|
||||
# Filtrer les valeurs nulles
|
||||
df_filtered = df[df["nombre_total"].notna()]
|
||||
|
||||
if not df_filtered.empty:
|
||||
# Tracer la ligne pour le nombre total d'objets (sans marqueurs, avec courbe lissée)
|
||||
line, = plt.plot(
|
||||
df_filtered["date"],
|
||||
df_filtered["nombre_total"],
|
||||
linestyle="-",
|
||||
label="",
|
||||
)
|
||||
|
||||
# Ajouter un remplissage pastel sous la courbe
|
||||
plt.fill_between(
|
||||
df_filtered["date"],
|
||||
df_filtered["nombre_total"],
|
||||
alpha=0.3,
|
||||
color=line.get_color()
|
||||
)
|
||||
|
||||
# Configurer les axes et les légendes
|
||||
plt.xlabel("Date")
|
||||
plt.ylabel("Nombre d'objets")
|
||||
|
||||
plt.grid(True, linestyle="--", alpha=0.7)
|
||||
|
||||
# Formater l'axe des x pour afficher les dates correctement
|
||||
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
|
||||
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6))
|
||||
plt.gcf().autofmt_xdate()
|
||||
|
||||
# Ajouter une légende
|
||||
plt.legend()
|
||||
|
||||
zone_label = ""
|
||||
# Ajouter des informations sur la commune
|
||||
if "code_insee" in df.columns and len(df["code_insee"].unique()) == 1:
|
||||
insee_code = df["code_insee"].iloc[0]
|
||||
plt.figtext(0.02, 0.02, f"Commune: {insee_code}", fontsize=10)
|
||||
zone_label = insee_code
|
||||
elif "zone" in df.columns and len(df["zone"].unique()) == 1:
|
||||
zone = df["zone"].iloc[0]
|
||||
plt.figtext(0.02, 0.02, f"Zone: {zone}", fontsize=10)
|
||||
zone_label = zone
|
||||
|
||||
plt.title(f"{zone_label} : {label_objet} dans le temps")
|
||||
|
||||
# Ajouter la date de génération
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
plt.figtext(0.98, 0.02, f"Généré le: {now}", fontsize=8, ha="right")
|
||||
|
||||
# Ajuster la mise en page
|
||||
plt.tight_layout()
|
||||
|
||||
# Sauvegarder ou afficher le graphique
|
||||
if output_path:
|
||||
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
||||
print(f"Graphique sauvegardé: {output_path}")
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
|
||||
def generate_completion_graph(df, output_path=None):
|
||||
"""
|
||||
Génère un graphique montrant l'évolution du taux de complétion des attributs dans le temps.
|
||||
|
||||
Args:
|
||||
df: DataFrame pandas contenant les données
|
||||
output_path: Chemin de sortie pour le graphique (optionnel)
|
||||
"""
|
||||
# Vérifier si la colonne de pourcentage de complétion existe
|
||||
if "pourcentage_completion" not in df.columns:
|
||||
print(
|
||||
"Avertissement: La colonne 'pourcentage_completion' n'existe pas dans le CSV. Le graphique de complétion ne sera pas généré."
|
||||
)
|
||||
return
|
||||
|
||||
# Créer une figure avec une taille adaptée
|
||||
plt.figure(figsize=(12, 8))
|
||||
|
||||
# Déterminer la colonne pour les types d'objets (type_objet ou theme)
|
||||
type_column = "type_objet" if "type_objet" in df.columns else "theme"
|
||||
|
||||
# Obtenir la liste des types d'objets uniques
|
||||
if type_column in df.columns:
|
||||
object_types = df[type_column].unique()
|
||||
|
||||
# Créer un graphique pour chaque type d'objet
|
||||
for obj_type in object_types:
|
||||
# Filtrer les données pour ce type d'objet
|
||||
obj_data = df[df[type_column] == obj_type]
|
||||
|
||||
# Filtrer les valeurs nulles
|
||||
obj_data_filtered = obj_data[obj_data["pourcentage_completion"].notna()]
|
||||
|
||||
if not obj_data_filtered.empty:
|
||||
# Tracer la ligne pour le taux de complétion (sans marqueurs, avec courbe lissée)
|
||||
line, = plt.plot(
|
||||
obj_data_filtered["date"],
|
||||
obj_data_filtered["pourcentage_completion"],
|
||||
linestyle="-",
|
||||
label=f"{obj_type} - Complétion (%)",
|
||||
)
|
||||
|
||||
# Ajouter un remplissage pastel sous la courbe
|
||||
plt.fill_between(
|
||||
obj_data_filtered["date"],
|
||||
obj_data_filtered["pourcentage_completion"],
|
||||
alpha=0.3,
|
||||
color=line.get_color()
|
||||
)
|
||||
else:
|
||||
# Si aucune colonne de type n'est trouvée, tracer simplement le taux de complétion global
|
||||
# Filtrer les valeurs nulles
|
||||
df_filtered = df[df["pourcentage_completion"].notna()]
|
||||
|
||||
if not df_filtered.empty:
|
||||
# Tracer la ligne pour le taux de complétion (sans marqueurs, avec courbe lissée)
|
||||
line, = plt.plot(
|
||||
df_filtered["date"],
|
||||
df_filtered["pourcentage_completion"],
|
||||
linestyle="-",
|
||||
label="Complétion (%)",
|
||||
)
|
||||
|
||||
# Ajouter un remplissage pastel sous la courbe
|
||||
plt.fill_between(
|
||||
df_filtered["date"],
|
||||
df_filtered["pourcentage_completion"],
|
||||
alpha=0.3,
|
||||
color=line.get_color()
|
||||
)
|
||||
|
||||
# Configurer les axes et les légendes
|
||||
plt.xlabel("Date")
|
||||
plt.ylabel("Complétion (%)")
|
||||
plt.title("Évolution du taux de complétion dans le temps")
|
||||
plt.grid(True, linestyle="--", alpha=0.7)
|
||||
|
||||
# Définir les limites de l'axe y entre 0 et 100%
|
||||
plt.ylim(0, 100)
|
||||
|
||||
# Formater l'axe des x pour afficher les dates correctement
|
||||
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m"))
|
||||
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6))
|
||||
plt.gcf().autofmt_xdate()
|
||||
|
||||
# Ajouter une légende
|
||||
plt.legend()
|
||||
|
||||
# Ajouter des informations sur la commune
|
||||
if "code_insee" in df.columns and len(df["code_insee"].unique()) == 1:
|
||||
insee_code = df["code_insee"].iloc[0]
|
||||
plt.figtext(0.02, 0.02, f"Commune: {insee_code}", fontsize=10)
|
||||
elif "zone" in df.columns and len(df["zone"].unique()) == 1:
|
||||
zone = df["zone"].iloc[0]
|
||||
plt.figtext(0.02, 0.02, f"Zone: {zone}", fontsize=10)
|
||||
|
||||
# Ajouter la date de génération
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
plt.figtext(0.98, 0.02, f"Généré le: {now}", fontsize=8, ha="right")
|
||||
|
||||
# Ajuster la mise en page
|
||||
plt.tight_layout()
|
||||
|
||||
# Sauvegarder ou afficher le graphique
|
||||
if output_path:
|
||||
# Modifier le nom du fichier pour indiquer qu'il s'agit du taux de complétion
|
||||
base, ext = os.path.splitext(output_path)
|
||||
completion_path = f"{base}_completion{ext}"
|
||||
plt.savefig(completion_path, dpi=300, bbox_inches="tight")
|
||||
print(f"Graphique de complétion sauvegardé: {completion_path}")
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
|
||||
def main():
|
||||
"""Fonction principale."""
|
||||
# Analyser les arguments de la ligne de commande
|
||||
args = parse_args()
|
||||
|
||||
# Vérifier que le fichier CSV existe
|
||||
if not os.path.isfile(args.csv_file):
|
||||
print(f"Erreur: Le fichier {args.csv_file} n'existe pas.")
|
||||
sys.exit(1)
|
||||
|
||||
# Charger les données
|
||||
df = load_data(args.csv_file, args.insee, args.period)
|
||||
|
||||
# Vérifier qu'il y a des données
|
||||
if df.empty:
|
||||
print(f"Aucune donnée trouvée pour la période {args.period}.")
|
||||
sys.exit(1)
|
||||
|
||||
# Déterminer le chemin de sortie si non spécifié
|
||||
if not args.output:
|
||||
# Utiliser le même nom que le fichier CSV mais avec l'extension .png
|
||||
base_name = os.path.splitext(args.csv_file)[0]
|
||||
output_path = f"{base_name}_{args.period}_graph.png"
|
||||
else:
|
||||
output_path = args.output
|
||||
|
||||
# Générer les graphiques
|
||||
generate_graph(df, output_path)
|
||||
generate_completion_graph(df, output_path)
|
||||
|
||||
print("Graphiques générés avec succès!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
167
counting_osm_objects/get_all_polys.py
Executable file
167
counting_osm_objects/get_all_polys.py
Executable file
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour récupérer les polygones de toutes les communes françaises listées dans un fichier CSV.
|
||||
|
||||
Ce script:
|
||||
1. Ouvre le fichier osm-commerces-villes-export.csv
|
||||
2. Extrait les codes INSEE (colonne 'zone')
|
||||
3. Pour chaque code INSEE, vérifie si le polygone existe déjà
|
||||
4. Si non, utilise get_poly.py pour récupérer le polygone
|
||||
|
||||
Usage:
|
||||
python get_all_polys.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
from get_poly import query_overpass_api, extract_polygon, save_polygon_to_file
|
||||
|
||||
# Chemin vers le fichier CSV contenant les codes INSEE
|
||||
CSV_FILE = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "osm-commerces-villes-export.csv"
|
||||
)
|
||||
|
||||
# Chemin vers le dossier où sont stockés les polygones
|
||||
POLYGONS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "polygons")
|
||||
|
||||
|
||||
def ensure_polygons_dir_exists():
|
||||
"""
|
||||
Vérifie que le dossier 'polygons' existe, sinon le crée.
|
||||
"""
|
||||
os.makedirs(POLYGONS_DIR, exist_ok=True)
|
||||
print(f"Dossier de polygones: {POLYGONS_DIR}")
|
||||
|
||||
|
||||
def polygon_exists(insee_code):
|
||||
"""
|
||||
Vérifie si le polygone pour le code INSEE donné existe déjà.
|
||||
|
||||
Args:
|
||||
insee_code (str): Le code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
bool: True si le polygone existe, False sinon
|
||||
"""
|
||||
polygon_file = os.path.join(POLYGONS_DIR, f"commune_{insee_code}.poly")
|
||||
return os.path.isfile(polygon_file)
|
||||
|
||||
|
||||
def get_polygon(insee_code):
|
||||
"""
|
||||
Récupère le polygone pour le code INSEE donné.
|
||||
|
||||
Args:
|
||||
insee_code (str): Le code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
str: Le chemin du fichier polygone créé, ou None en cas d'erreur
|
||||
"""
|
||||
try:
|
||||
print(f"Récupération du polygone pour la commune {insee_code}...")
|
||||
|
||||
# Interroger l'API Overpass
|
||||
data = query_overpass_api(insee_code)
|
||||
|
||||
# Extraire le polygone
|
||||
polygon = extract_polygon(data)
|
||||
|
||||
# Sauvegarder le polygone dans un fichier
|
||||
output_file = save_polygon_to_file(polygon, insee_code)
|
||||
|
||||
print(f"Polygone pour la commune {insee_code} sauvegardé dans {output_file}")
|
||||
return output_file
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la récupération du polygone pour {insee_code}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def read_insee_codes_from_csv():
|
||||
"""
|
||||
Lit le fichier CSV et extrait les codes INSEE (colonne 'zone').
|
||||
|
||||
Returns:
|
||||
list: Liste des codes INSEE
|
||||
"""
|
||||
insee_codes = []
|
||||
|
||||
try:
|
||||
print(f"Lecture du fichier CSV: {CSV_FILE}")
|
||||
|
||||
if not os.path.isfile(CSV_FILE):
|
||||
print(f"Erreur: Le fichier {CSV_FILE} n'existe pas.")
|
||||
return insee_codes
|
||||
|
||||
with open(CSV_FILE, "r", encoding="utf-8") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
|
||||
for row in reader:
|
||||
if "zone" in row and row["zone"]:
|
||||
insee_codes.append(row["zone"])
|
||||
|
||||
print(f"Nombre de codes INSEE trouvés: {len(insee_codes)}")
|
||||
return insee_codes
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la lecture du fichier CSV: {e}")
|
||||
return insee_codes
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale du script.
|
||||
"""
|
||||
try:
|
||||
# S'assurer que le dossier des polygones existe
|
||||
ensure_polygons_dir_exists()
|
||||
|
||||
# Lire les codes INSEE depuis le fichier CSV
|
||||
insee_codes = read_insee_codes_from_csv()
|
||||
|
||||
if not insee_codes:
|
||||
print("Aucun code INSEE trouvé dans le fichier CSV.")
|
||||
return 1
|
||||
|
||||
# Compteurs pour les statistiques
|
||||
total = len(insee_codes)
|
||||
existing = 0
|
||||
created = 0
|
||||
failed = 0
|
||||
|
||||
# Pour chaque code INSEE, récupérer le polygone s'il n'existe pas déjà
|
||||
for i, insee_code in enumerate(insee_codes, 1):
|
||||
print(f"\nTraitement de la commune {i}/{total}: {insee_code}")
|
||||
|
||||
if polygon_exists(insee_code):
|
||||
print(f"Le polygone pour la commune {insee_code} existe déjà.")
|
||||
existing += 1
|
||||
continue
|
||||
|
||||
# Récupérer le polygone
|
||||
result = get_polygon(insee_code)
|
||||
|
||||
if result:
|
||||
created += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# Afficher les statistiques
|
||||
print("\nRésumé:")
|
||||
print(f"Total des communes traitées: {total}")
|
||||
print(f"Polygones déjà existants: {existing}")
|
||||
print(f"Polygones créés avec succès: {created}")
|
||||
print(f"Échecs: {failed}")
|
||||
|
||||
return 0 # Succès
|
||||
except KeyboardInterrupt:
|
||||
print("\nOpération annulée par l'utilisateur.")
|
||||
return 1 # Erreur
|
||||
except Exception as e:
|
||||
print(f"Erreur inattendue: {e}")
|
||||
return 1 # Erreur
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
287
counting_osm_objects/get_poly.py
Normal file
287
counting_osm_objects/get_poly.py
Normal file
|
@ -0,0 +1,287 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour récupérer le polygone d'une commune française à partir de son code INSEE.
|
||||
|
||||
Ce script:
|
||||
1. Demande un code INSEE
|
||||
2. Interroge l'API Overpass Turbo pour obtenir les limites administratives
|
||||
3. Extrait le polygone de la commune
|
||||
4. Sauvegarde le polygone dans un fichier
|
||||
|
||||
Usage:
|
||||
python get_poly.py [code_insee]
|
||||
|
||||
Si le code INSEE n'est pas fourni en argument, le script le demandera interactivement.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import argparse
|
||||
|
||||
|
||||
def get_insee_code():
|
||||
"""
|
||||
Récupère le code INSEE soit depuis les arguments de ligne de commande,
|
||||
soit en demandant à l'utilisateur.
|
||||
|
||||
Returns:
|
||||
str: Le code INSEE de la commune
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Récupère le polygone d'une commune à partir de son code INSEE"
|
||||
)
|
||||
parser.add_argument("insee", nargs="?", help="Code INSEE de la commune")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.insee:
|
||||
return args.insee
|
||||
|
||||
# Si le code INSEE n'est pas fourni en argument, le demander
|
||||
return input("Entrez le code INSEE de la commune: ")
|
||||
|
||||
|
||||
def query_overpass_api(insee_code):
|
||||
"""
|
||||
Interroge l'API Overpass pour obtenir les limites administratives d'une commune.
|
||||
|
||||
Args:
|
||||
insee_code (str): Le code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
dict: Les données GeoJSON de la commune
|
||||
"""
|
||||
print(f"Récupération des limites administratives pour la commune {insee_code}...")
|
||||
|
||||
# Construire la requête Overpass QL pour obtenir la relation administrative
|
||||
query = f"""
|
||||
[out:json][timeout:60];
|
||||
(
|
||||
relation["boundary"="administrative"]["admin_level"="8"]["ref:INSEE"="{insee_code}"];
|
||||
way(r);
|
||||
node(w);
|
||||
);
|
||||
out geom;
|
||||
"""
|
||||
|
||||
# Encoder la requête pour l'URL
|
||||
encoded_query = urllib.parse.quote(query)
|
||||
|
||||
# Construire l'URL de l'API Overpass
|
||||
url = f"https://overpass-api.de/api/interpreter?data={encoded_query}"
|
||||
|
||||
try:
|
||||
# Envoyer la requête à l'API
|
||||
print("Envoi de la requête à Overpass API...")
|
||||
with urllib.request.urlopen(url) as response:
|
||||
data = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
# Afficher des informations sur la réponse (version réduite pour production)
|
||||
print(
|
||||
f"Réponse reçue de l'API Overpass. Nombre d'éléments: {len(data.get('elements', []))}"
|
||||
)
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la requête à l'API Overpass: {e}")
|
||||
raise RuntimeError(f"Erreur lors de la requête à l'API Overpass: {e}")
|
||||
|
||||
|
||||
def extract_polygon(data):
|
||||
"""
|
||||
Extrait le polygone des données GeoJSON.
|
||||
|
||||
Args:
|
||||
data (dict): Les données GeoJSON de la commune
|
||||
|
||||
Returns:
|
||||
list: Liste des coordonnées du polygone
|
||||
"""
|
||||
print("Extraction du polygone des données...")
|
||||
|
||||
# Vérifier si des éléments ont été trouvés
|
||||
if not data.get("elements"):
|
||||
print("Aucune limite administrative trouvée pour ce code INSEE.")
|
||||
raise ValueError("Aucune limite administrative trouvée pour ce code INSEE.")
|
||||
|
||||
try:
|
||||
# Collecter tous les nœuds (points) avec leurs coordonnées
|
||||
nodes = {}
|
||||
for element in data["elements"]:
|
||||
if element["type"] == "node":
|
||||
nodes[element["id"]] = (element["lon"], element["lat"])
|
||||
|
||||
# Trouver les ways qui forment le contour de la commune
|
||||
ways = []
|
||||
for element in data["elements"]:
|
||||
if element["type"] == "way":
|
||||
ways.append(element)
|
||||
|
||||
# Si aucun way n'est trouvé, essayer d'extraire directement les coordonnées des nœuds
|
||||
if not ways and nodes:
|
||||
print("Aucun way trouvé. Utilisation directe des nœuds...")
|
||||
polygon = list(nodes.values())
|
||||
return polygon
|
||||
|
||||
# Trouver la relation administrative
|
||||
relation = None
|
||||
for element in data["elements"]:
|
||||
if (
|
||||
element["type"] == "relation"
|
||||
and element.get("tags", {}).get("boundary") == "administrative"
|
||||
):
|
||||
relation = element
|
||||
break
|
||||
|
||||
if not relation:
|
||||
print("Aucune relation administrative trouvée.")
|
||||
# Si nous avons des ways, nous pouvons essayer de les utiliser directement
|
||||
if ways:
|
||||
print("Tentative d'utilisation directe des ways...")
|
||||
# Prendre le premier way comme contour
|
||||
way = ways[0]
|
||||
polygon = []
|
||||
for node_id in way.get("nodes", []):
|
||||
if node_id in nodes:
|
||||
polygon.append(nodes[node_id])
|
||||
return polygon
|
||||
raise ValueError(
|
||||
"Impossible de trouver une relation administrative ou des ways"
|
||||
)
|
||||
|
||||
# Extraire les ways qui forment le contour extérieur de la relation
|
||||
outer_ways = []
|
||||
for member in relation.get("members", []):
|
||||
if member.get("role") == "outer" and member.get("type") == "way":
|
||||
# Trouver le way correspondant
|
||||
for way in ways:
|
||||
if way["id"] == member["ref"]:
|
||||
outer_ways.append(way)
|
||||
break
|
||||
|
||||
# Si aucun way extérieur n'est trouvé, utiliser tous les ways
|
||||
if not outer_ways:
|
||||
print("Aucun way extérieur trouvé. Utilisation de tous les ways...")
|
||||
outer_ways = ways
|
||||
|
||||
# Construire le polygone à partir des ways extérieurs
|
||||
polygon = []
|
||||
for way in outer_ways:
|
||||
for node_id in way.get("nodes", []):
|
||||
if node_id in nodes:
|
||||
polygon.append(nodes[node_id])
|
||||
|
||||
if not polygon:
|
||||
raise ValueError("Impossible d'extraire le polygone de la relation")
|
||||
|
||||
print(f"Polygone extrait avec {len(polygon)} points.")
|
||||
return polygon
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'extraction du polygone: {e}")
|
||||
raise RuntimeError(f"Erreur lors de l'extraction du polygone: {e}")
|
||||
|
||||
|
||||
def save_polygon_to_file(polygon, insee_code):
|
||||
"""
|
||||
Sauvegarde le polygone dans un fichier.
|
||||
|
||||
Args:
|
||||
polygon (list): Liste des coordonnées du polygone
|
||||
insee_code (str): Le code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
str: Le chemin du fichier créé
|
||||
|
||||
Raises:
|
||||
ValueError: Si le polygone est vide ou invalide
|
||||
IOError: Si une erreur survient lors de l'écriture du fichier
|
||||
"""
|
||||
if not polygon:
|
||||
raise ValueError("Le polygone est vide")
|
||||
|
||||
try:
|
||||
# Créer le répertoire de sortie s'il n'existe pas
|
||||
output_dir = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "polygons"
|
||||
)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Définir le nom du fichier de sortie
|
||||
output_file = os.path.join(output_dir, f"commune_{insee_code}.poly")
|
||||
|
||||
print(f"Sauvegarde du polygone dans le fichier {output_file}...")
|
||||
|
||||
# Écrire le polygone dans le fichier au format .poly (format utilisé par Osmosis)
|
||||
with open(output_file, "w") as f:
|
||||
f.write(f"commune_{insee_code}\n")
|
||||
f.write("1\n") # Numéro de section
|
||||
|
||||
# Écrire les coordonnées
|
||||
for i, (lon, lat) in enumerate(polygon):
|
||||
f.write(f" {lon:.7f} {lat:.7f}\n")
|
||||
|
||||
# Fermer le polygone en répétant le premier point
|
||||
if len(polygon) > 1 and polygon[0] != polygon[-1]:
|
||||
lon, lat = polygon[0]
|
||||
f.write(f" {lon:.7f} {lat:.7f}\n")
|
||||
|
||||
f.write("END\n")
|
||||
f.write("END\n")
|
||||
|
||||
print(f"Polygone sauvegardé avec succès dans {output_file}")
|
||||
return output_file
|
||||
except IOError as e:
|
||||
print(f"Erreur lors de l'écriture du fichier: {e}")
|
||||
raise # Re-raise the IOError
|
||||
except Exception as e:
|
||||
print(f"Erreur inattendue lors de la sauvegarde du polygone: {e}")
|
||||
raise RuntimeError(f"Erreur inattendue lors de la sauvegarde du polygone: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale du script.
|
||||
"""
|
||||
try:
|
||||
# Récupérer le code INSEE
|
||||
insee_code = get_insee_code()
|
||||
|
||||
# Vérifier que le code INSEE est valide (format numérique ou alphanumérique pour les DOM-TOM)
|
||||
if not insee_code:
|
||||
raise ValueError("Le code INSEE ne peut pas être vide")
|
||||
|
||||
if not insee_code.isalnum() or len(insee_code) not in [5, 3]:
|
||||
raise ValueError(
|
||||
"Code INSEE invalide. Il doit être composé de 5 chiffres (ou 3 pour certains territoires)."
|
||||
)
|
||||
|
||||
# Interroger l'API Overpass
|
||||
data = query_overpass_api(insee_code)
|
||||
|
||||
# Extraire le polygone
|
||||
polygon = extract_polygon(data)
|
||||
|
||||
# Sauvegarder le polygone dans un fichier
|
||||
output_file = save_polygon_to_file(polygon, insee_code)
|
||||
|
||||
print(
|
||||
f"Terminé. Le polygone de la commune {insee_code} a été sauvegardé dans {output_file}"
|
||||
)
|
||||
return 0 # Succès
|
||||
except ValueError as e:
|
||||
print(f"Erreur de validation: {e}")
|
||||
return 1 # Erreur
|
||||
except KeyboardInterrupt:
|
||||
print("\nOpération annulée par l'utilisateur.")
|
||||
return 1 # Erreur
|
||||
except Exception as e:
|
||||
print(f"Erreur inattendue: {e}")
|
||||
return 1 # Erreur
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
301
counting_osm_objects/historize_zone.py
Executable file
301
counting_osm_objects/historize_zone.py
Executable file
|
@ -0,0 +1,301 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script principal pour lancer l'analyse historique d'une ville.
|
||||
|
||||
Ce script:
|
||||
1. Demande à l'utilisateur quelle ville il souhaite traiter
|
||||
2. Trouve le code INSEE de la ville demandée
|
||||
3. Vérifie si le polygone de la ville existe, sinon le récupère
|
||||
4. Traite les données historiques OSM pour cette ville
|
||||
|
||||
Usage:
|
||||
python historize_zone.py [--input fichier_historique.osh.pbf]
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import argparse
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Chemin vers le répertoire du script
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Chemin vers le fichier CSV contenant les données des villes
|
||||
CITIES_CSV = os.path.join(SCRIPT_DIR, "osm-commerces-villes-export.csv")
|
||||
|
||||
# Chemin vers le répertoire des polygones
|
||||
POLYGONS_DIR = os.path.join(SCRIPT_DIR, "polygons")
|
||||
|
||||
# Chemin par défaut pour le fichier d'historique OSM France
|
||||
DEFAULT_HISTORY_FILE = os.path.join(SCRIPT_DIR, "osm_data", "france-internal.osh.pbf")
|
||||
|
||||
|
||||
def run_command(command):
|
||||
"""Exécute une commande shell et retourne la sortie"""
|
||||
print(f"Exécution: {command}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Erreur lors de l'exécution de la commande: {e}")
|
||||
print(f"Sortie de la commande: {e.stdout}")
|
||||
print(f"Erreur de la commande: {e.stderr}")
|
||||
return None
|
||||
|
||||
|
||||
def load_cities():
|
||||
"""
|
||||
Charge les données des villes depuis le fichier CSV.
|
||||
|
||||
Returns:
|
||||
dict: Dictionnaire des villes avec le nom comme clé et les données comme valeur
|
||||
"""
|
||||
cities = {}
|
||||
try:
|
||||
with open(CITIES_CSV, "r", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
if row.get("name") and row.get("zone"):
|
||||
cities[row["name"].lower()] = row
|
||||
except Exception as e:
|
||||
print(f"Erreur lors du chargement du fichier CSV des villes: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
return cities
|
||||
|
||||
|
||||
def find_city(city_name, cities):
|
||||
"""
|
||||
Recherche une ville par son nom dans le dictionnaire des villes.
|
||||
|
||||
Args:
|
||||
city_name (str): Nom de la ville à rechercher
|
||||
cities (dict): Dictionnaire des villes
|
||||
|
||||
Returns:
|
||||
dict: Données de la ville si trouvée, None sinon
|
||||
"""
|
||||
# Recherche exacte
|
||||
if city_name.lower() in cities:
|
||||
return cities[city_name.lower()]
|
||||
|
||||
# Recherche partielle
|
||||
matches = []
|
||||
for name, data in cities.items():
|
||||
if city_name.lower() in name:
|
||||
matches.append(data)
|
||||
|
||||
if not matches:
|
||||
return None
|
||||
|
||||
# Si plusieurs correspondances, demander à l'utilisateur de choisir
|
||||
if len(matches) > 1:
|
||||
print(f"Plusieurs villes correspondent à '{city_name}':")
|
||||
for i, city in enumerate(matches):
|
||||
print(f"{i+1}. {city['name']} (INSEE: {city['zone']})")
|
||||
|
||||
choice = input("Entrez le numéro de la ville souhaitée (ou 'q' pour quitter): ")
|
||||
if choice.lower() == "q":
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
index = int(choice) - 1
|
||||
if 0 <= index < len(matches):
|
||||
return matches[index]
|
||||
else:
|
||||
print("Choix invalide.")
|
||||
return None
|
||||
except ValueError:
|
||||
print("Veuillez entrer un numéro valide.")
|
||||
return None
|
||||
|
||||
return matches[0]
|
||||
|
||||
|
||||
def check_polygon_exists(insee_code):
|
||||
"""
|
||||
Vérifie si le polygone d'une commune existe déjà.
|
||||
|
||||
Args:
|
||||
insee_code (str): Code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
str: Chemin vers le fichier polygone s'il existe, None sinon
|
||||
"""
|
||||
poly_file = os.path.join(POLYGONS_DIR, f"commune_{insee_code}.poly")
|
||||
if os.path.isfile(poly_file):
|
||||
return poly_file
|
||||
return None
|
||||
|
||||
|
||||
def get_polygon(insee_code):
|
||||
"""
|
||||
Récupère le polygone d'une commune à partir de son code INSEE.
|
||||
|
||||
Args:
|
||||
insee_code (str): Code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
str: Chemin vers le fichier polygone créé, None en cas d'erreur
|
||||
"""
|
||||
get_poly_script = os.path.join(SCRIPT_DIR, "get_poly.py")
|
||||
command = f"python3 {get_poly_script} {insee_code}"
|
||||
|
||||
output = run_command(command)
|
||||
if output:
|
||||
# Vérifier si le polygone a été créé
|
||||
poly_file = check_polygon_exists(insee_code)
|
||||
if poly_file:
|
||||
return poly_file
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def process_city_history(input_file, poly_file, cleanup=False, benchmark=False):
|
||||
"""
|
||||
Traite l'historique OSM pour une ville.
|
||||
|
||||
Args:
|
||||
input_file (str): Chemin vers le fichier d'historique OSM
|
||||
poly_file (str): Chemin vers le fichier polygone de la ville
|
||||
cleanup (bool): Si True, nettoie les fichiers temporaires après traitement
|
||||
benchmark (bool): Si True, affiche des informations de performance détaillées
|
||||
|
||||
Returns:
|
||||
bool: True si le traitement a réussi, False sinon
|
||||
"""
|
||||
loop_script = os.path.join(
|
||||
SCRIPT_DIR, "loop_thematics_history_in_zone_to_counts.py"
|
||||
)
|
||||
output_dir = os.path.join(SCRIPT_DIR, "test_results")
|
||||
temp_dir = os.path.join(SCRIPT_DIR, "test_temp")
|
||||
|
||||
# Créer les répertoires de sortie si nécessaires
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
# Construire la commande avec les options supplémentaires
|
||||
command = f"python3 {loop_script} --input {input_file} --poly {poly_file} --output-dir {output_dir} --temp-dir {temp_dir}"
|
||||
|
||||
# Ajouter les options de nettoyage et de benchmark si activées
|
||||
if cleanup:
|
||||
command += " --cleanup"
|
||||
if benchmark:
|
||||
command += " --benchmark"
|
||||
|
||||
print(f"Exécution de la commande: {command}")
|
||||
output = run_command(command)
|
||||
if output is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Fonction principale"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Analyse historique d'une ville dans OpenStreetMap."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input",
|
||||
"-i",
|
||||
default=DEFAULT_HISTORY_FILE,
|
||||
help=f"Fichier d'historique OSM (.osh.pbf). Par défaut: {DEFAULT_HISTORY_FILE}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cleanup",
|
||||
"-c",
|
||||
action="store_true",
|
||||
help="Nettoyer les fichiers temporaires après traitement",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--benchmark",
|
||||
"-b",
|
||||
action="store_true",
|
||||
help="Afficher des informations de performance détaillées",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--city",
|
||||
"-v",
|
||||
help="Nom de la ville à traiter (si non spécifié, demande interactive)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Vérifier que le fichier d'historique existe
|
||||
if not os.path.isfile(args.input):
|
||||
print(f"Erreur: Le fichier d'historique {args.input} n'existe pas.")
|
||||
print(
|
||||
f"Veuillez spécifier un fichier d'historique valide avec l'option --input."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Charger les données des villes
|
||||
cities = load_cities()
|
||||
if not cities:
|
||||
print("Aucune ville n'a été trouvée dans le fichier CSV.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Données chargées pour {len(cities)} villes.")
|
||||
|
||||
# Obtenir le nom de la ville à traiter
|
||||
city_name = args.city
|
||||
if not city_name:
|
||||
# Mode interactif si aucune ville n'est spécifiée
|
||||
city_name = input("Quelle ville souhaitez-vous traiter ? ")
|
||||
|
||||
# Rechercher la ville
|
||||
city = find_city(city_name, cities)
|
||||
if not city:
|
||||
print(f"Aucune ville correspondant à '{city_name}' n'a été trouvée.")
|
||||
sys.exit(1)
|
||||
|
||||
insee_code = city["zone"]
|
||||
print(f"Ville trouvée: {city['name']} (INSEE: {insee_code})")
|
||||
|
||||
# Vérifier si le polygone existe
|
||||
poly_file = check_polygon_exists(insee_code)
|
||||
if poly_file:
|
||||
print(f"Le polygone pour {city['name']} existe déjà: {poly_file}")
|
||||
else:
|
||||
print(f"Le polygone pour {city['name']} n'existe pas. Récupération en cours...")
|
||||
poly_file = get_polygon(insee_code)
|
||||
if not poly_file:
|
||||
print(f"Erreur: Impossible de récupérer le polygone pour {city['name']}.")
|
||||
sys.exit(1)
|
||||
print(f"Polygone récupéré avec succès: {poly_file}")
|
||||
|
||||
# Afficher les options activées
|
||||
if args.benchmark:
|
||||
print("\n=== Options ===")
|
||||
print(
|
||||
f"Nettoyage des fichiers temporaires: {'Activé' if args.cleanup else 'Désactivé'}"
|
||||
)
|
||||
print(f"Benchmark: Activé")
|
||||
print("===============\n")
|
||||
|
||||
# Traiter l'historique pour cette ville
|
||||
print(f"Traitement de l'historique OSM pour {city['name']}...")
|
||||
success = process_city_history(args.input, poly_file, args.cleanup, args.benchmark)
|
||||
|
||||
if success:
|
||||
print(f"Traitement terminé avec succès pour {city['name']}.")
|
||||
else:
|
||||
print(f"Erreur lors du traitement de l'historique pour {city['name']}.")
|
||||
sys.exit(1)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
945
counting_osm_objects/loop_thematics_history_in_zone_to_counts.py
Normal file
945
counting_osm_objects/loop_thematics_history_in_zone_to_counts.py
Normal file
|
@ -0,0 +1,945 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour compter les objets OSM par thématique sur une zone donnée à différentes dates.
|
||||
|
||||
Ce script utilise osmium pour:
|
||||
1. Filtrer les données historiques OSM à différentes dates (mensuelles sur les 10 dernières années)
|
||||
2. Compter les objets correspondant à chaque thématique à chaque date
|
||||
3. Calculer le pourcentage de complétion des attributs importants pour chaque thème
|
||||
4. Sauvegarder les résultats dans des fichiers CSV
|
||||
5. Générer des graphiques montrant l'évolution dans le temps
|
||||
|
||||
Exemple d'utilisation:
|
||||
python3 loop_thematics_history_in_zone_to_counts.py --input france.osh.pbf --poly polygons/commune_91111.poly
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import csv
|
||||
import json
|
||||
import argparse
|
||||
import multiprocessing
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from functools import lru_cache
|
||||
import time
|
||||
|
||||
# Définition des thématiques et leurs tags correspondants (repris de split_history_to_thematics.py)
|
||||
THEMES = {
|
||||
"borne-de-recharge": {
|
||||
"tag_filter": "amenity=charging_station",
|
||||
"important_tags": ["operator", "capacity"],
|
||||
},
|
||||
"borne-incendie": {
|
||||
"tag_filter": "emergency=fire_hydrant",
|
||||
"important_tags": ["ref", "colour"],
|
||||
},
|
||||
"arbres": {
|
||||
"tag_filter": "natural=tree",
|
||||
"important_tags": ["species", "leaf_type"],
|
||||
},
|
||||
"defibrillator": {
|
||||
"tag_filter": "emergency=defibrillator",
|
||||
"important_tags": ["operator", "access"],
|
||||
},
|
||||
"toilets": {
|
||||
"tag_filter": "amenity=toilets",
|
||||
"important_tags": ["access", "wheelchair"],
|
||||
},
|
||||
"bus_stop": {
|
||||
"tag_filter": "highway=bus_stop",
|
||||
"important_tags": ["name", "shelter"],
|
||||
},
|
||||
"camera": {
|
||||
"tag_filter": "man_made=surveillance",
|
||||
"important_tags": ["operator", "surveillance"],
|
||||
},
|
||||
"recycling": {
|
||||
"tag_filter": "amenity=recycling",
|
||||
"important_tags": ["recycling_type", "operator"],
|
||||
},
|
||||
"substation": {
|
||||
"tag_filter": "power=substation",
|
||||
"important_tags": ["operator", "voltage"],
|
||||
},
|
||||
"laboratory": {
|
||||
"tag_filter": "healthcare=laboratory",
|
||||
"important_tags": ["name", "operator"],
|
||||
},
|
||||
"school": {"tag_filter": "amenity=school", "important_tags": ["name", "operator"]},
|
||||
"police": {"tag_filter": "amenity=police", "important_tags": ["name", "operator"]},
|
||||
"healthcare": {
|
||||
"tag_filter": "healthcare or amenity=doctors or amenity=pharmacy or amenity=hospital or amenity=clinic or amenity=social_facility",
|
||||
"important_tags": ["name", "healthcare"],
|
||||
},
|
||||
"bicycle_parking": {
|
||||
"tag_filter": "amenity=bicycle_parking",
|
||||
"important_tags": ["capacity", "covered"],
|
||||
},
|
||||
"advertising_board": {
|
||||
"tag_filter": "advertising=board and message=political",
|
||||
"important_tags": ["operator", "content"],
|
||||
},
|
||||
"building": {
|
||||
"tag_filter": "building",
|
||||
"important_tags": ["building", "addr:housenumber"],
|
||||
},
|
||||
"email": {
|
||||
"tag_filter": "email or contact:email",
|
||||
"important_tags": ["email", "contact:email"],
|
||||
},
|
||||
"bench": {
|
||||
"tag_filter": "amenity=bench",
|
||||
# "important_tags": ["backrest", "material"]
|
||||
},
|
||||
"waste_basket": {
|
||||
"tag_filter": "amenity=waste_basket",
|
||||
# "important_tags": ["operator", "capacity"]
|
||||
},
|
||||
"street_lamp": {
|
||||
"tag_filter": "highway=street_lamp",
|
||||
# "important_tags": ["light:method", "operator"]
|
||||
},
|
||||
"drinking_water": {
|
||||
"tag_filter": "amenity=drinking_water",
|
||||
# "important_tags": ["drinking_water", "bottle"]
|
||||
},
|
||||
"power_pole": {
|
||||
"tag_filter": "power=pole",
|
||||
# "important_tags": ["ref", "operator"]
|
||||
},
|
||||
"manhole": {
|
||||
"tag_filter": "man_made=manhole",
|
||||
# "important_tags": ["man_made", "substance"]
|
||||
},
|
||||
"little_free_library": {
|
||||
"tag_filter": "amenity=public_bookcase",
|
||||
# "important_tags": ["amenity", "capacity"]
|
||||
},
|
||||
"playground": {
|
||||
"tag_filter": "leisure=playground",
|
||||
# "important_tags": ["name", "surface"]
|
||||
},
|
||||
"siret": {
|
||||
"tag_filter": "ref:FR:SIRET",
|
||||
# "important_tags": ["name", "surface"]
|
||||
},
|
||||
"restaurants": {
|
||||
"tag_filter": "amenity=restaurant",
|
||||
"important_tags": ["opening_hours", "contact:street", "contact:housenumber", "website", "contact:phone"]
|
||||
},
|
||||
"rnb": {
|
||||
"tag_filter": "ref:FR:RNB",
|
||||
# "important_tags": ["name", "surface"]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def run_command(command):
|
||||
"""Exécute une commande shell et retourne la sortie"""
|
||||
print(f"Exécution: {command}")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
return result.stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Erreur lors de l'exécution de la commande: {e}")
|
||||
print(f"Sortie de la commande: {e.stdout}")
|
||||
print(f"Erreur de la commande: {e.stderr}")
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def run_command_cached(command):
|
||||
"""Version mise en cache de run_command pour éviter les appels redondants"""
|
||||
return run_command(command)
|
||||
|
||||
|
||||
def count_objects_at_date(
|
||||
input_file, date_str, tag_filter, output_dir=None, insee_code=None
|
||||
):
|
||||
"""
|
||||
Compte les objets correspondant à un filtre de tag à une date spécifique.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
date_str: Date au format ISO (YYYY-MM-DDThh:mm:ssZ)
|
||||
tag_filter: Filtre de tag (ex: "amenity=charging_station")
|
||||
output_dir: Répertoire de sortie pour les fichiers temporaires
|
||||
insee_code: Code INSEE de la commune (optionnel)
|
||||
|
||||
Returns:
|
||||
Nombre d'objets correspondant au filtre à la date spécifiée
|
||||
"""
|
||||
# Créer un répertoire temporaire si nécessaire
|
||||
if output_dir:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
insee_suffix = f"_insee_{insee_code}" if insee_code else ""
|
||||
temp_file = os.path.join(
|
||||
output_dir, f"temp_{date_str.replace(':', '_')}__{insee_suffix}.osm.pbf"
|
||||
)
|
||||
|
||||
# Vérifier si le fichier temporaire existe déjà
|
||||
if not os.path.exists(temp_file):
|
||||
time_filter_cmd = f"osmium time-filter {input_file} {date_str} -O -o {temp_file} -f osm.pbf"
|
||||
run_command(time_filter_cmd)
|
||||
|
||||
tags_count_cmd = f"osmium tags-count {temp_file} -F osm.pbf {tag_filter}"
|
||||
else:
|
||||
# Utiliser des pipes comme dans l'exemple
|
||||
tags_count_cmd = f"osmium time-filter {input_file} {date_str} -O -o - -f osm.pbf | osmium tags-count - -F osm.pbf {tag_filter}"
|
||||
|
||||
# Exécuter la commande et récupérer le résultat (utiliser la version mise en cache)
|
||||
output = run_command_cached(tags_count_cmd)
|
||||
|
||||
# Analyser la sortie pour obtenir le nombre d'objets
|
||||
if output:
|
||||
# La sortie d'osmium tags-count est au format "count "key" "value"" ou "count "key""
|
||||
# Par exemple: "42 "amenity" "charging_station"" ou "42 "operator""
|
||||
parts = output.strip().split()
|
||||
if len(parts) >= 1:
|
||||
try:
|
||||
return int(parts[0])
|
||||
except ValueError:
|
||||
print(f"Impossible de convertir '{parts[0]}' en entier")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def export_to_geojson(
|
||||
input_file, date_str, main_tag_filter, output_dir, insee_code=None
|
||||
):
|
||||
"""
|
||||
Exporte les données OSM filtrées à une date spécifique vers un fichier GeoJSON.
|
||||
Si le fichier GeoJSON ou les fichiers temporaires existent déjà, l'export est ignoré.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
date_str: Date au format ISO (YYYY-MM-DDThh:mm:ssZ)
|
||||
main_tag_filter: Filtre de tag principal (ex: "amenity=charging_station")
|
||||
output_dir: Répertoire pour les fichiers temporaires
|
||||
insee_code: Code INSEE de la commune (optionnel)
|
||||
|
||||
Returns:
|
||||
Chemin vers le fichier GeoJSON créé
|
||||
"""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Ajouter le code INSEE au nom du fichier si disponible
|
||||
insee_suffix = f"_insee_{insee_code}" if insee_code else ""
|
||||
|
||||
# Définir le chemin du fichier GeoJSON de sortie
|
||||
geojson_file = os.path.join(
|
||||
output_dir, f"export_{date_str.replace(':', '_')}__{insee_suffix}.geojson"
|
||||
)
|
||||
|
||||
# Vérifier si le fichier GeoJSON existe déjà
|
||||
if os.path.exists(geojson_file):
|
||||
return geojson_file
|
||||
|
||||
# Définir les chemins des fichiers temporaires
|
||||
temp_file = os.path.join(
|
||||
output_dir, f"temp_{date_str.replace(':', '_')}__{insee_suffix}.osm.pbf"
|
||||
)
|
||||
filtered_file = os.path.join(
|
||||
output_dir,
|
||||
f"filtered_{date_str.replace(':', '_')}__{main_tag_filter.replace('=', '_').replace(' ', '_')}__{insee_suffix}.osm.pbf",
|
||||
)
|
||||
|
||||
# Vérifier si le fichier temporaire filtré par date existe déjà
|
||||
if not os.path.exists(temp_file):
|
||||
# Créer un fichier temporaire pour les données filtrées par date
|
||||
time_filter_cmd = (
|
||||
f"osmium time-filter {input_file} {date_str} -O -o {temp_file} -f osm.pbf"
|
||||
)
|
||||
run_command_cached(time_filter_cmd)
|
||||
|
||||
# Vérifier si le fichier filtré par tag existe déjà
|
||||
if not os.path.exists(filtered_file):
|
||||
# Filtrer les objets qui ont le tag principal
|
||||
filter_cmd = f"osmium tags-filter {temp_file} {main_tag_filter} -f osm.pbf -O -o {filtered_file}"
|
||||
run_command_cached(filter_cmd)
|
||||
|
||||
# Exporter vers GeoJSON
|
||||
export_cmd = f"osmium export {filtered_file} -O -o {geojson_file} -f geojson --geometry-types point,linestring,polygon"
|
||||
run_command_cached(export_cmd)
|
||||
|
||||
return geojson_file
|
||||
|
||||
|
||||
def count_features_with_tags_in_geojson(geojson_file, attribute_tags):
|
||||
"""
|
||||
Compte les features dans un fichier GeoJSON qui ont des attributs spécifiques.
|
||||
|
||||
Args:
|
||||
geojson_file: Chemin vers le fichier GeoJSON
|
||||
attribute_tags: Liste d'attributs à vérifier (ex: ["operator", "capacity"])
|
||||
|
||||
Returns:
|
||||
Dictionnaire avec le nombre de features ayant chaque attribut spécifié
|
||||
"""
|
||||
try:
|
||||
with open(geojson_file, "r") as f:
|
||||
geojson_data = json.load(f)
|
||||
|
||||
# Initialiser les compteurs pour chaque attribut
|
||||
counts = {tag: 0 for tag in attribute_tags}
|
||||
|
||||
# Compter en une seule passe
|
||||
for feature in geojson_data.get("features", []):
|
||||
properties = feature.get("properties", {})
|
||||
for tag in attribute_tags:
|
||||
if tag in properties and properties[tag]:
|
||||
counts[tag] += 1
|
||||
|
||||
return counts
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Erreur lors du comptage des features avec les attributs {attribute_tags}: {e}"
|
||||
)
|
||||
return {tag: None for tag in attribute_tags}
|
||||
|
||||
|
||||
def count_objects_with_tags(
|
||||
input_file,
|
||||
date_str,
|
||||
main_tag_filter,
|
||||
attribute_tags,
|
||||
output_dir=None,
|
||||
insee_code=None,
|
||||
):
|
||||
"""
|
||||
Compte les objets qui ont à la fois le tag principal et des attributs spécifiques.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
date_str: Date au format ISO (YYYY-MM-DDThh:mm:ssZ)
|
||||
main_tag_filter: Filtre de tag principal (ex: "amenity=charging_station")
|
||||
attribute_tags: Liste d'attributs à vérifier (ex: ["operator", "capacity"])
|
||||
output_dir: Répertoire pour les fichiers temporaires
|
||||
insee_code: Code INSEE de la commune (optionnel)
|
||||
|
||||
Returns:
|
||||
Dictionnaire avec le nombre d'objets ayant chaque attribut spécifié
|
||||
"""
|
||||
# Utiliser l'export GeoJSON si un répertoire de sortie est spécifié
|
||||
if output_dir:
|
||||
# Exporter vers GeoJSON (la fonction export_to_geojson vérifie déjà si le fichier GeoJSON existe)
|
||||
geojson_file = export_to_geojson(
|
||||
input_file, date_str, main_tag_filter, output_dir, insee_code
|
||||
)
|
||||
|
||||
# Compter les features avec les attributs spécifiques en une seule passe
|
||||
return count_features_with_tags_in_geojson(geojson_file, attribute_tags)
|
||||
else:
|
||||
# Méthode alternative si pas de répertoire temporaire
|
||||
counts = {}
|
||||
for tag in attribute_tags:
|
||||
combined_filter = f"{main_tag_filter} and {tag}"
|
||||
counts[tag] = count_objects_at_date(
|
||||
input_file, date_str, combined_filter, output_dir, insee_code
|
||||
)
|
||||
return counts
|
||||
|
||||
|
||||
def count_objects_with_tag(
|
||||
input_file,
|
||||
date_str,
|
||||
main_tag_filter,
|
||||
attribute_tag,
|
||||
output_dir=None,
|
||||
insee_code=None,
|
||||
):
|
||||
"""
|
||||
Compte les objets qui ont à la fois le tag principal et un attribut spécifique.
|
||||
Version compatible avec l'ancienne API pour la rétrocompatibilité.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
date_str: Date au format ISO (YYYY-MM-DDThh:mm:ssZ)
|
||||
main_tag_filter: Filtre de tag principal (ex: "amenity=charging_station")
|
||||
attribute_tag: Attribut à vérifier (ex: "operator")
|
||||
output_dir: Répertoire pour les fichiers temporaires
|
||||
insee_code: Code INSEE de la commune (optionnel)
|
||||
|
||||
Returns:
|
||||
Nombre d'objets ayant à la fois le tag principal et l'attribut spécifié
|
||||
"""
|
||||
result = count_objects_with_tags(
|
||||
input_file, date_str, main_tag_filter, [attribute_tag], output_dir, insee_code
|
||||
)
|
||||
return result.get(attribute_tag, None)
|
||||
|
||||
|
||||
def generate_time_slices(max_dates=None):
|
||||
"""
|
||||
Génère une liste de dates avec différentes fréquences selon l'ancienneté:
|
||||
- 30 derniers jours: quotidien
|
||||
- 12 derniers mois: mensuel
|
||||
- Jusqu'à 2004: annuel
|
||||
|
||||
Args:
|
||||
max_dates: Nombre maximum de dates à générer (pour les tests)
|
||||
|
||||
Returns:
|
||||
Liste de dates au format ISO (YYYY-MM-DDThh:mm:ssZ)
|
||||
"""
|
||||
dates = []
|
||||
now = datetime.now()
|
||||
|
||||
# 1. Dates quotidiennes pour les 30 derniers jours
|
||||
current_date = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
for _ in range(30):
|
||||
date_str = current_date.strftime("%Y-%m-%dT00:00:00Z")
|
||||
dates.append(date_str)
|
||||
current_date -= timedelta(days=1)
|
||||
|
||||
# 2. Dates mensuelles pour les 12 derniers mois (en excluant le mois courant déjà couvert)
|
||||
current_date = datetime(now.year, now.month, 1) - timedelta(
|
||||
days=1
|
||||
) # Dernier jour du mois précédent
|
||||
current_date = datetime(
|
||||
current_date.year, current_date.month, 1
|
||||
) # Premier jour du mois précédent
|
||||
|
||||
for _ in range(
|
||||
60
|
||||
): # 11 mois supplémentaires (12 mois au total avec le mois courant)
|
||||
date_str = current_date.strftime("%Y-%m-%dT00:00:00Z")
|
||||
if date_str not in dates: # Éviter les doublons
|
||||
dates.append(date_str)
|
||||
|
||||
# Passer au mois précédent
|
||||
if current_date.month == 1:
|
||||
current_date = datetime(current_date.year - 1, 12, 1)
|
||||
else:
|
||||
current_date = datetime(current_date.year, current_date.month - 1, 1)
|
||||
|
||||
# 3. Dates annuelles de l'année précédente jusqu'à 2004
|
||||
start_year = min(
|
||||
now.year - 1, 2023
|
||||
) # Commencer à l'année précédente (ou 2023 si nous sommes en 2024)
|
||||
for year in range(start_year, 2003, -1):
|
||||
date_str = f"{year}-01-01T00:00:00Z"
|
||||
if date_str not in dates: # Éviter les doublons
|
||||
dates.append(date_str)
|
||||
|
||||
# Limiter le nombre de dates si spécifié
|
||||
if max_dates is not None and max_dates > 0:
|
||||
dates = dates[:max_dates]
|
||||
|
||||
# Trier les dates par ordre chronologique
|
||||
dates.sort()
|
||||
|
||||
return dates
|
||||
|
||||
|
||||
def extract_zone_data(input_file, poly_file, output_dir):
|
||||
"""
|
||||
Extrait les données pour une zone spécifique à partir d'un fichier d'historique OSM.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
poly_file: Fichier de polygone (.poly) définissant la zone
|
||||
output_dir: Répertoire de sortie
|
||||
|
||||
Returns:
|
||||
Chemin vers le fichier extrait
|
||||
"""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Obtenir le nom de la zone à partir du nom du fichier poly
|
||||
zone_name = Path(poly_file).stem
|
||||
|
||||
# Créer le fichier de sortie
|
||||
output_file = os.path.join(output_dir, f"{zone_name}.osh.pbf")
|
||||
|
||||
# Exécuter la commande osmium extract seulement si le fichier n'existe pas déjà
|
||||
if os.path.exists(output_file):
|
||||
print(f"Le fichier {output_file} existe déjà, utilisation du fichier existant.")
|
||||
else:
|
||||
print(f"Extraction des données pour la zone {zone_name}...")
|
||||
command = f"osmium extract -p {poly_file} -H {input_file} -O -o {output_file}"
|
||||
run_command(command)
|
||||
|
||||
return output_file
|
||||
|
||||
|
||||
def process_theme(
|
||||
theme_name,
|
||||
theme_info,
|
||||
zone_file,
|
||||
zone_name,
|
||||
dates,
|
||||
output_dir,
|
||||
temp_dir,
|
||||
insee_code=None,
|
||||
):
|
||||
"""
|
||||
Traite une thématique spécifique pour une zone donnée.
|
||||
|
||||
Args:
|
||||
theme_name: Nom de la thématique
|
||||
theme_info: Informations sur la thématique (tag_filter, important_tags)
|
||||
zone_file: Fichier de la zone à traiter
|
||||
zone_name: Nom de la zone
|
||||
dates: Liste des dates à traiter
|
||||
output_dir: Répertoire pour les fichiers de sortie
|
||||
temp_dir: Répertoire pour les fichiers temporaires
|
||||
insee_code: Code INSEE de la commune (optionnel)
|
||||
|
||||
Returns:
|
||||
Chemin vers le fichier CSV généré
|
||||
"""
|
||||
start_time = time.time()
|
||||
print(f"Traitement de la thématique '{theme_name}' pour la zone '{zone_name}'...")
|
||||
|
||||
# Préparer le fichier CSV de sortie
|
||||
csv_file = os.path.join(output_dir, f"{zone_name}_{theme_name}.csv")
|
||||
|
||||
# Entêtes du CSV - colonnes de base
|
||||
headers = ["date", "zone", "theme", "nombre_total"]
|
||||
|
||||
# Ajouter une colonne pour chaque tag important
|
||||
# Vérifier si la clé 'important_tags' existe, sinon utiliser une liste vide
|
||||
important_tags = theme_info.get("important_tags", [])
|
||||
for attr in important_tags:
|
||||
headers.append(f"nombre_avec_{attr}")
|
||||
|
||||
# Ajouter la colonne de pourcentage de complétion
|
||||
headers.append("pourcentage_completion")
|
||||
|
||||
# Vérifier si le fichier CSV existe déjà et lire les données existantes
|
||||
existing_dates = set()
|
||||
existing_rows = []
|
||||
existing_data = {} # Dictionnaire pour stocker les données existantes par date
|
||||
file_exists = os.path.exists(csv_file)
|
||||
|
||||
if file_exists:
|
||||
try:
|
||||
with open(csv_file, "r", newline="") as f:
|
||||
reader = csv.reader(f)
|
||||
existing_headers = next(reader) # Lire les entêtes
|
||||
|
||||
# Vérifier que les entêtes correspondent
|
||||
if existing_headers == headers:
|
||||
for row in reader:
|
||||
if len(row) >= 1: # S'assurer que la ligne a au moins une date
|
||||
date = row[0] # La date est dans la première colonne
|
||||
existing_dates.add(date)
|
||||
existing_rows.append(row)
|
||||
|
||||
# Stocker les données existantes dans un dictionnaire pour un accès facile
|
||||
existing_data[date] = row
|
||||
else:
|
||||
print(f"Les entêtes du fichier existant ne correspondent pas, création d'un nouveau fichier.")
|
||||
file_exists = False
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la lecture du fichier CSV existant: {e}")
|
||||
file_exists = False
|
||||
|
||||
# Filtrer les dates qui n'ont pas encore été traitées
|
||||
dates_to_process = [date_str for date_str in dates if date_str.split("T")[0] not in existing_dates]
|
||||
|
||||
# Mode d'ouverture du fichier (écriture ou ajout)
|
||||
mode = "a" if file_exists else "w"
|
||||
|
||||
with open(csv_file, mode, newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
|
||||
# Écrire les entêtes si c'est un nouveau fichier
|
||||
if not file_exists:
|
||||
writer.writerow(headers)
|
||||
|
||||
# Traiter chaque date qui n'a pas encore été traitée
|
||||
for date_str in dates_to_process:
|
||||
tag_filter = theme_info["tag_filter"]
|
||||
|
||||
# Compter le nombre total d'objets
|
||||
total_count = count_objects_at_date(
|
||||
zone_file, date_str, tag_filter, temp_dir, insee_code
|
||||
)
|
||||
|
||||
# Compter les objets avec chaque attribut important en une seule passe
|
||||
# Vérifier si la clé 'important_tags' existe et n'est pas vide
|
||||
important_tags = theme_info.get("important_tags", [])
|
||||
if important_tags:
|
||||
attr_counts_dict = count_objects_with_tags(
|
||||
zone_file,
|
||||
date_str,
|
||||
tag_filter,
|
||||
important_tags,
|
||||
temp_dir,
|
||||
insee_code,
|
||||
)
|
||||
attr_counts = [
|
||||
(attr, attr_counts_dict.get(attr, 0)) for attr in important_tags
|
||||
]
|
||||
else:
|
||||
attr_counts = []
|
||||
|
||||
# Formater la date pour le CSV (YYYY-MM-DD)
|
||||
csv_date = date_str.split("T")[0]
|
||||
|
||||
# Vérifier si le total_count est 0 et s'il était > 0 dans le passé
|
||||
recalculate = False
|
||||
if total_count == 0:
|
||||
# Parcourir les données existantes pour voir si une valeur était > 0 dans le passé
|
||||
was_greater_than_zero = False
|
||||
for existing_date in sorted(existing_data.keys()):
|
||||
if existing_date >= csv_date:
|
||||
break # Ne pas regarder les dates futures
|
||||
|
||||
existing_row = existing_data[existing_date]
|
||||
if len(existing_row) >= 4: # S'assurer que la ligne a une valeur de nombre_total
|
||||
try:
|
||||
existing_total = existing_row[3]
|
||||
if existing_total and existing_total != "" and int(existing_total) > 0:
|
||||
was_greater_than_zero = True
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
# Ignorer les valeurs qui ne peuvent pas être converties en entier
|
||||
pass
|
||||
|
||||
if was_greater_than_zero:
|
||||
# Si une valeur était > 0 dans le passé et est maintenant 0, remplacer par une valeur vide
|
||||
total_count = ""
|
||||
print(f"Valeur passée de > 0 à 0 pour {theme_name} à la date {csv_date}, remplacée par une valeur vide.")
|
||||
|
||||
# Relancer le calcul osmium
|
||||
recalculate = True
|
||||
|
||||
# Vérifier également pour chaque attribut important
|
||||
for i, (attr, count) in enumerate(attr_counts):
|
||||
if count == 0:
|
||||
# Parcourir les données existantes pour voir si une valeur était > 0 dans le passé
|
||||
was_greater_than_zero = False
|
||||
for existing_date in sorted(existing_data.keys()):
|
||||
if existing_date >= csv_date:
|
||||
break # Ne pas regarder les dates futures
|
||||
|
||||
existing_row = existing_data[existing_date]
|
||||
if len(existing_row) >= 4 + i + 1: # S'assurer que la ligne a une valeur pour cet attribut
|
||||
try:
|
||||
existing_attr_count = existing_row[4 + i]
|
||||
if existing_attr_count and existing_attr_count != "" and int(existing_attr_count) > 0:
|
||||
was_greater_than_zero = True
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
# Ignorer les valeurs qui ne peuvent pas être converties en entier
|
||||
pass
|
||||
|
||||
if was_greater_than_zero:
|
||||
# Si une valeur était > 0 dans le passé et est maintenant 0, remplacer par une valeur vide
|
||||
attr_counts[i] = (attr, "")
|
||||
print(f"Valeur de {attr} passée de > 0 à 0 pour {theme_name} à la date {csv_date}, remplacée par une valeur vide.")
|
||||
|
||||
# Relancer le calcul osmium
|
||||
recalculate = True
|
||||
|
||||
# Si on doit recalculer, relancer le calcul osmium
|
||||
if recalculate:
|
||||
print(f"Relancement du calcul osmium pour {theme_name} à la date {csv_date}...")
|
||||
# Supprimer les fichiers temporaires pour forcer un recalcul
|
||||
temp_file_pattern = os.path.join(temp_dir, f"temp_{date_str.replace(':', '_')}__*")
|
||||
run_command(f"rm -f {temp_file_pattern}")
|
||||
|
||||
# Recalculer le nombre total d'objets
|
||||
if total_count == "":
|
||||
total_count = count_objects_at_date(
|
||||
zone_file, date_str, tag_filter, temp_dir, insee_code
|
||||
)
|
||||
|
||||
# Recalculer les objets avec chaque attribut important
|
||||
if important_tags:
|
||||
attr_counts_dict = count_objects_with_tags(
|
||||
zone_file,
|
||||
date_str,
|
||||
tag_filter,
|
||||
important_tags,
|
||||
temp_dir,
|
||||
insee_code,
|
||||
)
|
||||
attr_counts = [
|
||||
(attr, attr_counts_dict.get(attr, 0)) for attr in important_tags
|
||||
]
|
||||
|
||||
# Calculer le pourcentage de complétion
|
||||
if total_count is not None and total_count != "" and total_count > 0 and len(attr_counts) > 0:
|
||||
# Filtrer les comptages None ou vides avant de calculer la moyenne
|
||||
valid_counts = [(attr, count) for attr, count in attr_counts if count is not None and count != ""]
|
||||
if valid_counts:
|
||||
# Moyenne des pourcentages de présence de chaque attribut important
|
||||
completion_pct = sum(
|
||||
count / total_count * 100 for _, count in valid_counts
|
||||
) / len(valid_counts)
|
||||
else:
|
||||
completion_pct = 0
|
||||
else:
|
||||
completion_pct = 0
|
||||
|
||||
# Préparer la ligne CSV avec les colonnes de base
|
||||
# Si le comptage total a échoué (None), ajouter une chaîne vide au lieu de 0
|
||||
row = [csv_date, zone_name, theme_name, "" if total_count is None else total_count]
|
||||
|
||||
# Ajouter les compteurs pour chaque attribut important
|
||||
for attr, count in attr_counts:
|
||||
# Si le comptage a échoué (None), ajouter une chaîne vide au lieu de 0
|
||||
row.append("" if count is None else count)
|
||||
|
||||
# Ajouter le pourcentage de complétion
|
||||
row.append(round(completion_pct, 2))
|
||||
|
||||
# Écrire la ligne dans le CSV
|
||||
writer.writerow(row)
|
||||
|
||||
# Si aucune nouvelle date n'a été traitée, afficher un message
|
||||
if not dates_to_process:
|
||||
print(f"Toutes les dates pour la thématique '{theme_name}' sont déjà traitées dans le fichier CSV existant.")
|
||||
|
||||
print(f"Résultats sauvegardés dans {csv_file}")
|
||||
|
||||
# Générer un graphique pour cette thématique
|
||||
generate_graph(csv_file, zone_name, theme_name)
|
||||
|
||||
end_time = time.time()
|
||||
print(f"Thématique '{theme_name}' traitée en {end_time - start_time:.2f} secondes")
|
||||
|
||||
return csv_file
|
||||
|
||||
|
||||
def cleanup_temp_files(temp_dir, keep_zone_files=True):
|
||||
"""
|
||||
Nettoie les fichiers temporaires dans le répertoire spécifié.
|
||||
|
||||
Args:
|
||||
temp_dir: Répertoire contenant les fichiers temporaires
|
||||
keep_zone_files: Si True, conserve les fichiers de zone extraits (.osh.pbf)
|
||||
"""
|
||||
print(f"Nettoyage des fichiers temporaires dans {temp_dir}...")
|
||||
count = 0
|
||||
|
||||
for file in os.listdir(temp_dir):
|
||||
file_path = os.path.join(temp_dir, file)
|
||||
|
||||
# Conserver les fichiers de zone extraits si demandé
|
||||
if (
|
||||
keep_zone_files
|
||||
and file.endswith(".osh.pbf")
|
||||
and not file.startswith("temp_")
|
||||
and not file.startswith("filtered_")
|
||||
):
|
||||
continue
|
||||
|
||||
# Supprimer les fichiers temporaires
|
||||
if (
|
||||
file.startswith("temp_")
|
||||
or file.startswith("filtered_")
|
||||
or file.startswith("export_")
|
||||
):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
count += 1
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la suppression du fichier {file_path}: {e}")
|
||||
|
||||
print(f"{count} fichiers temporaires supprimés.")
|
||||
|
||||
|
||||
def process_zone(
|
||||
input_file, poly_file, output_dir, temp_dir, max_dates=None, cleanup=False
|
||||
):
|
||||
"""
|
||||
Traite une zone spécifique pour toutes les thématiques en parallèle.
|
||||
|
||||
Args:
|
||||
input_file: Fichier d'historique OSM (.osh.pbf)
|
||||
poly_file: Fichier de polygone (.poly) définissant la zone
|
||||
output_dir: Répertoire pour les fichiers de sortie
|
||||
temp_dir: Répertoire pour les fichiers temporaires
|
||||
max_dates: Nombre maximum de dates à traiter (pour les tests)
|
||||
cleanup: Si True, nettoie les fichiers temporaires après traitement
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
# Créer les répertoires nécessaires
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
# Obtenir le nom de la zone à partir du nom du fichier poly
|
||||
zone_name = Path(poly_file).stem
|
||||
|
||||
# Extraire le code INSEE à partir du nom du fichier poly (format: commune_XXXXX.poly)
|
||||
insee_code = None
|
||||
if zone_name.startswith("commune_"):
|
||||
insee_code = zone_name.replace("commune_", "")
|
||||
|
||||
# Extraire les données pour la zone
|
||||
zone_file = extract_zone_data(input_file, poly_file, temp_dir)
|
||||
|
||||
# Générer les dates avec différentes fréquences selon l'ancienneté
|
||||
dates = generate_time_slices(max_dates) # Limité par max_dates si spécifié
|
||||
|
||||
print(f"Traitement de {len(THEMES)} thématiques pour la zone '{zone_name}'...")
|
||||
|
||||
# Déterminer le nombre de processus à utiliser (nombre de cœurs disponibles - 1, minimum 1)
|
||||
num_processes = max(1, multiprocessing.cpu_count() - 1)
|
||||
print(f"Utilisation de {num_processes} processus pour le traitement parallèle")
|
||||
|
||||
# Créer un pool de processus
|
||||
with multiprocessing.Pool(processes=num_processes) as pool:
|
||||
# Préparer les arguments pour chaque thématique
|
||||
theme_args = [
|
||||
(
|
||||
theme_name,
|
||||
theme_info,
|
||||
zone_file,
|
||||
zone_name,
|
||||
dates,
|
||||
output_dir,
|
||||
temp_dir,
|
||||
insee_code,
|
||||
)
|
||||
for theme_name, theme_info in THEMES.items()
|
||||
]
|
||||
|
||||
# Exécuter le traitement des thématiques en parallèle
|
||||
pool.starmap(process_theme, theme_args)
|
||||
|
||||
# Nettoyer les fichiers temporaires si demandé
|
||||
if cleanup:
|
||||
cleanup_temp_files(temp_dir, keep_zone_files=True)
|
||||
|
||||
end_time = time.time()
|
||||
total_time = end_time - start_time
|
||||
print(f"Traitement de la zone '{zone_name}' terminé en {total_time:.2f} secondes")
|
||||
|
||||
return total_time
|
||||
|
||||
|
||||
def generate_graph(csv_file, zone_name, theme_name):
|
||||
"""
|
||||
Génère un graphique à partir des données CSV.
|
||||
|
||||
Args:
|
||||
csv_file: Fichier CSV contenant les données
|
||||
zone_name: Nom de la zone
|
||||
theme_name: Nom de la thématique
|
||||
"""
|
||||
# Vérifier si le script generate_graph.py existe
|
||||
if os.path.exists(os.path.join(os.path.dirname(__file__), "generate_graph.py")):
|
||||
# Construire le chemin de sortie pour le graphique
|
||||
output_path = os.path.splitext(csv_file)[0] + "_graph.png"
|
||||
|
||||
# Exécuter le script pour générer le graphique
|
||||
command = f"python3 {os.path.join(os.path.dirname(__file__), 'generate_graph.py')} {csv_file} --output {output_path}"
|
||||
run_command(command)
|
||||
print(f"Graphique généré: {output_path}")
|
||||
else:
|
||||
print(
|
||||
"Le script generate_graph.py n'a pas été trouvé. Aucun graphique n'a été généré."
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Fonction principale"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compte les objets OSM par thématique sur une zone donnée à différentes dates."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input", "-i", required=True, help="Fichier d'historique OSM (.osh.pbf)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--poly",
|
||||
"-p",
|
||||
required=True,
|
||||
help="Fichier de polygone (.poly) définissant la zone",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
"-o",
|
||||
default="resultats",
|
||||
help="Répertoire pour les fichiers de sortie",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--temp-dir",
|
||||
"-t",
|
||||
default="temp",
|
||||
help="Répertoire pour les fichiers temporaires",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-dates",
|
||||
"-m",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Nombre maximum de dates à traiter (pour les tests, par défaut: toutes les dates)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cleanup",
|
||||
"-c",
|
||||
action="store_true",
|
||||
help="Nettoyer les fichiers temporaires après traitement",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--benchmark",
|
||||
"-b",
|
||||
action="store_true",
|
||||
help="Afficher des informations de performance détaillées",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Vérifier que les fichiers d'entrée existent
|
||||
if not os.path.isfile(args.input):
|
||||
print(f"Erreur: Le fichier d'entrée {args.input} n'existe pas.")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isfile(args.poly):
|
||||
print(f"Erreur: Le fichier de polygone {args.poly} n'existe pas.")
|
||||
sys.exit(1)
|
||||
|
||||
# Afficher des informations sur la configuration
|
||||
if args.benchmark:
|
||||
print("\n=== Configuration ===")
|
||||
print(f"Nombre de processeurs: {multiprocessing.cpu_count()}")
|
||||
print(f"Nombre de thématiques: {len(THEMES)}")
|
||||
print(
|
||||
f"Nettoyage des fichiers temporaires: {'Activé' if args.cleanup else 'Désactivé'}"
|
||||
)
|
||||
print("=====================\n")
|
||||
|
||||
# Mesurer le temps d'exécution
|
||||
start_time = time.time()
|
||||
|
||||
# Traiter la zone
|
||||
total_time = process_zone(
|
||||
args.input,
|
||||
args.poly,
|
||||
args.output_dir,
|
||||
args.temp_dir,
|
||||
args.max_dates,
|
||||
args.cleanup,
|
||||
)
|
||||
|
||||
# Afficher des informations de performance
|
||||
if args.benchmark:
|
||||
print("\n=== Performance ===")
|
||||
print(f"Temps total d'exécution: {total_time:.2f} secondes")
|
||||
print(f"Temps moyen par thématique: {total_time / len(THEMES):.2f} secondes")
|
||||
print("===================\n")
|
||||
|
||||
print("Traitement terminé.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
193
counting_osm_objects/osm-commerces-villes-export.csv
Normal file
193
counting_osm_objects/osm-commerces-villes-export.csv
Normal file
|
@ -0,0 +1,193 @@
|
|||
zone,name,lat,lon,population,budgetAnnuel,completionPercent,placesCount,avecHoraires,avecAdresse,avecSite,avecAccessibilite,avecNote,siren,codeEpci,codesPostaux
|
||||
91111,Briis-sous-Forges,48.6246916,2.1243349,3375,4708125.00,30,67,12,8,9,7,3,219101110,249100074,91640
|
||||
79034,Bessines,46.3020750,-0.5167969,1882,2676204.00,58,56,36,26,34,4,1,217900349,200041317,79000
|
||||
76216,Déville-lès-Rouen,49.4695338,1.0495889,10690,18386800.00,20,110,16,3,11,1,1,217602168,200023414,76250
|
||||
91249,Forges-les-Bains,48.6290401,2.0996273,4138,6310450.00,33,31,7,5,2,5,0,219102498,249100074,91470
|
||||
59140,Caullery,50.0817000,3.3737000,464,824528.00,,,,,,,,,,
|
||||
08122,Chooz,50.0924000,4.7996000,792,1375704.00,,,,,,,,,,
|
||||
12084,Creissels,44.0621000,3.0572000,1564,2493016.00,,,,,,,,,,
|
||||
59183,Dunkerque,51.0183000,2.3431000,87013,153229893.00,,,,,,,,,,
|
||||
86116,Jazeneuil,46.4749000,0.0735000,797,1212237.00,,,,,,,,,,
|
||||
75113,,48.8303000,2.3656000,177735,239764515.00,,,,,,,,,,
|
||||
06088,Nice,43.7032000,7.2528000,353701,557079075.00,,,,,,,,,,
|
||||
38185,Grenoble,45.1842000,5.7155000,156389,231299331.00,,,,,,,,,,
|
||||
75117,,48.8874000,2.3050000,161206,231975434.00,,,,,,,,,,
|
||||
75116,,48.8572000,2.2630000,159733,287199934.00,,,,,,,,,,
|
||||
35236,Redon,47.6557000,-2.0787000,9336,15030960.00,,,,,,,,,,
|
||||
35238,Rennes,48.1159000,-1.6884000,227830,408727020.00,,,,,,,,,,
|
||||
78646,Versailles,48.8039000,2.1191000,83918,142912354.00,,,,,,,,,,
|
||||
12230,Saint-Jean-Delnous,44.0391000,2.4912000,382,665062.00,,,,,,,,,,
|
||||
76193,"La Crique",49.6929000,1.2051000,367,651425.00,,,,,,,,,,
|
||||
49007,Angers,47.4819000,-0.5629000,157555,207815045.00,,,,,,,,,,
|
||||
79003,Aiffres,46.2831000,-0.4147000,5423,9631248.00,,,,,,,,,,
|
||||
29151,Morlaix,48.5971000,-3.8215000,15220,22129880.00,,,,,,,,,,
|
||||
38544,Vienne,45.5221000,4.8803000,31555,55252805.00,,,,,,,,,,
|
||||
42100,"La Gimond",45.5551000,4.4144000,278,428676.00,,,,,,,,,,
|
||||
76008,Ancourt,49.9110000,1.1835000,627,850839.00,,,,,,,,,,
|
||||
76618,Petit-Caux,49.9612000,1.2343000,9626,15334218.00,,,,,,,,,,
|
||||
13202,Marseille,43.3225000,5.3497000,24153,40842723.00,,,,,,,,,,
|
||||
46102,Figeac,44.6067000,2.0231000,9757,17025965.00,,,,,,,,,,
|
||||
75107,,48.8548000,2.3115000,48196,70992708.00,,,,,,,,,,
|
||||
7812,"Les Clayes-sous-Bois",,,,,,,,,,,,,,
|
||||
76192,Criel-sur-Mer,50.0221000,1.3215000,2592,3659904.00,,,,,,,,,,
|
||||
94080,Vincennes,48.8471000,2.4383000,48368,64232704.00,,,,,,,,,,
|
||||
13055,Marseille,43.2803000,5.3806000,877215,1184240250.00,,,,,,,,,,
|
||||
93055,Pantin,48.9006000,2.4085000,60954,90272874.00,,,,,,,,,,
|
||||
69385,,45.7560000,4.8012000,48277,83277825.00,,,,,,,,,,
|
||||
75109,,48.8771000,2.3379000,58419,79625097.00,,,,,,,,,,
|
||||
44184,Saint-Nazaire,47.2768000,-2.2392000,73111,104694952.00,,,,,,,,,,
|
||||
13208,,43.2150000,5.3256000,83414,120866886.00,,,,,,,,,,
|
||||
59271,Grande-Synthe,51.0157000,2.2938000,20347,34284695.00,,,,,,,,,,
|
||||
77379,Provins,48.5629000,3.2845000,11824,20183568.00,,,,,,,,,,
|
||||
75108,,48.8732000,2.3111000,35418,51710280.00,,,,,,,,,,
|
||||
77122,Combs-la-Ville,48.6602000,2.5765000,22712,29661872.00,,,,,,,,,,
|
||||
79298,Saint-Symphorien,46.2688000,-0.4842000,1986,2909490.00,,,,,,,,,,
|
||||
51454,Reims,49.2535000,4.0551000,178478,310908676.00,,,,,,,,,,
|
||||
64102,Bayonne,43.4844000,-1.4611000,53312,82740224.00,,,,,,,,,,
|
||||
55153,Dieppe-sous-Douaumont,49.2217000,5.5201000,193,302817.00,,,,,,,,,,
|
||||
39300,Lons-le-Saunier,46.6758000,5.5574000,16942,25379116.00,,,,,,,,,,
|
||||
85288,Talmont-Saint-Hilaire,46.4775000,-1.6299000,8327,10908370.00,,,,,,,,,,
|
||||
13203,,43.3113000,5.3806000,55653,81642951.00,,,,,,,,,,
|
||||
69387,,45.7321000,4.8393000,87491,134998613.00,,,,,,,,,,
|
||||
64024,Anglet,43.4893000,-1.5193000,42288,73665696.00,,,,,,,,,,
|
||||
69265,Ville-sur-Jarnioux,45.9693000,4.5942000,820,1209500.00,,,,,,,,,,
|
||||
75112,,48.8342000,2.4173000,139788,228273804.00,,,,,,,,,,
|
||||
69388,,45.7342000,4.8695000,84956,119703004.00,,,,,,,,,,
|
||||
74010,Annecy,45.9024000,6.1264000,131272,235764512.00,,,,,,,,,,
|
||||
74100,Desingy,46.0022000,5.8870000,759,1032240.00,,,,,,,,,,
|
||||
75114,,48.8297000,2.3230000,137581,190412104.00,,,,,,,,,,
|
||||
75118,,48.8919000,2.3487000,185825,275578475.00,,,,,,,,,,
|
||||
85191,"La Roche-sur-Yon",46.6659000,-1.4162000,54699,80899821.00,,,,,,,,,,
|
||||
85194,"Les Sables-d'Olonne",46.5264000,-1.7611000,48740,85002560.00,,,,,,,,,,
|
||||
12220,Sainte-Eulalie-de-Cernon,43.9605000,3.1550000,321,558540.00,,,,,,,,,,
|
||||
14341,Ifs,49.1444000,-0.3385000,11868,16377840.00,,,,,,,,,,
|
||||
35288,Saint-Malo,48.6465000,-2.0066000,47255,81562130.00,,,,,,,,,,
|
||||
86194,Poitiers,46.5846000,0.3715000,89472,130718592.00,,,,,,,,,,
|
||||
91338,Limours,48.6463000,2.0823000,6408,8676432.00,,,,,,,,,,
|
||||
91640,,,,,,,,,,,,,,,
|
||||
79191,Niort,46.3274000,-0.4613000,60074,92934478.00,,,,,,,,,,
|
||||
06004,Antibes,43.5823000,7.1048000,76612,134147612.00,,,,,,,,,,
|
||||
13207,,43.2796000,5.3274000,34866,53031186.00,,,,,,,,,,
|
||||
91657,Vigneux-sur-Seine,48.7021000,2.4274000,31233,41477424.00,,,,,,,,,,
|
||||
73124,Gilly-sur-Isère,45.6549000,6.3487000,3109,4651064.00,,,,,,,,,,
|
||||
92040,Issy-les-Moulineaux,48.8240000,2.2628000,67695,108108915.00,,,,,,,,,,
|
||||
93051,Noisy-le-Grand,48.8327000,2.5560000,71632,104654352.00,,,,,,,,,,
|
||||
14675,Soliers,49.1315000,-0.2898000,2190,2923650.00,,,,,,,,,,
|
||||
76655,Saint-Valery-en-Caux,49.8582000,0.7094000,3884,5985244.00,,,,,,,,,,
|
||||
34172,Montpellier,43.6100000,3.8742000,307101,508866357.00,,,,,,,,,,
|
||||
76351,"Le Havre",49.4958000,0.1312000,166462,260346568.00,,,,,,,,,,
|
||||
76665,Sauchay,49.9179000,1.2073000,448,618240.00,,,,,,,,,,
|
||||
13204,,43.3063000,5.4002000,49744,75312416.00,,,,,,,,,,
|
||||
75104,Paris,48.8541000,2.3569000,28039,44161425.00,,,,,,,,,,
|
||||
45234,Orléans,47.8734000,1.9122000,116344,178355352.00,,,,,,,,,,
|
||||
13100,Saint-Rémy-de-Provence,43.7815000,4.8455000,9547,16334917.00,,,,,,,,,,
|
||||
27275,Gaillon,49.1602000,1.3405000,6785,11995880.00,,,,,,,,,,
|
||||
44047,Couëron,47.2391000,-1.7472000,23541,31521399.00,,,,,,,,,,
|
||||
16070,Chabanais,45.8656000,0.7076000,1564,2744820.00,,,,,,,,,,
|
||||
12029,Bor-et-Bar,44.1971000,2.0822000,198,304128.00,,,,,,,,,,
|
||||
59155,Coudekerque-Branche,51.0183000,2.3986000,20833,36707746.00,,,,,,,,,,
|
||||
16106,Confolens,46.0245000,0.6639000,2726,3595594.00,,,,,,,,,,
|
||||
75020,,,,,,,,,,,,,,,
|
||||
76217,Dieppe,49.9199000,1.0838000,28599,48446706.00,,,,,,,,,,
|
||||
95128,,,,,,,,,,,,,,,
|
||||
44000,,,,,,,,,,,,,,,
|
||||
79000,,,,,,,,,,,,,,,
|
||||
27562,Saint-Marcel,49.0927000,1.4395000,4474,6259126.00,,,,,,,,,,
|
||||
87011,Bellac,46.1013000,1.0325000,3569,5117946.00,,,,,,,,,,
|
||||
94016,Cachan,48.7914000,2.3318000,30526,50642634.00,,,,,,,,,,
|
||||
50237,"La Haye-Pesnel",48.8134000,-1.3732000,1261,2220621.00,,,,,,,,,,
|
||||
91174,Corbeil-Essonnes,48.5973000,2.4646000,53712,72081504.00,,,,,,,,,,
|
||||
11012,Argeliers,43.3090000,2.9137000,2128,3562272.00,,,,,,,,,,
|
||||
94041,Ivry-sur-Seine,48.8125000,2.3872000,64526,85432424.00,,,,,,,,,,
|
||||
95127,Cergy,49.0373000,2.0455000,69578,108263368.00,,,,,,,,,,
|
||||
69001,Affoux,45.8448000,4.4116000,397,648698.00,,,,,,,,,,
|
||||
44190,Saint-Sébastien-sur-Loire,47.2065000,-1.5023000,28373,39920811.00,,,,,,,,,,
|
||||
69123,Lyon,45.7580000,4.8351000,520774,755643074.00,,,,,,,,,,
|
||||
07336,Vernon,44.5074000,4.2251000,223,371295.00,,,,,,,,,,
|
||||
91201,Draveil,48.6777000,2.4249000,29824,51506048.00,,,,,,,,,,
|
||||
29174,Plonéour-Lanvern,47.9066000,-4.2678000,6403,9931053.00,,,,,,,,,,
|
||||
75017,,,,,,,,,,,,,,,
|
||||
17197,Jonzac,45.4413000,-0.4237000,3576,5188776.00,,,,,,,,,,
|
||||
85195,,,,,,,,,,,,,,,
|
||||
44162,Saint-Herblain,47.2246000,-1.6306000,50561,88481750.00,,,,,,,,,,
|
||||
00000,"toutes les villes",,,,,,,,,,,,,,
|
||||
57463,Metz,49.1048000,6.1962000,121695,192643185.00,,,,,,,,,,
|
||||
57466,Metzing,49.1008000,6.9605000,693,945945.00,,,,,,,,,,
|
||||
37000,,,,,,,,,,,,,,,
|
||||
91312,Igny,48.7375000,2.2229000,10571,14831113.00,,,,,,,,,,
|
||||
25462,Pontarlier,46.9167000,6.3796000,17928,23449824.00,,,,,,,,,,
|
||||
33063,Bordeaux,44.8624000,-0.5848000,265328,467773264.00,,,,,,,,,,
|
||||
16015,Angoulême,45.6458000,0.1450000,41423,66069685.00,,,,,,,,,,
|
||||
02196,Clacy-et-Thierret,49.5546000,3.5651000,294,458934.00,,,,,,,,,,
|
||||
24037,Bergerac,44.8519000,0.4883000,26852,47850264.00,,,,,,,,,,
|
||||
78686,Viroflay,48.8017000,2.1725000,16943,27091857.00,,,,,,,,,,
|
||||
83137,Toulon,43.1364000,5.9334000,180834,319172010.00,,,,,,,,,,
|
||||
44026,Carquefou,47.2968000,-1.4687000,20535,28707930.00,,,,,,,,,,
|
||||
87154,Saint-Junien,45.8965000,0.8853000,11382,19406310.00,,,,,,,,,,
|
||||
79081,Chauray,46.3537000,-0.3862000,7173,12387771.00,,,,,,,,,,
|
||||
73008,Aix-les-Bains,45.6943000,5.9035000,32175,53539200.00,,,,,,,,,,
|
||||
86195,Port-de-Piles,46.9989000,0.5956000,548,744184.00,,,,,,,,,,
|
||||
91470,,,,,,,,,,,,,,,
|
||||
34008,"Les Aires",43.5687000,3.0676000,613,1081332.00,,,,,,,,,,
|
||||
44270,,,,,,,,,,,,,,,
|
||||
25056,Besançon,47.2602000,6.0123000,120057,158355183.00,,,,,,,,,,
|
||||
75019,,,,,,,,,,,,,,,
|
||||
78712,,,,,,,,,,,,,,,
|
||||
11262,Narbonne,43.1493000,3.0337000,56692,79482184.00,,,,,,,,,,
|
||||
60602,Saint-Valery,49.7251000,1.7303000,54,90450.00,,,,,,,,,,
|
||||
91421,Montgeron,48.6952000,2.4638000,23890,40851900.00,,,,,,,,,,
|
||||
82112,Moissac,44.1219000,1.1002000,13652,24027520.00,,,,,,,,,,
|
||||
92033,Garches,48.8469000,2.1861000,17705,25442085.00,,,,,,,,,,
|
||||
38053,Bourgoin-Jallieu,45.6025000,5.2747000,29816,41593320.00,,,,,,,,,,
|
||||
34335,Villemagne-l'Argentière,43.6189000,3.1208000,420,586320.00,,,,,,,,,,
|
||||
13213,,43.3528000,5.4301000,93425,144528475.00,,,,,,,,,,
|
||||
44020,Bouguenais,47.1710000,-1.6181000,20590,34282350.00,,,,,,,,,,
|
||||
36044,Châteauroux,46.8023000,1.6903000,43079,70864955.00,,,,,,,,,,
|
||||
11164,Ginestas,43.2779000,2.8830000,1579,2837463.00,,,,,,,,,,
|
||||
60009,Allonne,49.3952000,2.1157000,1737,2883420.00,,,,,,,,,,
|
||||
41151,"Montrichard Val de Cher",47.3594000,1.1998000,3641,5559807.00,,,,,,,,,,
|
||||
27554,"La Chapelle-Longueville",49.1085000,1.4115000,3283,4796463.00,,,,,,,,,,
|
||||
34189,Olonzac,43.2816000,2.7431000,1683,2511036.00,,,,,,,,,,
|
||||
34028,Bédarieux,43.6113000,3.1637000,5820,9550620.00,,,,,,,,,,
|
||||
74112,"Épagny Metz-Tessy",45.9430000,6.0934000,8642,13956830.00,,,,,,,,,,
|
||||
75102,,48.8677000,2.3411000,20433,35103894.00,,,,,,,,,,
|
||||
60057,Beauvais,49.4425000,2.0877000,55906,84082624.00,,,,,,,,,,
|
||||
59350,Lille,50.6311000,3.0468000,238695,403871940.00,,,,,,,,,,
|
||||
91477,Igny,48.7155000,2.2293000,36067,46923167.00,,,,,,,,,,
|
||||
12145,Millau,44.0982000,3.1176000,21859,34865105.00,,,,,,,,,,
|
||||
12115,L'Hospitalet-du-Larzac,43.9755000,3.2074000,344,569320.00,,,,,,,,,,
|
||||
79004,,,,,,,,,,,,,,,
|
||||
75014,,,,,,,,,,,,,,,
|
||||
75018,,,,,,,,,,,,,,,
|
||||
55154,Dieue-sur-Meuse,49.0790000,5.4293000,1452,2099592.00,,,,,,,,,,
|
||||
79192,,,,,,,,,,,,,,,
|
||||
06018,Biot,43.6273000,7.0821000,10196,15192040.00,,,,,,,,,,
|
||||
25393,Montécheroux,47.3469000,6.7965000,557,839399.00,,,,,,,,,,
|
||||
14554,"Le Castelet",49.0870000,-0.2811000,1829,3076378.00,,,,,,,,,,
|
||||
69384,,45.7805000,4.8260000,35232,49782816.00,,,,,,,,,,
|
||||
78672,Villennes-sur-Seine,48.9372000,1.9975000,5792,9359872.00,,,,,,,,,,
|
||||
78123,Carrières-sous-Poissy,48.9469000,2.0264000,19951,35752192.00,,,,,,,,,,
|
||||
77000,,,,,,,,,,,,,,,
|
||||
31056,Beauzelle,43.6680000,1.3753000,8184,11670384.00,,,,,,,,,,
|
||||
38553,Villefontaine,45.6161000,5.1549000,19018,25579210.00,,,,,,,,,,
|
||||
47001,Agen,44.2010000,0.6302000,32193,48965553.00,,,,,,,,,,
|
||||
54700,,,,,,,,,,,,,,,
|
||||
70279,Gray,47.4310000,5.6153000,5455,9071665.00,,,,,,,,,,
|
||||
74000,,,,,,,,,,,,,,,
|
||||
91339,Linas,48.6261000,2.2525000,7310,12412380.00,,,,,,,,,,
|
||||
17306,Royan,45.6343000,-1.0127000,19322,33311128.00,,,,,,,,,,
|
||||
17300,"La Rochelle",46.1620000,-1.1765000,79961,118182358.00,,,,,,,,,,
|
||||
59000,,,,,,,,,,,,,,,
|
||||
77284,Meaux,48.9573000,2.9035000,56659,84025297.00,,,,,,,,,,
|
||||
76414,Martin-Église,49.9105000,1.1279000,1595,2177175.00,,,,,,,,,,
|
||||
54528,Toul,48.6794000,5.8980000,15570,21626730.00,,,,,,,,,,
|
||||
63124,Cournon-d'Auvergne,45.7420000,3.1885000,20020,30930900.00,,,,,,,,,,
|
||||
87085,Limoges,45.8567000,1.2260000,129754,203194764.00,,,,,,,,,,
|
||||
78000,,,,,,,,,,,,,,,
|
||||
13205,,43.2925000,5.4006000,45020,61632380.00,,,,,,,,,,
|
||||
31584,Villemur-sur-Tarn,43.8582000,1.4947000,6235,9813890.00,,,,,,,,,,
|
||||
91228,Évry-Courcouronnes,48.6287000,2.4313000,66700,90245100.00,,,,,,,,,,
|
||||
41018,Blois,47.5813000,1.3049000,47092,79350020.00,,,,,,,,,,
|
||||
66136,Perpignan,42.6990000,2.9045000,120996,193593600.00,,,,,,,,,,
|
||||
11041,Bize-Minervois,43.3364000,2.8719000,1292,1758412.00,,,,,,,,,,
|
||||
75016,,,,,,,,,,,,,,,
|
|
3
counting_osm_objects/osm_data/empty.osm
Normal file
3
counting_osm_objects/osm_data/empty.osm
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<osm version="0.6" generator="empty">
|
||||
</osm>
|
110
counting_osm_objects/osm_data/style.lua
Normal file
110
counting_osm_objects/osm_data/style.lua
Normal file
|
@ -0,0 +1,110 @@
|
|||
local tables = {}
|
||||
|
||||
-- Table pour les bornes incendie
|
||||
tables.fire_hydrants = osm2pgsql.define_node_table('fire_hydrants', {
|
||||
{ column = 'id_column', type = 'id_type' },
|
||||
{ column = 'geom', type = 'point', projection = 4326 },
|
||||
{ column = 'tags', type = 'hstore' },
|
||||
{ column = 'ref', type = 'text' },
|
||||
{ column = 'color', type = 'text' },
|
||||
{ column = 'insee', type = 'text' },
|
||||
})
|
||||
|
||||
-- Table pour les arbres
|
||||
tables.trees = osm2pgsql.define_node_table('trees', {
|
||||
{ column = 'id_column', type = 'id_type' },
|
||||
{ column = 'geom', type = 'point', projection = 4326 },
|
||||
{ column = 'tags', type = 'hstore' },
|
||||
{ column = 'species', type = 'text' },
|
||||
{ column = 'height', type = 'text' },
|
||||
{ column = 'insee', type = 'text' },
|
||||
})
|
||||
|
||||
-- Table pour les bornes de recharge (nodes)
|
||||
tables.charging_stations = osm2pgsql.define_node_table('charging_stations', {
|
||||
{ column = 'id_column', type = 'id_type' },
|
||||
{ column = 'geom', type = 'point', projection = 4326 },
|
||||
{ column = 'tags', type = 'hstore' },
|
||||
{ column = 'operator', type = 'text' },
|
||||
{ column = 'capacity', type = 'text' },
|
||||
{ column = 'insee', type = 'text' },
|
||||
})
|
||||
|
||||
-- Table pour les bornes de recharge (ways)
|
||||
tables.charging_stations_ways = osm2pgsql.define_way_table('charging_stations_ways', {
|
||||
{ column = 'id_column', type = 'id_type' },
|
||||
{ column = 'geom', type = 'linestring', projection = 4326 },
|
||||
{ column = 'tags', type = 'hstore' },
|
||||
{ column = 'operator', type = 'text' },
|
||||
{ column = 'capacity', type = 'text' },
|
||||
{ column = 'insee', type = 'text' },
|
||||
})
|
||||
|
||||
-- Function to determine the INSEE code from multiple possible sources
|
||||
function get_insee_code(tags)
|
||||
-- Try to get INSEE code from different tags
|
||||
if tags['ref:INSEE'] then
|
||||
return tags['ref:INSEE']
|
||||
elseif tags['addr:postcode'] then
|
||||
-- French postal codes often start with the department code
|
||||
-- For example, 91150 is in department 91, which can help identify the INSEE code
|
||||
return tags['addr:postcode'] and string.sub(tags['addr:postcode'], 1, 2) .. "111"
|
||||
elseif tags['addr:city'] and tags['addr:city'] == 'Étampes' then
|
||||
-- If the city is Étampes, use the INSEE code 91111
|
||||
return "91111"
|
||||
else
|
||||
-- Default to 91111 (Étampes) for this specific use case
|
||||
-- In a production environment, you would use a spatial query to determine the INSEE code
|
||||
return "91111"
|
||||
end
|
||||
end
|
||||
|
||||
function osm2pgsql.process_node(object)
|
||||
-- Check for fire hydrants with different tagging schemes
|
||||
if object.tags.emergency == 'fire_hydrant' or object.tags.amenity == 'fire_hydrant' then
|
||||
tables.fire_hydrants:insert({
|
||||
tags = object.tags,
|
||||
ref = object.tags.ref,
|
||||
color = object.tags.color,
|
||||
insee = get_insee_code(object.tags)
|
||||
})
|
||||
end
|
||||
|
||||
-- Check for trees
|
||||
if object.tags.natural == 'tree' then
|
||||
tables.trees:insert({
|
||||
tags = object.tags,
|
||||
species = object.tags.species,
|
||||
height = object.tags.height,
|
||||
insee = get_insee_code(object.tags)
|
||||
})
|
||||
end
|
||||
|
||||
-- Check for charging stations
|
||||
if object.tags.amenity == 'charging_station' then
|
||||
tables.charging_stations:insert({
|
||||
tags = object.tags,
|
||||
operator = object.tags.operator,
|
||||
capacity = object.tags.capacity,
|
||||
insee = get_insee_code(object.tags)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function osm2pgsql.process_way(object)
|
||||
-- Check for charging stations that might be mapped as ways
|
||||
if object.tags.amenity == 'charging_station' then
|
||||
tables.charging_stations_ways:insert({
|
||||
tags = object.tags,
|
||||
operator = object.tags.operator,
|
||||
capacity = object.tags.capacity,
|
||||
insee = get_insee_code(object.tags)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function osm2pgsql.process_relation(object)
|
||||
return
|
||||
end
|
||||
|
||||
|
309
counting_osm_objects/plotly_city.py
Executable file
309
counting_osm_objects/plotly_city.py
Executable file
|
@ -0,0 +1,309 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script pour générer un graphique interactif avec plotly montrant l'évolution du nombre d'objets OSM
|
||||
à partir d'un fichier CSV thématique d'une ville (code INSEE 91111 par défaut).
|
||||
|
||||
Ce script utilise la bibliothèque plotly pour créer des graphiques interactifs à partir des données
|
||||
contenues dans des fichiers CSV thématiques. Par défaut, il utilise le code INSEE 91111.
|
||||
Le titre du graphique inclut le tag principal (thème) et le nom de la ville.
|
||||
|
||||
Utilisation:
|
||||
python plotly_city.py chemin/vers/fichier.csv [options]
|
||||
|
||||
Options:
|
||||
--output, -o : Chemin de sortie pour le graphique HTML (optionnel)
|
||||
--insee, -i : Code INSEE de la commune à analyser (par défaut: 91111)
|
||||
--city_name, -c : Nom de la ville (si non spécifié, sera généré à partir du code INSEE)
|
||||
|
||||
Exemple:
|
||||
python plotly_city.py test_results/commune_91111_borne-de-recharge.csv
|
||||
|
||||
Dépendances requises:
|
||||
- pandas
|
||||
- plotly
|
||||
|
||||
Installation des dépendances:
|
||||
pip install pandas plotly
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command line arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Génère un graphique interactif avec plotly à partir des données CSV d'objets OSM."
|
||||
)
|
||||
parser.add_argument(
|
||||
"csv_file", help="Chemin vers le fichier CSV contenant les données"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", "-o", help="Chemin de sortie pour le graphique (HTML)", default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
"--insee", "-i", help="Code INSEE de la commune à analyser", default="91111"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--city_name",
|
||||
"-c",
|
||||
help="Nom de la ville (si non spécifié, sera extrait du CSV)",
|
||||
default=None,
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_city_name(insee_code):
|
||||
"""
|
||||
Récupère le nom de la ville à partir du code INSEE.
|
||||
Cette fonction pourrait être améliorée pour utiliser une API ou une base de données.
|
||||
|
||||
Args:
|
||||
insee_code: Code INSEE de la commune
|
||||
|
||||
Returns:
|
||||
Nom de la ville ou le code INSEE si le nom n'est pas trouvé
|
||||
"""
|
||||
# Pour l'instant, on retourne simplement le code INSEE
|
||||
# Dans une version future, on pourrait implémenter une recherche dans une base de données
|
||||
return f"Commune {insee_code}"
|
||||
|
||||
|
||||
def load_data(csv_file, insee_code="91111"):
|
||||
"""
|
||||
Charge les données depuis le fichier CSV.
|
||||
|
||||
Args:
|
||||
csv_file: Chemin vers le fichier CSV
|
||||
insee_code: Code INSEE de la commune à filtrer
|
||||
|
||||
Returns:
|
||||
DataFrame pandas contenant les données filtrées
|
||||
"""
|
||||
# Charger le CSV avec gestion des erreurs pour les lignes mal formatées
|
||||
try:
|
||||
df = pd.read_csv(csv_file, on_bad_lines="skip")
|
||||
except TypeError: # Pour les versions plus anciennes de pandas
|
||||
df = pd.read_csv(csv_file, error_bad_lines=False, warn_bad_lines=True)
|
||||
|
||||
# Vérifier si le CSV a la structure attendue
|
||||
if "date" in df.columns:
|
||||
# Format de CSV avec colonne 'date' directement
|
||||
df["date"] = pd.to_datetime(df["date"])
|
||||
else:
|
||||
# Si aucune colonne de date n'est trouvée, essayer d'utiliser la première colonne
|
||||
try:
|
||||
df["date"] = pd.to_datetime(df.iloc[:, 0])
|
||||
except:
|
||||
print("Erreur: Impossible de trouver ou convertir une colonne de date.")
|
||||
sys.exit(1)
|
||||
|
||||
# Filtrer par code INSEE si la colonne 'zone' contient des codes INSEE
|
||||
if "zone" in df.columns:
|
||||
# Vérifier si la zone contient le code INSEE
|
||||
if any(
|
||||
zone.endswith(insee_code) for zone in df["zone"] if isinstance(zone, str)
|
||||
):
|
||||
df = df[df["zone"].str.endswith(insee_code)]
|
||||
|
||||
# Trier par date
|
||||
df = df.sort_values("date")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def generate_plotly_graph(
|
||||
df, city_name=None, output_path=None, insee_code="91111", csv_file=None
|
||||
):
|
||||
"""
|
||||
Génère un graphique interactif avec plotly montrant l'évolution du nombre d'objets dans le temps.
|
||||
|
||||
Args:
|
||||
df: DataFrame pandas contenant les données
|
||||
city_name: Nom de la ville (optionnel)
|
||||
output_path: Chemin de sortie pour le graphique (optionnel)
|
||||
insee_code: Code INSEE de la commune
|
||||
csv_file: Chemin vers le fichier CSV source (pour générer un nom de fichier de sortie par défaut)
|
||||
"""
|
||||
# Si le nom de la ville n'est pas fourni, essayer de le récupérer
|
||||
if not city_name:
|
||||
city_name = get_city_name(insee_code)
|
||||
|
||||
# Déterminer la colonne pour les types d'objets (theme)
|
||||
theme_column = "theme"
|
||||
|
||||
# Créer une figure avec deux sous-graphiques (nombre total et taux de complétion)
|
||||
fig = make_subplots(
|
||||
rows=2,
|
||||
cols=1,
|
||||
subplot_titles=("Nombre d'objets OSM", "Taux de complétion des attributs (%)"),
|
||||
vertical_spacing=0.15,
|
||||
)
|
||||
|
||||
# Obtenir la liste des thèmes uniques
|
||||
if theme_column in df.columns:
|
||||
themes = df[theme_column].unique()
|
||||
|
||||
# Créer un graphique pour chaque thème
|
||||
for theme in themes:
|
||||
# Filtrer les données pour ce thème
|
||||
theme_data = df[df[theme_column] == theme]
|
||||
|
||||
# Tracer la ligne pour le nombre total d'objets
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=theme_data["date"],
|
||||
y=theme_data["nombre_total"],
|
||||
mode="lines+markers",
|
||||
name=f"{theme} - Total",
|
||||
hovertemplate="%{x}<br>Nombre: %{y}<extra></extra>",
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
|
||||
# Tracer la ligne pour le taux de complétion si disponible
|
||||
if "pourcentage_completion" in theme_data.columns:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=theme_data["date"],
|
||||
y=theme_data["pourcentage_completion"],
|
||||
mode="lines+markers",
|
||||
name=f"{theme} - Complétion (%)",
|
||||
hovertemplate="%{x}<br>Complétion: %{y}%<extra></extra>",
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
else:
|
||||
# Si aucune colonne de thème n'est trouvée, tracer simplement le nombre total
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df["date"],
|
||||
y=df["nombre_total"],
|
||||
mode="lines+markers",
|
||||
name="Total",
|
||||
hovertemplate="%{x}<br>Nombre: %{y}<extra></extra>",
|
||||
),
|
||||
row=1,
|
||||
col=1,
|
||||
)
|
||||
|
||||
# Tracer la ligne pour le taux de complétion si disponible
|
||||
if "pourcentage_completion" in df.columns:
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df["date"],
|
||||
y=df["pourcentage_completion"],
|
||||
mode="lines+markers",
|
||||
name="Complétion (%)",
|
||||
hovertemplate="%{x}<br>Complétion: %{y}%<extra></extra>",
|
||||
),
|
||||
row=2,
|
||||
col=1,
|
||||
)
|
||||
|
||||
# Configurer les axes et les légendes
|
||||
fig.update_xaxes(title_text="Date", row=1, col=1)
|
||||
fig.update_xaxes(title_text="Date", row=2, col=1)
|
||||
fig.update_yaxes(title_text="Nombre d'objets", row=1, col=1)
|
||||
fig.update_yaxes(title_text="Taux de complétion (%)", range=[0, 100], row=2, col=1)
|
||||
|
||||
# Obtenir le thème principal (premier thème trouvé)
|
||||
main_tag = (
|
||||
themes[0] if theme_column in df.columns and len(themes) > 0 else "Objets OSM"
|
||||
)
|
||||
|
||||
# Mettre à jour le titre du graphique avec le tag principal et le nom de la ville
|
||||
fig.update_layout(
|
||||
title=f"{main_tag} - {city_name}",
|
||||
hovermode="x unified",
|
||||
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
||||
height=800,
|
||||
width=1000,
|
||||
margin=dict(t=100, b=50, l=50, r=50),
|
||||
)
|
||||
|
||||
# Ajouter des annotations pour les informations supplémentaires
|
||||
fig.add_annotation(
|
||||
text=f"Code INSEE: {insee_code}",
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
x=0.01,
|
||||
y=-0.15,
|
||||
showarrow=False,
|
||||
font=dict(size=10),
|
||||
)
|
||||
|
||||
# Ajouter la date de génération
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
fig.add_annotation(
|
||||
text=f"Généré le: {now}",
|
||||
xref="paper",
|
||||
yref="paper",
|
||||
x=0.99,
|
||||
y=-0.15,
|
||||
showarrow=False,
|
||||
font=dict(size=8),
|
||||
align="right",
|
||||
)
|
||||
|
||||
# Sauvegarder ou afficher le graphique
|
||||
if output_path:
|
||||
fig.write_html(output_path)
|
||||
print(f"Graphique interactif sauvegardé: {output_path}")
|
||||
elif csv_file:
|
||||
# Déterminer un chemin de sortie par défaut basé sur le fichier CSV
|
||||
base_name = os.path.splitext(csv_file)[0]
|
||||
default_output = f"{base_name}_plotly.html"
|
||||
fig.write_html(default_output)
|
||||
print(f"Graphique interactif sauvegardé: {default_output}")
|
||||
else:
|
||||
# Si aucun chemin de sortie n'est spécifié et aucun fichier CSV n'est fourni,
|
||||
# utiliser un nom par défaut basé sur le code INSEE et le thème
|
||||
default_output = f"commune_{insee_code}_{main_tag}_plotly.html"
|
||||
fig.write_html(default_output)
|
||||
print(f"Graphique interactif sauvegardé: {default_output}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Fonction principale."""
|
||||
# Analyser les arguments de la ligne de commande
|
||||
args = parse_args()
|
||||
|
||||
# Vérifier que le fichier CSV existe
|
||||
if not os.path.isfile(args.csv_file):
|
||||
print(f"Erreur: Le fichier {args.csv_file} n'existe pas.")
|
||||
sys.exit(1)
|
||||
|
||||
# Charger les données
|
||||
df = load_data(args.csv_file, args.insee)
|
||||
|
||||
# Vérifier qu'il y a des données
|
||||
if df.empty:
|
||||
print(f"Aucune donnée trouvée pour le code INSEE {args.insee}.")
|
||||
sys.exit(1)
|
||||
|
||||
# Déterminer le chemin de sortie si non spécifié
|
||||
if not args.output:
|
||||
# Utiliser le même nom que le fichier CSV mais avec l'extension .html
|
||||
base_name = os.path.splitext(args.csv_file)[0]
|
||||
output_path = f"{base_name}_plotly.html"
|
||||
else:
|
||||
output_path = args.output
|
||||
|
||||
# Générer le graphique
|
||||
generate_plotly_graph(df, args.city_name, output_path, args.insee, args.csv_file)
|
||||
|
||||
print("Graphique interactif généré avec succès!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue