top of page

Tuto : calculer l’ensoleillement moyen dans une région (ou comment masquer un dataArray avec un shapefile en Python)

Dernière mise à jour : 7 nov.

Nous avons déjà vu plusieurs fois comment manipuler et exploiter des données météorologiques avec Python et Xarray, mais pour l’instant nous avons toujours travaillé soit sur un seul point soit sur l’ensemble du jeu de données. Comment faire lorsqu’on souhaite extraire plusieurs points appartenant à une forme complexe, par exemple des limites administratives (région, pays…) ou un bassin versant ?


découper un dataArray à partir d’un fichier shapefile contenant les limites des régions françaises

Dans ce tuto, nous allons voir comment “découper” un dataArray à partir d’un fichier shapefile contenant les limites des régions françaises, plus précisément nous allons :


  1. Télécharger un historique d’ensoleillement et l’ouvrir dans un dataArray,

  2. Télécharger un fichier shapefile contenant les limites des régions françaises, l’ouvrir avec Geopandas et extraire la forme d’une région,

  3. Créer un masque à partir de cette forme,

  4. Appliquer ce masque sur le datarray puis calculer l’ensoleillement moyen de la région.


Téléchargement et prétraitement des données météo


Pour commencer, téléchargez les données d’ensoleillement (Surface solar radiation downward) mensuelles d’ERA5-Land pour la France au format NetCDF. Nous avons déjà vu en détails de quoi il s’agit et comment vous pouvez y accéder via le Climate Data Store de Copernicus ou via l’API.


Une fois que vous avez téléchargé ces données, il faut les ouvrir et calculer la moyenne d’ensoleillement sur la période que l’on souhaite étudier (nous allons prendre 1981–2020). Là aussi, un autre tutoriel explique déjà comment faire mais reprenons rapidement…


D’abord, nous allons ouvrir le fichier NetCDF avec Xarray. Dans ce cas, le dataset ne contient qu’une seule variable ssrd, il faut cependant la sélectionner pour obtenir un dataarray :


import xarray as xrpath = "data.nc" #Chemin vers les donnéesDS = xr.open_dataset(path)da = DS["ssrd"]

On va ensuite sélectionner la période à étudier :

from datetime import date#Dates de début et de fin de la période à étudierdebut = date(1991, 1, 1)fin = date(2020, 12, 31)da = da.sel(time = slice(debut, fin))

Il ne reste plus qu’à calculer la moyenne de ce datarray sur la dimension “time” :

da = da.mean(dim = 'time')

Voici le code pour l’ensemble :


  1. import xarray as xr

  2. from datetime import date

  3. path = "data.nc"

  4. debut = date(1991, 1, 1)

  5. fin = date(2020, 12, 31)

  6. var = "ssrd"

  7. def open_data(path, var, debut, fin):

  8. '''

  9.       fonction prenant :

  10.       path (str) : chemin vers la réanalyse

  11.       var (str) : nom de la variable étudiée

  12.       debut (datetime.date) : date de début de la période à étudier

  13.       fin (datetime.date) : date de fin de la période à étudier

  14.       Et renvoyant un datarray contenant la moyenne sur la période à étudier

  15.       '''

  16.       DS = xr.open_dataset(path)

  17.       da = DS[var]

  18.       da = da.sel(time = slice(debut, fin))

  19.       da = da.mean(dim = 'time')

  20.       return da

  21. da = open_data(path, var, debut, fin)


Il n’est pas indispensable de définir une fonction pour ça, mais cela nous assure que toutes les données intermédiaires seront supprimées à la fin des calculs puisque ce sont des variables locales… Compte-tenu de la taille des jeux de données utilisés, les oublier en mémoire pourrait vite poser problème !


Il est possible de vérifier rapidement le résultat avec un simple :

da.plot()

Voilà le graphique obtenu :


Moyenne du dataArray sur la dimension "time"


On est bien en France. Les valeurs sont plus élevées dans le sud que dans le nord. Tout semble en ordre…


Téléchargement et ouverture des limites de région


Pour calculer l’ensoleillement moyen par région, il faut déjà connaitre les limites des régions… Celles-ci peuvent être téléchargées au format shapefile par exemple ici.


Vous obtenez un dossier compressé qui contient 4 fichiers portant le même nom mais avec des extensions différentes. Même si nous n’allons utiliser directement que le fichier .shp, les autres doivent être conservés dans le même dossier.


Pour ouvrir ce fichier, nous allons utiliser la librairie Geopandas, la cousine de Pandas spécialisée dans les données spatiales :

import geopandas as gpdregions = gpd.read_file("georef-france-region-millesime.shp")regions

Le résultat obtenu ressemble beaucoup à un dataframe classique, la différence réside dans le colonne “geometry” qui contient des objets géométriques (ou plus exactement des objets shapely). Dans notre cas, ces géométries sont des polygones représentant la forme des régions françaises.


On peut manipuler ce geodataframe comme on le ferait avec un dataframe. Par exemple pour filtrer les régions d’outre-mer et classer les régions par ordre alphabétique :

#Sélection des régions de métropoleregions = regions[regions.reg_area_co == 'FXX']#Classement par ordre alphabétiqueregions = regions.sort_values(by = 'reg_name_up')#Réinitialisation de l'indexregions = regions.reset_index(drop = True)

Il est possible de visualiser simplement les formes sélectionnées avec :

regions.plot()

Encore une fois tout semble en ordre :


Dataframe de la limite des régions en France

Appliquer un filtre à un dataarray à partir d’un shapefile


Maintenant que nous avons toutes données nécessaires, il nous faut sélectionner les points du datarray appartenant à une région. Comment faire ?

Nous allons utiliser la méthode .where. Cette méthode s’utilise de la façon suivante :

da.where(condition)

Les valeurs contenues dans le datarray sont laissées inchangées aux points où la condition est vraie. Si la condition est fausse, elles sont supprimées (c’est-à-dire remplacées par un NaN).

Par exemple, si on veut garder seulement les points du datarray où l’ensoleillement est supérieur à la moyenne, on peut le faire de la façon suivante :

#calcul de la moyennemoyenne = float(da.mean())#sélection des points où la valeur est supérieureda.where(da.values > moyenne)

Vérifions avec .plot() :

Une application utile de .where est de trouver les coordonnées d’une valeur précise dans un dataarray. Par exemple si vous souhaitez savoir quel est le point du datarray avec l’ensoleillement maximal, vous pouvez le faire de la façon suivante :


  1. #Calcul du maximum du datarray

  2. maximum = float(da.max())

  3. #On masque toutes les valeurs différentes du maximum

  4. da_ = da.where(da.values == maximum)

  5. #On supprime les latitudes où toutes les valeurs sont NaN

  6. da_ = da_.dropna(dim = "latitude", how = 'all')

  7. #Pareil avec les longitudes

  8. da_ = da_.dropna(dim = 'longitude', how = 'all')

  9. #Le dataarray ne contient plus qu'un point dont on peu extraire les coordonnées

  10. print("Latitude du point d'ensoleillement maximal :", float(da_.latitude))

  11. print("Longitude du point d'ensoleillement maximal :", float(da_.longitude))


Evidemment sélectionner les points se trouvant dans une région nécessite une condition plus complexe. Nous allons créer un masque, c’est-à-dire un tableau dont la valeur sera False (ou 0) pour les points se trouvant en dehors de la région et True (1) pour ceux se trouvant à l’interieur.


Commençons par récupérer la première région du geodataframe regions, il s’agit d’Auvergne-Rhône-Alpes puisque nous l’avons classé par ordre alphabétique :

region = regions.iloc[0]

Si on a les coordonnées d’un point, il est facile de tester s’il se situe dans cette région, pour cela il faut d’abord créer le point correspondant avec shapely :

import shapelypoint = shapely.geometry.Point(lon, lat)

Ensuite on peut simplement vérifier s’il se trouve dans la forme de la région de la façon suivante :

point.within(region.geometry)

On obtient True si le point est dans la geometry, False sinon.


Il ne reste plus qu’à faire la même chose pour tous les points du dataarray. Commençons par initialiser notre masque en créant un Numpy array de mêmes dimensions que le dataarray :

import numpy as npmasque = np.zeros(da.shape)

Ce masque est en fait un tableau dont chaque case correspond à un couple de latitude, longitude du dataarray : la valeur masque[m][n] correspond à la m-iène latitude dans da.latitude et à la n-ième longitude dans da.longitude. On peut savoir s’il se trouve dans la forme de la région de la façon suivante :

lat = float(da.latitude[m]) #m-ieme latitudelon = float(da.longitude[n]) #n_ieme longitude#Création du point de coordonnées lat,lonpoint = shapely.geometry.Point(lon, lat)#Teste si le point est dans la régionpoint.within(region.geometry)

On modifie ensuite le masque en fonction :

masque[m][n] = point.within(region)

Pour obtenir le masque complet, il suffit de parcourir l’ensemble de points d’assembler ces différentes étapes et de répéter l’opération pour l’ensemble des points. On peut le faire avec la fonction suivante :


  1. import shapely

  2. import numpy as np

  3. def creation_masque(da, region):

  4. '''

  5.      Fonction prenant :

  6.      da (datarray) : un dataarray a deux dimensions latitude et longitude

  7.      region (Pandas serie) : une ligne de geodataframe contenant la geometry à masquer

  8.      Et renvoyant :

  9.      masque (Numpy array) : masque pour le dataarray correspondant à la géometrie de la région

  10.      '''

  11.      #Initialisation du masque

  12.      masque = np.zeros(da.shape)

  13.      for m in range(masque.shape[0]):

  14.        for n in range(masque.shape[1]):

  15.      lat = float(da.latitude[m])

  16.                lon = float(da.longitude[n])

  17.      point = shapely.geometry.Point(lon, lat)

  18.      masque[m][n] = point.within(region.geometry)

  19. return masque


Appliquer le masque et calculer la moyenne


Le plus facile reste à faire : appliquer le masque que l’on vient de créer avec .where :

da_regional = da.where(masque)

Une petite vérification au cas où :

da_regional.plot()

On a bien sélectionné les points du dataarray se situant en Auverge-Rhône-Alpes :



On peut maintenant calculer l’ensoleillement moyen dans la région en prenant la moyenne du dataarray :

float(da_regional.mean())

On obtient 13 558 111 J/m², soit 3.766kWh/m². C’est un ordre de grandeur cohérent.


Pour terminer mettons l’ensemble du code en ordre afin de renouveler facilement le calcul pour chaque région :


  1. import pandas as pd

  2. #Initialisation d'un dataframe pour stocker les résultats

  3. df = pd.DataFrame(columns = ["Région", "Ensoleillement"])

  4. for _, region in regions.iterrows():

  5. #Récupération du nom de la région

  6.         Nom_region = region.reg_name

  7.         #Création du masque

  8.         masque = creation_masque(da, region)

  9.         #Application du masque

  10.         da_regional = da.where(masque)

  11.         #Calcul de la moyenne

  12.         moyenne = float(da_regional.mean())

  13.         #Ajout du résultat au dataframe

  14.         df.loc[len(df)] = [Nom_region, moyenne]

  15. #Pour un résultat plus lisible, on calcule l'ensoleillement

  16. #par rapport à la région la plus favorisée en pourcent

  17. df["Ensoleillement_relatif"] = (df.Ensoleillement / df.Ensoleillement.max()) * 100

  18. df.Ensoleillement_relatif = df.Ensoleillement_relatif.astype('int')

  19. #Puis on ordonne le dataframe en fonction de cette valeur

  20. df = df.sort_values(by = 'Ensoleillement_relatif', ascending = False)


La région la plus ensoleillée est la Corse, Provence-Alpes-Côte d’Azur est presque ex-aequo avec juste 3% de soleil en moins, suivie plus loin par l’Occitanie (-12%). Les régions les plus défavorisées sont l’Île-de-France (qui reçoit en moyenne 28% d’énergie solaire en moins que la Corse) et les Hauts-de-France (30% moins) :


Ensoleillement moyen par région


 


A propos : Callendar est une start-up spécialisée dans le développement de solutions innovantes pour l’évaluation des risques climatiques. Conscients du défi que représente l’adaptation au changement climatique, nous nous efforçons de partager notre expertise au travers d’outils gratuits ou de tutoriels comme celui-ci.

bottom of page