287 lines
9.3 KiB
Python
287 lines
9.3 KiB
Python
#!/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())
|