top of page

Risque incendie : en France, combien de sites industriels à risque sont situés en forêt ?

Dernière mise à jour : 7 nov.

Climate Lab

Le 4 mars 2022, un feu de forêt apparu dans les collines dominant la ville d’Uljin en Corée du Sud a été stoppé à quelques centaines de mètres d’une des plus grandes centrales nucléaires de la planète. Pendant l’été 2021, c’est une centrale à charbon située dans le sud de la Turquie qui avait été sauvée in-extremis d’un violent incendie.


Pour un site industriel, être situé à proximité d’une forêt représente un risque. Surtout avec le changement climatique : l’augmentation de la température donc de l’évapotranspiration et la modification des précipitations contribuent à fragiliser la végétation et à aggraver les incendies.


Mais à quel point est-il fréquent que des sites industriels dangereux soient situés en forêt ? C’est ce que nous allons déterminer dans cette nouvelle démonstration du ClimateLab.

Données sur les installations à risque

La liste des installation classées pour la protection de l’environnement est disponible via le site Georisques du Ministère de l’écologie. Elle contient la localisation de chaque installation ainsi que de nombreuses informations : nom, adresse, activité, régime de classement ICPE, classement Seveso, etc.


  1. import geopandas as gpd

  2. import os

  3. #Chemin vers le fichier

  4. path = "ICPE"

  5. file = "InstallationsClassees.shp"

  6. #Ouverture

  7. gdf = gpd.read_file(os.path.join(path, file))

  8. #Affichage des 10 premières lignes

  9. gdf.head(10)


Au total, un peu plus de 100 000 sites sont classés, il y a des raffineries, des papeteries, des dépôts de produits chimiques mais aussi des entrepôts de services municipaux ou des supermarchés... Beaucoup ne nous intéressent pas, nous n’allons garder que les sites qui présentent un risque majeur, c’est-à-dire les sites Seveso seuil haut.


Par simplicité, nous allons aussi exclure les sites situés dans des départements d’outre-mer.


  1. #Sélection des installations Seveso seuil haut

  2. seveso = gdf[gdf.lib_seveso == 'Seveso seuil haut']

  3. #Exclusion des départements d'outre-mer

  4. seveso = seveso[~seveso.num_dep.isin(['971', '972', '973', '974'])]


Données d’occupation des sols


Pour connaitre le type d’environnement et de végétation présent autour de chacun de ces sites, nous allons utiliser la base de données européenne Corine land Cover.


Basée sur des observations satellites, ces données ont une résolution spatiale de 100 m. Chaque pixel est classé dans une catégorie parmi 44, par exemple “tissu urbain continu”, “landes et broussailles”, “plages, dunes et sables” et bien sûr forêts.


Ces données sont disponibles sous plusieurs format, nous utiliserons la version .tif que l’on peut ouvrir avec Xarray (comme nous l’avons vu dans un tuto précédent). Pour limiter le volume de données à garder en mémoire, nous allons commencer par sélectionner grossièrement la zone qui nous intéresse : la France métropolitaine.


  1. import xarray as xr

  2. #Chemin vers les données Corine

  3. path = "u2018_clc2018_v2020_20u1_raster100m\DATA"

  4. file = "U2018_CLC2018_V2020_20u1.tif"

  5. #Ouverture des données

  6. da = xr.open_rasterio(os.path.join(path, file))

  7. da = da.sel(band = 1, drop = True)

  8. #Sélection de la métropole

  9. da = da.sel(y = slice(3200000, 2000000), x = slice(3200000, 4300000))

  10. #Affichage

  11. da.plot(vmin = 0, add_colorbar = False)


L’affichage permet de vérifier que l’on ne s’est pas trompé :


Données d'occupation des sols ) type d’environnement et de végétation présent autour de chacun de ces sites, nous allons utiliser la base de données européenne Corine land Cover


Définir la zone d’intérêt autour de chaque site


Les géométries fournies avec le geodataframe des sites Seveso ne sont en réalité que des points. La zone qui nous intéresse est plus étendue : nous allons donc les remplacer par des polygones quasi-circulaires de rayon 1000 mètres centrés sur chaque site.


Le système de coordonnées du geodataframe est le WGS84. Pour pouvoir exprimer le rayon de chaque cercle en mètres nous allons convertir vers une projections azimutale locale, créer le polygone puis reconvertir en WGS84.


Par exemple pour la première ligne :


  1. from functools import partial

  2. import pyproj

  3. from shapely.ops import transform

  4. #Rayon du cercle à tracer autour du site

  5. = 1000

  6. #sélection du premier site dans le géodataframe

  7. site = seveso.iloc[0]

  8. #Latitude et longitude du site

  9. latitude = site.geometry.y

  10. longitude = site.geometry.x

  11. #Création de la projection azimutale

  12. projection_azimutale_locale = f"+proj=aeqd +R=6371000 +units=m +lat_0={latitude} +lon_0={longitude}"

  13. #Transformation de WGS84 vers la projection locale

  14. wgs84_to_projlocale = partial(

  15. pyproj.transform,

  16.            pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"),

  17.            pyproj.Proj(projection_azimutale_locale),

  18. )

  19. #Transformation de la projections locale vers WGS84

  20. projlocale_to_wgs84 = partial(

  21. pyproj.transform,

  22.        pyproj.Proj(projection_azimutale_locale),

  23.        pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"),

  24. )

  25. #Conversion du point vers la projection locale

  26. centre = transform(wgs84_to_projlocale, site.geometry)

  27. #Création d'un buffer de rayon r autour de ce point

  28. cercle = centre.buffer(r)

  29. #Conversion de ce polygone vers la projection originale

  30. cercle = transform(projlocale_to_wgs84, cercle)



On peut créer rapidement une carte folium pour s’assurer que tout va bien :


  1. import folium

  2. #Paramètres de la carte

  3. location = [latitude, longitude]

  4. zoom = 14

  5. tiles = 'cartodbpositron'

  6. #Création de la carte

  7. Carte = folium.Map(location = location,

  8. zoom_start = zoom,

  9. tiles = tiles)

  10. #Affichage du point

  11. point = gpd.GeoSeries(site.geometry)

  12. point_j = folium.GeoJson(data = point.to_json())

  13. point_j.add_to(Carte)

  14. #Affichage du polygone

  15. polygone = gpd.GeoSeries(cercle, crs = 'WGS84')

  16. polygone_j = folium.GeoJson(data = polygone.to_json())

  17. polygone_j.add_to(Carte)

  18. display(Carte)


Le résultat correspond bien à ce qui est souhaité :


zone d’intérêt autour de chaque site - carte folium

Il ne reste donc plus qu’à faire la même chose pour chaque point et à remplacer les géométries contenues dans le geodataframe :


  1. for i, site in seveso.iterrows():

  2. #Latitude et longitude du site

  3. latitude = site.geometry.y

  4. longitude = site.geometry.x

  5. #Création de la projection azimutale

  6. projection_azimutale_locale = f"+proj=aeqd +R=6371000 +units=m +lat_0={latitude} +lon_0={longitude}"

  7. #Transformation de WGS84 vers la projection locale

  8. wgs84_to_projlocale = partial(

  9. pyproj.transform,

  10.        pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"),

  11.        pyproj.Proj(projection_azimutale_locale),

  12. )

  13. #Transformation de la projections locale vers WGS84

  14. projlocale_to_wgs84 = partial(

  15. pyproj.transform,

  16. pyproj.Proj(projection_azimutale_locale),

  17.       pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"),

  18. )

  19. #Conversion du point vers la projection locale

  20. centre = transform(wgs84_to_projlocale, site.geometry)

  21. #Création d'un buffer de rayon r autour de ce point

  22. cercle = centre.buffer(r)

  23. #Conversion de ce polygone vers la projection originale

  24. cercle = transform(projlocale_to_wgs84, cercle)

  25. seveso.loc[i, "geometry"] = cercle



Croisement des deux jeux de données


Pour chaque site Seveso, nous avons créé un polygone définissant la zone qui nous intéresse. L’objectif est maintenant d’extraire les données Corine dans ce polygone et de voir si on y trouve des forêts.

Comme les données Corine utilise une projection différente (ETRS89, une projection azimutale équivalente de Lambert), il faut avant tout convertir le geodataframe vers le crs correspondant :


  1. #Transformation vers le système de coordonnées du datarray

  2. seveso = seveso.to_crs(da.crs)


Commençons encore une fois par travailler sur le premier site de la liste. Nous allons d’abord récupérer les coordonnées maximales et minimales du polygone afin de ne garder que la partie du datarray qui nous intéresse :


  1. #sélection du premier site dans le géodataframe

  2. site = seveso.iloc[0]

  3. #Récupération la bounding box de la zone

  4. xmin, ymin, xmax, ymax = site.geometry.bounds

  5. #Réduction du datarray à cette zone

  6. da_reduit = da.sel(x = slice(xmin, xmax), y = slice(ymax, ymin))


Ensuite, nous allons masquer les points du datarray restant qui se trouvent en dehors du polygone (la fonction utilisée est inspirée d’un tuto précédent) :


  1. import numpy as np

  2. from shapely.geometry import Point

  3. def creation_masque(da, region):

  4. '''

  5.        Fonction prenant :

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

  7.        region (polygone) : la geométrie utilisée pour créer le masque

  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.        x = float(da.x[n])

  16.                 y = float(da.y[m])

  17.                 point = Point(x, y)

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

  19.        return masque

  20. #Création du masque

  21. masque = creation_masque(da_reduit, site.geometry)

  22. #Application du masque et affichage du résultat

  23. da_reduit.where(masque).plot()



On obtient le résultat suivant :


Croisement des jeux de données et conservation des données du datarray conservées dans le polygone

Dans les données Corine, les forêts correspondent aux codes 23, 24 et 25, respectivement forêts de feuillus, de conifères et mixtes. Y en a-t-il autour de ce site ? On peut le calculer de la façon suivante :


  1. codes_foret = [23, 24, 25]

  2. codes = da_reduit.where(masque).data.flatten()

  3. codes = codes[~np.isnan(codes)]

  4. pixels_foret = codes[np.isin(codes, codes_forets)]

  5. print(f"Pourcentage de forêt : {round(len(pixels_foret)/len(codes) * 100, 2)}%")


Pas l’ombre d’une forêt ici. Selon ces données, l’environnement dans un rayon de 1 kilomètre autour du site est composé à 56% de zone portuaire, à 18% de tissu urbain discontinu, 17% de voies d’eau, 4% de plans d’eau et 4% de terres arables non-irriguées. Cela ressemble effectivement à une description de l’avant-port de Strasbourg où se trouve ce site :


Calcul des zones de forêts de feuillus, conifères et mixtes

Synthèse et résultats

Il ne reste plus qu’à reproduire ce calcul pour tout les sites :


  1. #Initialisation d'une colonne pour le résultat

  2. seveso["prct_foret_1km"] = np.nan

  3. for i, site in seveso.iterrows():

  4. #Récupération la bounding box de la zone

  5.      xmin, ymin, xmax, ymax = site.geometry.bounds

  6.      #Réduction du datarray à cette zone

  7.      da_reduit = da.sel(x = slice(xmin, xmax), y = slice(ymax, ymin))

  8.      #Création du masque

  9.      masque = creation_masque(da_reduit, site.geometry)


  10.      #Récupération des données dans la zone et calcul du pourcentage de foret

  11.      codes = da_reduit.where(masque).data.flatten()

  12.      codes = codes[~np.isnan(codes)]

  13.      pixels_foret = codes[np.isin(codes, codes_forets)]

  14.      prct_foret = len(pixels_foret)/len(codes) * 100

  15.      seveso.loc[i, "prct_foret_1km"] = prct_foret

  16. #Liste des sites situés à proximité de forêt

  17. seveso[seveso.prct_foret_1km > 0]


Et voilà notre résultat : sur 616 sites industriels classés Seveso seuil haut, 276 sont situés à proximité d’une forêt. L’environnement est composé à 10% ou plus de forêt pour 157 sites et à 20% ou plus pour 90.



 


A propos : Callendar est une start-up spécialisée dans le développement de solutions innovantes pour l’évaluation des risques climatiques. Le ClimateLab permet de mobiliser rapidement nos outils et notre expertise pour répondre à une question précise.

bottom of page