Cartes avec Python/Geopandas

Où l’on présente quelques cartes réalisées avec Python

Je crée un axe principal ax1 pour les départements de métropole, et 6 axes insérés dans le premier, pour les départements d’outremer ainsi que pour la région parisienne.

La variable ‘dep’ de type DataFrame, contenant les données, est agrémentée d’une colonne appelée ‘random’ contenant un nombre aléatoire. Ce nombre sert de critère pour que le colormap attribue une couleur au département. Ici j’ai utilisé le colormap ‘RdPu’ (“red to purple”), mais dans ce cas d’autres types de colormaps conviendraient. Celui par défaut est viridis, qui est adapté aux daltonien.nes.

Les départements sont séparés en deux DF, un pour la métropole, un autre pour l’outremer ; en effet, l’affichage des contours pour la métropole est différent de celui des outremer. Il y a des fonctions qui trient les départements dans deux DF distincts.

dromliste = ['La Réunion', 'Martinique', 'Guadeloupe',
            'Guyane', 'Mayotte']
def suppr_drom(df):
    """
    supprime les drom du df.
    """
    df2 = df
    for idx, row in df2.iterrows():
        if row['nom'] in dromliste:
            df2 = df2.drop(idx)
    return df2

def filtredrom(df):
    """
    ne conserve que les drom du df.
    """
    df2 = df
    for idx, row in df2.iterrows():
        if row['nom'] not in dromliste:
            df2 = df2.drop(idx)
    return df2

drom = filtredrom(dep)  # tableau des drom uniquement
metro = suppr_drom(dep)  # tableau métropole uniquement

Il y a une difficulté, ici, pour supprimer les lignes d’un df avec “drop”. Je n’ai pas tout compris, mais cette commande modifie le df initial, même si on crée un nouveau df contenant les données restantes. Ça marche en faisant “df2 = df” dans la fonction et en renvoyant df2. Peut-être le mieux aurait encore été de faire “df2 = df.copy()”. D’autre part, la rédaction de la condition dans les fonctions pourrait être améliorée.

Méthode de tracé des départements différente entre métropole et outremer :

  • métropole : une commande de type df.plot(ax=ax1, column=’random’) suffit, car les contours sont contigus. Cette commande ne serait pas adaptée pour des territoires situés à des milliers de km les uns des autres.
  • outremer : puisque chaque département est tracé sur un axe différent, la commande précédente est inadaptée. La solution choisie ici consiste à parcourir un à un les départements d’outremer dans le DF correspondant, en indiquant un axe différent, dont la donnée est stockée dans la colonne ‘axe’.

Dans ce cas, apparemment la ligne n’a pas de géométrie (c’est un objet geoSeries et non geoPandas). Il faut donc récupérer sa géométrie d’abord, puis tracer la geoSeries en précisant la géométrie.
La couleur ne peut pas être indiquée de la même manière que pour la métropole.

for idx, row in drom.iterrows():
    geom = drom.loc[idx]['geometry']
    couleur = drom.loc[idx]['random']
    axe = drom.loc[idx]['axe']
    gpd.GeoSeries(geom).plot(ax=axe, color=cmap(couleur),
                             edgecolor='k', alpha=0.5, lw=0.2)
# affichage de données sur les départements :
for idx, row in metro.iterrows():
    if row['code_insee'] in ['95','78','91','77','69M']:
        texte = row['code_insee']
        taille_txt = 3
    elif row['code_insee'] in ['75','93', '94','92']:
        texte = row['code_insee']
        taille_txt = 2
    else:
        texte = row['nom'] + '\n' + row['code_insee']
        taille_txt = 3
    ax.annotate(text=texte, xy=(row['centroid'].x,row['centroid'].y),
                 horizontalalignment='center', color = 'k',
                 fontsize=taille_txt, va='center')

Le script Python avec les fichiers d’informations géographiques compressés :


Fichiers SIG : le fichier ‘georef-france-region-millesime.shp’ (issu de https://data.opendatasoft.com) comporte toutes les régions de France (métropole, régions d’outremer et collectivités d’outremer). Cependant, les contours des côtes y sont beaucoup moins satisfaisants pour certaines collectivités (Clipperton, Saint-Martin, Saint-Barthélémy) que pour les autres. C’est pourquoi le script charge en complément les données pour ces 3 dernières collectivités, et qui sont issues du site geofabrik.de. Il faut donc procéder à quelques traitements pour homogénéiser les tableaux correspondants, avant de les fusionner.

Résumé du code

Après avoir chargé les données de 3 fichiers dans des DataFrames (toutes régions – Saint-Barthélémy et Saint-Martin – Clipperton), on supprime du 1er fichier les régions présentes dans les autres, puis on affecte à ces 3 DF des colonnes communes qui seront utilisées dans le script : ‘geometry’ (colonne déjà présente), ‘nom’, ‘axe’. La colonne ‘axe’ affecte à chaque région un des 14 axes graphiques créés : 1 pour la métropole, 5 pour les Rom et 8 pour les Com. Ensuite les données sont concaténées en un seul DF. Enfin, une boucle parcourt les lignes de ce DF. Pour affecter une couleur à chaque région, j’utilise un colormap (ici, ‘YlOrRd’ : jaune-orange-rouge) et je génère un nombre aléatoire pour chaque ligne du DF. Chaque région est colorée avec la couleur associée par ce colormap au nombre aléatoire).

Quelques commentaires du code

Appel des modules (bibliothèques) nécessaires :

from numpy import random  # listes de nombres aléatoires
import matplotlib.pyplot as plt  # graphiques
import geopandas as gpd  # gestion de données carto
from pandas import concat  # concaténer les DataFrames.

Fonctions créées :

def crochets_suppr(str):
    """
    Supprime les 2 premiers et 2 derniers caractères d'une chaine.
    Nécessaire à cause du formatage des noms dans le fichier reg.
    """
    return str[2:-2]

Dans le 1er fichier importé, les noms des régions sont données entre deux paires de guillemets et une paire de crochets. On se débarrasse de ces signes inutiles en supprimant les 2 caractères au début et à la fin des strings.

Import des données et attribution d’un même système CRS de coordonnées :

reg = gpd.read_file('georef-france-region-millesime/georef-france-region-millesime.shp')
reg.to_crs(epsg=3395, inplace=True)  # projection dans un CRS, pour calculs ultérieurs
# fichier pour Saint-Martin et Saint-Barthélémy :
sb_sm = gpd.read_file('REGION_sb_sm/REGION_sb_sm.shp')
sb_sm.to_crs(reg.crs, inplace=True)
# fichier pour Clipperton :
clipp = gpd.read_file('ile-de-clipperton-latest-free.shp/gis_osm_natural_a_free_1.shp')
clipp.to_crs(reg.crs, inplace=True)

Opérations sur les DF :

#---------- OPERATIONS SUR LES DF :----------------------
# suppression de St-Barthélémy, St-Martin et Clipperton de reg :
reg.drop([5,16,17], inplace = True)  

# ajout de colonnes "nom" :
reg['nom'] = reg.reg_name.apply(crochets_suppr)
sb_sm['nom'] = sb_sm.NOM
clipp['nom'] = 'Clipperton'

# concaténation de tous les tableau dans un seul df :
df = concat([reg, sb_sm, clipp], ignore_index=True)
# génération de nombres aléatoires appelés 'random' pour couleurs :
random.seed(1)
df['random'] = random.rand(len(df), 1)

# calcul du centroide des départements :
df['centroid'] = df.centroid

La figure comporte 2 grands axes principaux l’un sous l’autre : en haut pour la métropole, avec 5 petits axes insérés dans le 1er, pour y tracer les Rom. Le grand axe du bas est constitué de 8 axes secondaires insérés, pour les Com.

La fonction ‘attrib_axes()’ qui attribue les axes aux régions est présentée clairement pour modification. J’ai choisi de représenter dans la mesure du possible les positions des Rom et Com sur le planisphère relativement à la métropole. Une fois que toutes les régions sont rassemblées dans un même DF, certaines opérations sont beaucoup plus courtes :

# ajout d'une colonne 'axe' dans le df
df['axe'] = attrib_axes()

# suppression des cadres des axes :
for axe in df.axe:
    axe.set_axis_off()

Boucle d’affichage des régions :

# affichages des données géo :

for idx in range(len(df)):
    geom = df.iloc[idx]['geometry']
    couleur = df.iloc[idx]['random']
    axe = df.iloc[idx]['axe']
    texte = df.iloc[idx]['nom']
    if texte == "Polynésie française":  # Polynésie française, zoom sur Tahiti
        axe.set_xlim(-1.69e7,-1.655e7)
        axe.set_ylim(-2.05e6,-1.8e6)
        x = -1.675e7
        y = -1.93e6
        axe.text(0.01, 0.01,'(Tahiti)',
                 horizontalalignment='left', color = 'k',
                 va='center', transform=axe.transAxes)
    elif texte == "Terres australes et antarctiques françaises":  # TAAF, zoom sur Kerguélen
        axe.set_xlim(7.55e6, 7.86e6)
        axe.set_ylim(-6.5e6, -6.1e6)
        x = 7.7e6
        y = -6.15e6
        axe.text(0.01, 0.01,'(Kerguélen)',
                 horizontalalignment='left', color = 'k',
                 va='center', transform=axe.transAxes)
    else:
        x = df.iloc[idx]['centroid'].x
        y = df.iloc[idx]['centroid'].y
    # contours colorés des régions :
    gpd.GeoSeries(geom).plot(ax=axe, color=cmap(couleur),
        edgecolor='k', alpha=0.8, lw=0.05)
    # annotations :
    axe.annotate(text=texte, xy=(x,y),
       horizontalalignment='center', color = 'k',
       fontsize=3, va='center')

Pour la Polynésie française et les TAAF, les îles sont tellement distantes les unes des autres que leur tracé sur un même axe est illisible. J’ai pris le parti de zoomer les axes sur des îles plus visibles (Tahiti et quelques îles environnantes, et Kerguélen).

À noter que la syntaxe de cette boucle est bien plus verbeuse que pour le cas de territoires contigus (pays, régions, continent…) où un simple ‘df.plot()’ suffirait. C’est principalement parce que les axes sont différents d’une région à l’autre. Il faut donc une boucle qui parcourt ligne par ligne le DF, en allant cherche dans les colonnes les infos nécessaires (nom, géométrie, axe…). La commande ‘gpd.GeoSeries(geom).plot()’ nécessite en argument la géométrie à tracer, qu’il faut au préalable avoir récupéré dans le DF.

Le script et les fichiers shapefile (.shp) compressés :


Un fichier .shp fournit les contours des pays, et un autre les positions et populations des capitales. L’essentiel du travail est ici de sélectionner un sous-continent, tant pour le fichier des pays que pour celui des villes, puis de sélectionner les capitales et les villes très peuplées (ici, seule Sao Paulo est une ville de +10M hab. sans être une capitale).


Ce script permet d’afficher une carte de France métropolitaine, et de placer les communes souhaitées en indiquant leur nom.

Dans le script, l’utilisateur indique le nom de la commune souhaitée, comme argument dans une fonction ‘point_ville_nom()’. Si ce nom est commun à plusieurs communes, il existe une fonction ‘recherche_lettres’ qui donne la liste des communes concernées, avec leur index de classement, qui est unique. On utilise alors la fonction ‘point_ville_index()’ pour afficher la commune souhaitée.

Les communes des collectivités d’outremer sont absentes du fichier de données.

Si on demande une commune d’un département d’outremer, la carte sera étirée et le point ne sera pas placé sur un fond de carte. Voir ci-dessous une seconde version avec les départements d’outremer.

Le fichier de communes française que j’ai trouvé est issu de https://www.data.gouv.fr/fr/datasets/decoupage-administratif-communal-francais-issu-d-openstreetmap/. Il est volumineux, puisqu’il comporte les contours des 35 000 communes, alors que je n’avais besoin que de leur centroïde pour cette carte. Pour raccourcir le délai d’exécution du script, j’ai calculé le centroïde des communes, puis créé un nouveau fichier (un DataFrame pandas), comportant uniquement 3 colonnes : noms, X et Y. Le résultat est un fichier de 1.9Mo (contre plusieurs centaines auparavant), appelé “communes.csv”. J’ai pu modifier légèrement le script pour qu’il charge les données des communes à partir de ce nouveau fichier plutôt que le fichier initial. Le temps de chargement est bien plus court.

Dans ce fichier compressé, le script Python avec les 2 fichiers de données annexes :


Seconde version avec l’affichage des régions d’outremer (Guadeloupe, Martinique, Guyane, Mayotte et La Réunion).

Le script Python et les 2 fichiers annexes :

Un commentaire

  1. Anonyme said:

    Merci beaucoup pour ce travail une fois de plus remarquable et source d’inspirations.
    Bien cordialement

    27 août 2024

Laisser un commentaire