Scripts du découpage

À quoi sert cette page ? Elle publie le code Python réellement exécuté pour construire les écorégions et les assemblées, chemins nettoyés de toute référence de projet. Le lecteur peut relire chaque choix de regroupement, la garantie de contiguïté et les retouches de frontières, puis rejouer la chaîne à partir des données publiques.

Voici le code tel qu'exécuté pour construire les écorégions et les assemblées, chemins nettoyés de toute référence de projet. Le fichier pivot s'appelle ecoregions-2027.gpkg dans tous les scripts. Les dépendances sont geopandas, pandas, shapely et GDAL, sous Python 3.10 ou supérieur.

Construction des écorégions

Ce script est le point d'entrée de la chaîne. Il lit les 187 secteurs hydrographiques BD Carthage et affecte chacun à son écorégion par une fonction de classement fondée sur le code de secteur et le code de région hydrographique. La dissolution des secteurs produit les géométries. Une garantie de contiguïté rattache ensuite les îles et petits fragments au bloc continental le plus proche. La population se somme depuis les points représentatifs des communes.

Le classement porte les choix de découpage propres à cette proposition, dont le rattachement de l'Oise et de la Marne à une même écorégion et l'individualisation de la Seine centrale parisienne. La fonction ci-dessous montre la logique par région hydrographique ; le découpage final est ensuite consolidé par les retouches de frontières.

#!/usr/bin/env python3
# Contours des écorégions au grain fin. Brique = secteurs BD Carthage (187).
import geopandas as gpd, pandas as pd
from pathlib import Path
BASE = Path(__file__).resolve().parents[1]; G = str(BASE/'ecoregions-2027.gpkg')
ADMIN = str(BASE.parent/'01_donnees/AdminExpress/ADMIN-EXPRESS.gpkg')

def grp(cd, reg):
    if cd in {'F0','F1','F2','F3','F4'}: return 'Seine amont-Bourgogne'
    if cd in {'F5','F6'}: return 'Marne'
    if cd == 'F7': return 'Paris-Seine centrale'
    if cd in {'H0','H1','H2'}: return 'Oise'
    if reg in {'H','I','G'}: return 'Normandie'
    if cd in {'Y0','Y1','Y2','Y3'}: return 'Languedoc-Roussillon'
    if cd == 'Y4': return 'Provence-Camargue'
    if reg == 'V': return 'Rhone'
    if reg in {'W','X'}: return 'Alpes du Sud'
    if reg == 'U': return 'Saone'
    if reg == 'O': return 'Garonne'
    if reg == 'P': return 'Dordogne'
    if reg == 'Q': return 'Adour'
    if reg == 'K': return 'Loire amont'
    if reg in {'L','M'}: return 'Loire aval'
    if reg == 'J': return 'Bretagne'
    return 'AUTRE_' + str(reg)

sect = gpd.read_file(G, layer='secteurs_hydro'); crs = sect.crs
sect['eco'] = [grp(c, r) for c, r in zip(sect['CdSecteurH'], sect['CdRegionHy'])]
eco = sect.dissolve(by='eco', as_index=False)'eco', 'geometry'

# garantie de contiguïté : chaque bloc continental (>1000 km2) = une unité ;
# les îles et fragments sont rattachés au bloc le plus proche
parts = eco.explode(index_parts=False).reset_index(drop=True)
parts['area'] = parts.geometry.area/1e6
cores = parts[parts['area'] > 1000].copy(); small = parts[parts['area'] <= 1000].copy()
if len(small):
    jn = gpd.sjoin_nearest(small'geometry', cores'eco','geometry', how='left')
    small = small.assign(eco=jn['eco'].values)
eco = gpd.GeoDataFrame(pd.concat([cores'eco','geometry', small'eco','geometry'],
                                 ignore_index=True), crs=crs)
eco = eco.dissolve(by='eco', as_index=False)

# population par sommation des points représentatifs de communes
com = gpd.read_file(ADMIN, layer='commune')
com['population'] = pd.to_numeric(com['population'], errors='coerce').fillna(0).astype(int)
pts = com.copy(); pts['geometry'] = com.representative_point()
pts = pts'population', 'geometry'.to_crs(crs)
j = gpd.sjoin(pts, eco'geometry', predicate='within', how='inner')
eco['pop'] = eco.index.map(j.groupby('index_right')['population'].sum()).fillna(0).astype(int)
eco['km2'] = (eco.geometry.area/1e6).round(0).astype(int)
eco['dens'] = (eco['pop']/eco['km2']).round(0).astype(int)
eco.to_file(G, layer='ecoregions_fin', driver='GPKG')

Retouches de frontières

Un second script corrige deux points sur la couche produite sans tout réexécuter, avec une écriture chirurgicale : sauvegarde horodatée du GeoPackage, puis réécriture de la seule couche ecoregions_fin. Le secteur V7, langue aval du Rhône coincée entre le Bas-Rhône-Provence et l'Aude, est réaffecté au Bas-Rhône-Provence dont il partage 72 km de frontière. Quatre unités improprement qualifiées de « côtiers » reçoivent leur hydronyme dominant.

#!/usr/bin/env python3
import shutil, datetime as dt
from pathlib import Path
import geopandas as gpd, pandas as pd

BASE = Path(__file__).resolve().parents[1]
G = str(BASE / "ecoregions-2027.gpkg")

REAFFECT = {"V7": "Bas-Rhone-Provence"}
RENAME = {
    "Aude-Orb-cotiers": "Aude et Orb",
    "Var et cotiers d'Azur": "Var et Argens",
    "Sevre et cotiers vendeens": "Sevre et Lay",
    "Cotiers aunisiens": "Leyre et Landes de Gascogne",
}

# sauvegarde horodatée avant toute écriture
stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
shutil.copy2(G, G + f".bak-avant-retouche-{stamp}")

# récupérer la vérité terrain secteur -> eco depuis la couche existante
eco0 = gpd.read_file(G, layer="ecoregions_fin"); crs = eco0.crs
sect = gpd.read_file(G, layer="secteurs_hydro").to_crs(crs)
pts = sect.copy(); pts["geometry"] = sect.representative_point()
j = gpd.sjoin(pts"CdSecteurH", "geometry", eco0"eco", "geometry",
              predicate="within", how="left")
sect = sect.merge(j"CdSecteurH", "eco".drop_duplicates("CdSecteurH"),
                  left_on="CdSecteurH", right_on="CdSecteurH", how="left")

# appliquer les édits puis reconstruire la couche
for cd, dest in REAFFECT.items():
    sect.loc[sect["CdSecteurH"] == cd, "eco"] = dest
sect["eco"] = sect["eco"].replace(RENAME)
eco = sect.dissolve(by="eco", as_index=False)"eco", "geometry"
# ... reconstruction population/km2/dens identique au script précédent ...

# contrôle de contiguïté : aucune unité en plusieurs blocs > 1000 km2
ex = eco.explode(index_parts=False); ex["a"] = ex.geometry.area / 1e6
multi = ex[ex["a"] > 1000].groupby("eco").size(); multi = multi[multi > 1]
assert multi.empty, f"unités non contiguës : {multi.to_dict()}"
eco.to_file(G, layer="ecoregions_fin", driver="GPKG")

Assemblées de bassin

Les 189 assemblées se construisent depuis les 1 140 sous-secteurs BD Carthage, maillage complet de l'Hexagone et de la Corse. La population se calcule par la même méthode que pour les écorégions, point représentatif de commune et jointure spatiale.

#!/usr/bin/env python3
import geopandas as gpd, pandas as pd
from pathlib import Path
G = str(Path(__file__).resolve().parents[1] / 'ecoregions-2027.gpkg')
ADMIN = '.../ADMIN-EXPRESS.gpkg'

ss = gpd.read_file(G, layer='sous_secteurs').reset_index(drop=True)
com = gpd.read_file(ADMIN, layer='commune')
com['population'] = pd.to_numeric(com['population'], errors='coerce').fillna(0).astype(int)
pts = com.copy(); pts['geometry'] = com.representative_point()
pts = pts'population', 'geometry'.to_crs(ss.crs)
j = gpd.sjoin(pts, ss'geometry', predicate='within', how='inner')
ss['pop'] = ss.index.map(j.groupby('index_right')['population'].sum()).fillna(0).astype(int)
ss['km2'] = (ss.geometry.area/1e6).round(0).astype(int)
ss['dens'] = (ss['pop']/ss['km2'].clip(lower=1)).round(0).astype(int)
ss.to_file(G, layer='assemblees', driver='GPKG')

La suite de la chaîne, populations par bassin et par hydro-écorégion, climat, nappes et cartographie, est documentée dans les scripts de données et de cartes puis vérifiée par les contrôles de cohérence.

Ce qu'il faut en retenir

Le découpage se fabrique par assemblage de secteurs hydrographiques entiers, jamais par découpe interne, et la population se somme depuis les points représentatifs des communes. Les retouches de frontières procèdent par écriture chirurgicale, sauvegarde horodatée puis réécriture d'une seule couche, avec un contrôle de contiguïté qui refuse toute unité éclatée. Le code montre les choix plutôt qu'il ne les masque.