# load libraries
library(data.table)
library(sf)
library(floodam.data)
library(so.ii)
# set paths
LOCAL_DATA = TRUE
path_dataset = if (isTRUE(LOCAL_DATA)) {
# to avoid loading data from internet when working in local
file.path("_data-common", "permanent", "dataset")
} else {
"https://nextcloud.inrae.fr/public.php/dav/files/RR523jxPWLFdqyB/dataset"
}
path_local = file.path("_data-local", "so-ii", "to-adaptation")
path_ppri = file.path(path_dataset, "ppri_so_ii.rds")
path_hazard = file.path(path_dataset, "3M-ruissellement-2024-11-22.gpkg")
path_adaptation = file.path(path_local, "state_2024-09-04.csv.gz")
path_adaptation_hazard = file.path(path_local, "state-hazard_2024-09-04.csv.gz")
path_output_map = file.path("image", "map")Comparaison du ruissellement avec les observations des adaptations
Génération de cartes avec R qui permettent la comparaison de l’étendue spatiale du ruissellement avec les observations ponctuelles des adaptations des bâtiments.
Objectif
Cette note vise à montrer comment produire une carte pour un rendu visuel de la cohérence entre les observations de terrains de la TO adaptation et la représentation spatiale au niveau institutionnel de l’aléa d’inondation.
Paramétrage initial
Le code est destiné à être exécuté avec R. Il est donc nécessaire que R soit installé sur votre système.
Ce tutoriel utilise les bibliothèques suivantes :
- data.table qui permet (entre autres) de lire rapidement un jeu de données tabulaires.
- sf qui permet de réaliser de nombreux traitements avec les données géomatiques au format vectoriel.
- floodam.data qui permet de réaliser des opérations adaptées aux données de l’observatoire.
- so.ii qui permet notamment de réaliser des cartes centrées sur le territoire de l’observatoire.
Il est également présupposé que vous disposiez d’un dossier **_data-local** accessible depuis votre espace de travail.
Chargement des librairies et définition des chemins
Les données que nous pouvons communiquer sont en libre accès depuis internet. Certaines ne le sont pas, ce tutoriel n’est donc pas complètement reproductible en l’état pour les personnes extérieures à l’équipe.
Pour faciler les traitements internes à l’équipe, la variable LOCAL_DATA de type logical est définie. Quand elle vaut TRUE toutes les données sont chargées depuis les dépots locaux, les éventuels ajustements nécessaires seront alors sauvegardés. Quand elle vaut FALSE, les données en libre accès sont chargées depuis internet, les éventuels ajustements ne sont pas sauvegardés.
Chargement des données
Les données de ruissellement sont chargées. Elles sont au format gpkg dans notre exemple, mais quasiment n’importe quel format peut être lu avec la librairie sf.
Les données du PPRI sont également chargées. Elles dont déjà été traitées et sont au format natif rds. Cela permet de ne pas faire des traitements supplémentaires pour remettre les données au format voulu.
# data in GPKG format
hazard = if (isTRUE(LOCAL_DATA)) sf::st_read(path_hazard) else sf::st_read(url(path_hazard))
st_geometry(hazard) = "geometry"
if (!is.null(hazard[["area"]])) hazard[["area"]] = units::set_units(hazard[["area"]], km^2)
# ppri in rds format (R native format) + management of possible remote directory
ppri = if (isTRUE(LOCAL_DATA)) readRDS(path_ppri) else readRDS(url(path_ppri))
# Compressed data are managed
# Test if adapted files already exists, if not take none adapted file
if (!file.exists(path_adaptation_hazard)) {
adaptation = data.table::fread(path_adaptation, data.table = FALSE)
adaptation = sf::st_as_sf(adaptation, coords = c("lon", "lat"), crs = "WGS84")
ADAPTATION_NEED = TRUE
} else {
adaptation = data.table::fread(path_adaptation_hazard, data.table = FALSE)
adaptation = sf::st_as_sf(adaptation, coords = c("lon", "lat"), crs = "WGS84")
ADAPTATION_NEED = FALSE
}Traitements
Préparation des codes couleurs pour les cartes et des légendes
Pour la couche ruissellement, il est choisi d’utiliser des couleurs avec de la transparence via la fonction alpha() de la librairie scales.
Pour la couche ppri, la légende est créée à partir des informations existant dans le jeu de données. Les couleurs, les bordures, les noms y sont déjà définis.
Pour la couche des adaptations, il est choisi d’utiliser des couleurs sans transparence, les dégradés sont obtenus avec la fonction col_mix() de la librairie scales. Les couleurs sans transparence sont nécessaires car beaucoup de points (autant que d’adresses) vont être dessinés. À l’échelle choisie ces points vont nécessairement se chevaucher. Avec des couleurs transparentes, il en résulterait un mélange des couleurs qui ne pourraient pas être décrit dans la légende.
# hazard
col_hazard = c(
"Aléa très fort" = scales::alpha("red", .4),
"Aléa fort" = scales::alpha("red", .2),
"Aléa modéré" = scales::alpha("orange", .2),
"Aléa résiduel" = scales::alpha("blue", .2),
"Bande de sécurité" = scales::alpha("green", .2)
)
legend_hazard = list(
legend = names(col_hazard),
fill = col_hazard
)
# ppri
legend_ppri = unique(st_drop_geometry(ppri[c("regulation_category_name", "color")]))
names(legend_ppri) = c("legend", "fill")
rownames(legend_ppri) = legend_ppri[["legend"]]
legend_ppri = legend_ppri[c("Zone rouge", "Zone bleue", "Zone indéterminée"), ]
selection = ppri[["regulation_category_name"]] %in% rownames(legend_ppri)
ppri[["color"]][!selection] = NA
# adaptation
level_adaptation = c("with", "none", "unobserved")
col_adaptation = c(
"with - out" = scales::col_mix("blue", "white", .25),
"with - 1" = "green",
"with - 2" = scales::col_mix("green", "white", .25),
"with - 3" = scales::col_mix("green", "white"),
"none - 1" = "red",
"none - 2" = scales::col_mix("red", "orange"),
"none - 3" = "orange",
"none - out" = scales::col_mix("black", "white", .80),
"unobserved" = scales::col_mix("black", "white", .95)
)
legend_adaptation = list(
legend = c("Avec 'in'", "Sans 'in", "Non observé", "Avec 'out'", "Sans 'out'"),
col = col_adaptation[c("with - 1", "none - 1", "unobserved", "with - out", "none - out")],
pt.bg = col_adaptation[c("with - 1", "none - 1", "unobserved", "with - out", "none - out")],
pch = 21,
pt.cex = 1,
ncol = 2
)Croisement des informations
# A valid geometry for hazard to function... Takes a lot of time..
if (isTRUE(ADAPTATION_NEED) && !sf::st_is_valid(hazard)) {
hazard = sf::st_make_valid(hazard)
if (!"area" %in% names(hazard)) hazard[["area"]] = units::set_units(st_area(hazard), km^2)
if (isTRUE(LOCAL_DATA)) sf::st_write(hazard, path_hazard, delete_dsn = TRUE)
}
if (isTRUE(ADAPTATION_NEED) && !sf::st_is_valid(ppri)) {
ppri = sf::st_make_valid(ppri)
if (!"area" %in% names(ppri)) ppri[["area"]] = units::set_units(st_area(ppri), km^2)
if (isTRUE(LOCAL_DATA)) saveRDS(ppri, path_ppri)
}
# Affect hazard_level and ppri_level to each observation
# Give priority to highest level
if (isTRUE(ADAPTATION_NEED) && !all(sf::st_is_valid(hazard))){
adaptation[["hazard_level"]] = "Hors aléa"
for (h in rev(names(col_hazard))) {
sel_h = hazard[["hazard_level"]] %in% h
sel_a = floodam.data::in_layer(adaptation[0], hazard[sel_h, "geometry"])
adaptation[["hazard_level"]][sel_a] = h
}
adaptation[["ppri_level"]] = "Hors PPRI"
for (h in rev(legend_ppri[["legend"]])) {
sel_h = ppri[["regulation_category_name"]] %in% h
sel_a = floodam.data::in_layer(adaptation[0], ppri[sel_h, "geometry"])
adaptation[["ppri_level"]][sel_a] = h
}
# Save adapted data to avoid doing it again
dataset = sf::st_drop_geometry(adaptation)
coordinates = sf::st_coordinates(adaptation)
dataset[["lon"]] = coordinates[, "X"]
dataset[["lat"]] = coordinates[, "Y"]
data.table::fwrite(dataset, path_adaptation_hazard)
}Analyse des données
Données de ruissellement
# Select only Montpellier
hazard = hazard[hazard[["commune"]] == "34172", ]
# Prepare info for Montpellier
area_montpellier = units::set_units(
sf::st_area(so.ii::so_ii_collectivity[so.ii::so_ii_collectivity[["commune"]] == "34172", ]),
km^2
)
# Areas
hazard[["hazard_level"]] = factor(hazard[["hazard_level"]], levels = names(col_hazard))
area_hazard = aggregate(list(superficie = hazard[["area"]]), list(zone = hazard[["hazard_level"]]), sum)
area_hazard = data.frame(
zone = c(as.character(area_hazard[["zone"]]), "Hors zonage", "Montpellier"),
superficie = c(area_hazard[["superficie"]] , area_montpellier - sum(area_hazard[["superficie"]] ), area_montpellier)
)
area_hazard[["proportion"]] = round(area_hazard[["superficie"]] / area_montpellier * 100, 2)
area_hazard[["superficie"]] = round(area_hazard[["superficie"]], 2)
knitr::kable(area_hazard)
write.csv(area_hazard, "table/montpellier/area_hazard.csv", row.names = FALSE)| zone | superficie | proportion |
|---|---|---|
| Aléa très fort | 2.83 | 4.96 |
| Aléa fort | 9.01 | 15.81 |
| Aléa modéré | 3.40 | 5.96 |
| Aléa résiduel | 5.07 | 8.91 |
| Bande de sécurité | 0.02 | 0.03 |
| Hors zonage | 36.64 | 64.32 |
| Montpellier | 56.97 | 100.00 |
Données du PPRI
# Select only Montpellier
ppri = ppri[ppri[["commune"]] == "34172", ]
ppri[["area"]] = units::set_units(st_area(ppri), km^2)
# Areas
ppri[["ppri_level"]] = ppri[["regulation_category_name"]]
ppri[["ppri_level"]] = factor(ppri[["ppri_level"]], levels = legend_ppri[["legend"]])
area_ppri = aggregate(list(superficie = ppri[["area"]]), list(zone = ppri[["ppri_level"]]), sum)
area_ppri = data.frame(
zone = c(as.character(area_ppri[["zone"]]), "Hors PPRI", "Montpellier"),
superficie = c(area_ppri[["superficie"]] , area_montpellier - sum(area_ppri[["superficie"]] ), area_montpellier)
)
area_ppri[["proportion"]] = round(area_ppri[["superficie"]] / area_montpellier * 100, 2)
area_ppri[["superficie"]] = round(area_ppri[["superficie"]], 2)
knitr::kable(area_ppri)
write.csv(area_ppri, "table/montpellier/area_ppri.csv", row.names = FALSE)| zone | superficie | proportion |
|---|---|---|
| Zone rouge | 2.76 | 4.85 |
| Zone bleue | 0.76 | 1.33 |
| Zone indéterminée | 0.13 | 0.23 |
| Hors PPRI | 53.31 | 93.58 |
| Montpellier | 56.97 | 100.00 |
Adaptations observées et exposition au ruissellement
adaptation = adaptation[adaptation[["commune"]] == "34172", ]
adaptation[["state"]] = factor(adaptation[["state"]], levels = level_adaptation)
adaptation[["hazard_level"]] = factor(adaptation[["hazard_level"]], levels= c(names(col_hazard), "Hors aléa"))
adaptation = adaptation[order(adaptation[["state"]], decreasing = TRUE), ]
dataset = sf::st_drop_geometry(adaptation)
exposure_hazard = table(dataset[c("hazard_level", "state")])
exposure_hazard = addmargins(exposure_hazard, FUN = list("total" = sum))
knitr::kable(exposure_hazard)
write.csv(exposure_hazard, "table/montpellier/exposure_hazard.csv", row.names = FALSE)| with | none | unobserved | total |
|---|---|---|---|
| 57 | 393 | 473 | 923 |
| 167 | 4354 | 6350 | 10871 |
| 13 | 1003 | 1438 | 2454 |
| 22 | 1555 | 2576 | 4153 |
| 0 | 1 | 0 | 1 |
| 85 | 7334 | 11674 | 19093 |
| 344 | 14640 | 22511 | 37495 |
Adaptations observées et exposition au sens du PPRI
adaptation[["ppri_level"]] = factor(adaptation[["ppri_level"]], levels= c(legend_ppri[["legend"]], "Hors PPRI"))
adaptation = adaptation[order(adaptation[["state"]], decreasing = TRUE), ]
dataset = sf::st_drop_geometry(adaptation)
exposure_ppri = table(dataset[c("ppri_level", "state")])
exposure_ppri = addmargins(exposure_ppri, FUN = list("total" = sum))
knitr::kable(exposure_ppri)
write.csv(exposure_ppri, "table/montpellier/exposure_ppri.csv", row.names = FALSE)| with | none | unobserved | total |
|---|---|---|---|
| 57 | 393 | 473 | 923 |
| 167 | 4354 | 6350 | 10871 |
| 13 | 1003 | 1438 | 2454 |
| 22 | 1555 | 2576 | 4153 |
| 0 | 1 | 0 | 1 |
| 85 | 7334 | 11674 | 19093 |
| 344 | 14640 | 22511 | 37495 |
Génération des cartes
La librairie so.ii permet de générer facilement des cartes sur les périmètres inclus dans so-ii. Ici nous faisons une carte avec les caractéristiques suivantes :
- centrée sur Montpellier (
scope = "34172") - pas de thème particulier
- sauvegardée au format png (c’est induit par le choix de l’extension pour le fichier de sauvegarde).
Carte du ruissellement
map_so_ii(
dataset = list(
ban = list(
x = adaptation[0],
pch = 21,
cex = .1,
col = scales::col_mix("black", "white", .95),
bg = scales::col_mix("black", "white", .95)
),
hazard = list(
x = hazard[0],
col = col_hazard[hazard[["hazard_level"]]],
border = NA
)
),
dataset_legend = as.list(legend_hazard),
scope = "34172",
path = file.path(path_output_map, "runoff.png")
)Carte du PPRI
map_so_ii(
dataset = list(
ban = list(
x = adaptation[0],
pch = 21,
cex = .1,
col = scales::col_mix("black", "white", .95),
bg = scales::col_mix("black", "white", .95)
),
ppri = list(
x = ppri[0],
col = ppri[["color"]],
border = NA
)
),
dataset_legend = as.list(legend_ppri),
scope = "34172",
path = file.path(path_output_map, "ppri-2004-34172.png")
)Observations en fonction de l’exposition au ruissellement
col_dataset = rep(col_adaptation[["unobserved"]], length(adaptation[["state"]]))
col_dataset[adaptation[["state"]] %in% "none"] = col_adaptation[["none - out"]]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["hazard_level"]] %in% "Aléa modéré" ] = col_adaptation["none - 3"]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["hazard_level"]] %in% "Aléa fort"] = col_adaptation["none - 2"]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["hazard_level"]] %in% "Aléa très fort"] = col_adaptation["none - 1"]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["hazard_level"]] %in% "Aléa modéré" ] = col_adaptation[["with - 3"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["hazard_level"]] %in% "Aléa fort"] = col_adaptation[["with - 2"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["hazard_level"]] %in% "Aléa très fort"] = col_adaptation[["with - 1"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["hazard_level"]] %in% "Hors aléa" ] = col_adaptation[["with - out"]]
o = order(factor(col_dataset, levels = col_adaptation), decreasing = TRUE)
adaptation = adaptation[o, ]
col_dataset = col_dataset[o]
map_so_ii(
dataset = adaptation[0],
pch = 21,
cex = .1,
col = col_dataset,
bg = col_dataset,
dataset_legend = legend_adaptation,
scope = "34172",
path = file.path(path_output_map, "runoff-adaptation.png")
)Observations en fonction de l’exposition au PPRI
col_dataset = rep(col_adaptation[["unobserved"]], length(adaptation[["state"]]))
col_dataset[adaptation[["state"]] %in% "none"] = col_adaptation[["none - out"]]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["ppri_level"]] %in% "Zone indéterminée" ] = col_adaptation["none - 3"]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["ppri_level"]] %in% "Zone bleue"] = col_adaptation["none - 2"]
col_dataset[adaptation[["state"]] %in% "none" & adaptation[["ppri_level"]] %in% "Zone rouge"] = col_adaptation["none - 1"]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["ppri_level"]] %in% "Zone indéterminée" ] = col_adaptation[["with - 3"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["ppri_level"]] %in% "Zone bleue"] = col_adaptation[["with - 2"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["ppri_level"]] %in% "Zone rouge"] = col_adaptation[["with - 1"]]
col_dataset[adaptation[["state"]] %in% "with" & adaptation[["ppri_level"]] %in% "Hors PPRI" ] = col_adaptation[["with - out"]]
o = order(factor(col_dataset, levels = col_adaptation), decreasing = TRUE)
adaptation = adaptation[o, ]
col_dataset = col_dataset[o]
map_so_ii(
dataset = adaptation[0],
pch = 21,
cex = .1,
col = col_dataset,
bg = col_dataset,
dataset_legend = legend_adaptation,
scope = "34172",
path = file.path(path_output_map, "ppri-2004-adaptation.png")
)


