L’objectif de ce cours interactif est de vous présenter les différents concepts permettant d’analyser et de simuler l’architecture (i.e. les limites de parcelles) et la composition (i.e. les différentes catégories d’occupation du sol) d’un parcellaire agricole.
Il portera sur différentes librairies liées au logiciel R, telles que sp, spatstat et raster.
Le domaine spatial étudié est le bassin versant de Roujan (43.300 N, 3.190 E) d’une surface de 0.91 \(km^2\). Le bassin de Roujan est un observatoire de recherche à long-terme (ORE OMERE) équipé par l’INRA depuis 1992. La pluviométrie totale annuelle varie entre 500 et 1400 mm. Le bassin est couvert principalement de vignes avec 237 parcelles cultivées d’une surface variant de 0.03 à 2.2 ha. Ce bassin accueille également un réseau hydrographique dense (11 km). Le domaine étudié débordera cependant des strictes limites du bassin, pour limiter les effets de bord.
Situation des différents bassins versants
Les modèles numériques de terrain (MNTs) disponibles pour ce projet sont à 5 m de résolution spatiale, acquis par stéréophotogrammétrie aérienne (5 m, source CD 34).
Une image aérienne RGB à 50 cm de résolution (BD-Ortho, Source IGN) est disponible sur la zone et sera utilisée pour illustrer certaines parties du projet.
Une carte d’occupation du sol par parcelle a été réalisée par photo-interprétation et relevé terrain (Source : ORE Omere).
Ce cours a pour objectifs de proposer une série d’exemple d’utilisation de R pour analyser et simuler un paysage agricole sous contraintes.
Il s’adresse à des étudiants ayant déjà de bonnes connaissances de l’environnement de travail R et souhaitant réaliser des analyses statistiques sur des données spatialisées.
Les données nécessaires à la réalisation du TP sont disponibles à l’adresse suivante:
https://www.dropbox.com/sh/3kgclyo75h77yvr/AACmnJH7wI8atel3HqZwk9Mta?dl=0
Dossier datas à télécharger (clic droit sur le dossier) et dézipper.
library(sp)
library(spatstat)
library(raster)
library(maptools)
library(rgdal)
library(randomForest)
library(gridExtra)
library(scales)
library(fields)
library(doBy)
library(rgeos)
Si certains packages ne sont pas installés, utiliser la commande:
Fenêtre RStudio
Nous allons également définir le dossier dans lequel se trouvent les données, en utilisant la commande:
Fenêtre RStudio
proj_RGF93=CRS("+init=epsg:2154") # projection du projet en RGF 93
# lecture du fichier shape délimitant l'étendue du projet
vETENDUE_BO=readOGR("datas","vETENDUE_BO RGF93") # etendue du bassin versant du Bourdic
## OGR data source with driver: ESRI Shapefile
## Source: "datas", layer: "vETENDUE_BO RGF93"
## with 1 features
## It has 2 fields
Objectif: Charger un fichier raster de type MNT
# lecture du MNT (modele numerique de terrain)
rMNT_PE=raster("datas/rMNT_PE RGF93.tif")
projection(rMNT_PE)=proj_RGF93 # attribution de la projection RGF 93 au MNT
names(rMNT_PE)="MNT" # Nommage du raster
Objectif: Découper un MNT selon une étendue donnée
# découpage de la zone à l'aide de la fonction crop
rMNT_BO=crop(rMNT_PE,vETENDUE_BO)
Objectif: Changer la résolution d’un raster
# Utilisation de la fonction aggregate
rMNT_BO<-aggregate(rMNT_BO,fact=5)
Objectif: Calculer des variables dérivées du MNT et compilation dans un même raster.
rSLOPE_BO=terrain(rMNT_BO,opt="slope") ; names(rSLOPE_BO)="Slope"
rASP_BO=terrain(rMNT_BO,opt="aspect") ; names(rASP_BO)="Aspect"
rHILL_BO=hillShade(terrain(rMNT_BO,opt="slope"),terrain(rMNT_BO,opt="aspect"))
names(rHILL_BO)="Hillshade"
rDERIVMNT_BO=stack(rSLOPE_BO,rASP_BO,rHILL_BO)
plot(rDERIVMNT_BO)
La transparence avec la fonction alpha permet de superposer des cartes, et on peut également ajouter des barres de couleur à divers endroits du graphique:
par(mfcol=c(1,1))
plot(rMNT_BO,col=bpy.colors(),main="Altitude avec ombrage en transparence",axes=F)
plot(rHILL_BO,col=alpha(grey(1:100/100),0.5),add=T,legend=F)
image.plot(legend.only=TRUE,zlim=range(na.omit(rHILL_BO[])),col=grey(1:100/100),horizontal=T)
Objectif: Charger des données vecteur de type Polygone, Ligne et Point et une table de correspondance associée.
# Chargement de la carte d'occupation du sol (emprise Bourdic)
vOCSOL_BO_2012=readOGR("datas","vOCSOL_BO_2012 RGF93")
vOCSOL_BO_1962=readOGR("datas","vOCSOL_BO_1962 RGF93")
# corrections des erreurs de topologie
vOCSOL_BO_2012=gBuffer(vOCSOL_BO_2012,width=0,byid=TRUE)
vOCSOL_BO_1962=gBuffer(vOCSOL_BO_1962,width=0,byid=TRUE)
# Decoupe des parcellaires selon l'étendue du bassin versant
vOCSOL_BO_2012=crop(vOCSOL_BO_2012,vETENDUE_BO)
vOCSOL_BO_1962=crop(vOCSOL_BO_1962,vETENDUE_BO)
# Chargement des données de pédologie (emprise Peyne)
vPEDO_PE=readOGR("datas","vPEDO_PE RGF93")
Ici le tableau de correspondance entre les occupations du sol et leurs identifiants:
Correspondance=
data.frame(ID=c(1220,3100,3220,2211,2212,2100,2210,1110,2220,1100),
Cover=c("Réseaux routiers et chemins (tous)","Bois",
"Landes et broussailles","Vignoble en gobelet","Vignoble palissé",
"Autres terres arables","Vignoble en transition",
"Tissu urbain continu","Arboriculture","Zones urbanisées"),
CoverEng=c("Road and rail networks","Forests","Sclerophyllous vegetation",
"Goblet vineyard","Aligned vineyard","Other arable lands",
"Transitional vineyard","Continuous urban area","Orchards",
"Discontinuous urban area"),
Color=c("#8B0000","#4DFF00","#A6FF80","#B414F1","#FFAAFF","#E0EC05",
"#FF00FF","#00004D","#DAA520","#CD0000"))
Correspondance=unique(orderBy(~ID,Correspondance)) # Tri de la table par ordre croissant de ID
On peut afficher le tableau de correspondance comme une image:
plot.new()
grid.table(Correspondance)
Il convient au départ pour des variables factorielles, ou qualitatives, de créer un vecteur de couleur correspondant aux attributs à représenter:
fOCSOL_BO_2012=factor(vOCSOL_BO_2012$Classvalue,
levels=Correspondance$ID,
labels=Correspondance$Color)
fOCSOL_BO_2012=as.character(fOCSOL_BO_2012)
par(mfrow=c(1,2)) # Division de la fenêtre graphique
plot(vETENDUE_BO,border="transparent")
plot(vOCSOL_BO_2012,col=alpha(fOCSOL_BO_2012,0.7),add=T,lwd=0.1) # plot de l'occupation du sol
plot(vETENDUE_BO,add=T,lwd=3,lty=3,border="red") # plot de l'étendue du projet
plot.new()
legend("center",legend=Correspondance$Cover,bty="n",fill=as.character(Correspondance$Color))
L’analyse des densités des parcelles consiste d’abord a établir la carte de chaleur des positions des centres de chaque parcelle. Une carte de chaleur correspond à un raster avec des valeurs plus élevées lorsque les points sont proches les uns des autres.
# Recuperation des centres des parcelles
vCENTRES_BO=as.ppp(coordinates(vOCSOL_BO_2012),vETENDUE_BO)
# definition de la grille de base pour les densites
spatstat.options(npixel=c(ncol(rMNT_BO),nrow(rMNT_BO)))
# Calcul de la carte de chaleur
rCENTRES_BO=raster(density(vCENTRES_BO))
names(rCENTRES_BO)="Centres"
Ensuite on représente cette carte de chaleur:
par(mfcol=c(1,2))
plot(vCENTRES_BO,main="",pch=19)
plot(rCENTRES_BO,col=bpy.colors(),main="",legend=F,axes=F,box=F)
Enfin, on calcule l’importance de chaque caractéristique géo-morphologique sur la densité des parcelles, à l’aide d’un modèle regressif multiple:
# Realignement des raster pour correspondance parfaite
rCENTRES_BO=resample(rCENTRES_BO,rDERIVMNT_BO)
# Creation du tableau complet
datas=as.data.frame(stack(rCENTRES_BO,rMNT_BO,rDERIVMNT_BO))
# Suppression des valeurs manquantes
datas=na.omit(datas)
# Creation du modèle
model=lm(Centres~.,datas)
summary(model)
Call:
lm(formula = Centres ~ ., data = datas)
Residuals:
Min 1Q Median 3Q Max
-6.046e-05 -1.662e-05 -2.392e-06 9.700e-06 1.092e-04
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 9.868e-05 6.095e-06 16.19 <2e-16 ***
MNT -4.396e-07 1.225e-08 -35.88 <2e-16 ***
Slope 1.669e-04 4.961e-06 33.64 <2e-16 ***
Aspect 1.370e-06 1.281e-07 10.70 <2e-16 ***
Hillshade 8.882e-05 8.202e-06 10.83 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.354e-05 on 12003 degrees of freedom
Multiple R-squared: 0.1907, Adjusted R-squared: 0.1905
F-statistic: 707.3 on 4 and 12003 DF, p-value: < 2.2e-16
L’analyse de l’occupation du sol consiste à comprendre comment sont réparties les cultures dans l’espace. Pour y parvenir, la méthode la plus simple consiste à rasteriser les fichiers vecteur des occupations du sol, i.e. caler une grille sur la zone et attribuer pour chaque case de la grille la valeur d’occupation du sol qui lui correspond.
rOCSOL_BO_2012=rasterize(vOCSOL_BO_2012,rMNT_BO,field="Classvalue",progress="text")
projection(rOCSOL_BO_2012)=proj_RGF93
par(mfrow=c(1,3)) # Division de la fenêtre graphique
plot(vETENDUE_BO,border="transparent",main="Vecteur")
plot(vOCSOL_BO_2012,col=alpha(fOCSOL_BO_2012,0.7),add=T,lwd=0.1) # plot de l'occupation du sol
plot(vETENDUE_BO,add=T,lwd=3,lty=3,border="red") # plot de l'étendue du projet
plot(rOCSOL_BO_2012,col=as.character(Correspondance$Color),axes=F,main="Raster",legend=F)
plot(vETENDUE_BO,add=T,lwd=3,lty=3,border="red") # plot de l'étendue du projet
plot.new()
legend("center",legend=Correspondance$Cover,bty="n",fill=as.character(Correspondance$Color))
Enfin, on calcule l’importance de chaque caractéristique géo-morphologique sur l’occupation du sol, à l’aide d’un modèle de type randomForest.
datasRF=na.omit(as.data.frame(stack(rOCSOL_BO_2012,rMNT_BO,rDERIVMNT_BO)))
datasRF[,1]=factor(datasRF[,1])
On calcule le modèle randomForest:
modelRF=randomForest(layer~.,data=datasRF,importance=T)
modelRF
Call:
randomForest(formula = layer ~ ., data = datasRF, importance = T)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 2
OOB estimate of error rate: 33.41%
Confusion matrix:
1100 1110 1220 2100 2210 2211 2212 2220 3100 3220 class.error
1100 40 9 0 18 0 0 46 3 3 7 0.6825397
1110 11 745 0 93 6 0 295 5 43 33 0.3948010
1220 4 11 0 22 1 0 157 4 15 8 1.0000000
2100 10 88 4 782 1 0 1011 16 51 47 0.6109453
2210 0 14 0 1 51 0 48 0 1 0 0.5565217
2211 0 0 0 1 0 0 1 0 0 0 1.0000000
2212 16 189 12 402 21 0 5774 29 111 59 0.1268713
2220 1 13 3 21 0 0 104 33 17 13 0.8390244
3100 4 25 5 48 0 0 297 9 328 32 0.5614973
3220 7 64 0 77 0 0 215 8 61 121 0.7811935
On peut ainsi déterminer l’importance de chacune des variables dans l’explication des occupations du sol:
par(mfcol=c(1,1),mar=rep(4,4))
varImpPlot(modelRF,type=2,main="",pch=19)
On rasterise la deuxième carte datant de 1962:
rOCSOL_BO_1962=rasterize(vOCSOL_BO_1962,rMNT_BO,field="Classvalue",progress="text")
projection(rOCSOL_BO_1962)=proj_RGF93
On peut ainsi évaluer la matrice de transition d’une culture à une autre entre 1962 et 2009:
dTRANS_BO=crosstab(stack(rOCSOL_BO_1962,rOCSOL_BO_2012))
dTRANS_BO=xtabs(Freq~Var1+Var2,dTRANS_BO)
plot.new()
grid.table(dTRANS_BO)
vPLOT_BO=runifpoint(100,win=vETENDUE_BO)
par(mfcol=c(1,2))
plot(vETENDUE_BO,main="")
plot(vPLOT_BO,pch=19,main="")
vTESS_BO=dirichlet(vPLOT_BO)
par(mfcol=c(1,2))
plot(vPLOT_BO,pch=19,main="")
plot(vTESS_BO,main="")
plot(vPLOT_BO,pch=19,add=T,col="darkgrey")
Il s’agit tout d’abord de créer la table permettant d’ajouter les occupations du sol:
vOCSOL_BO_sim=data.frame(ID=1:length(tiles(vTESS_BO)),
Annee1=NA,Annee2=NA,Annee3=NA,Annee4=NA,Annee5=NA)
Enfin on attribue de manière aléatoire les occupations du sol dans la table:
vOCSOL_BO_sim[,"Annee1"]=sample(Correspondance$ID,size=dim(vOCSOL_BO_sim)[1],replace=T)
vOCSOL_BO_sim[,"Annee2"]=sample(Correspondance$ID,size=dim(vOCSOL_BO_sim)[1],replace=T)
vOCSOL_BO_sim[,"Annee3"]=sample(Correspondance$ID,size=dim(vOCSOL_BO_sim)[1],replace=T)
vOCSOL_BO_sim[,"Annee4"]=sample(Correspondance$ID,size=dim(vOCSOL_BO_sim)[1],replace=T)
vOCSOL_BO_sim[,"Annee5"]=sample(Correspondance$ID,size=dim(vOCSOL_BO_sim)[1],replace=T)
par(mfrow=c(1,5)) # Division de la fenêtre graphique
for(i in paste("Annee",1:5,sep=""))
{
fOCSOL_BO=factor(vOCSOL_BO_sim[,i],
levels=Correspondance$ID,
labels=Correspondance$Color)
fOCSOL_BO=as.character(fOCSOL_BO)
plot(vETENDUE_BO,border="transparent",main=i)
for(j in 1:length(tiles(vTESS_BO))) plot(as.owin(vTESS_BO[j]),col=fOCSOL_BO[j],add=T)
}
En vous aidant de l’aide de la librairie spatstat:
Nous allons faire l’hypothèse que la densité des parcelles est dépendante de l’altitude. Plus l’altitude est élevée, et plus les parcelles sont concentrées. Il s’agit donc de réaliser un semis de point dépendant de l’altitude de la zone.
Cela nécessitera de créer une fonction permettant de transformer un objet raster en objet image:
as.rasterToim<-function(datas=rMNT_BO)
{
tmp<-as.matrix(datas)
win<-as.owin(c(xmin(extent(datas)),xmax(extent(datas)),ymin(extent(datas)),ymax(extent(datas))))
tmp[dim(tmp)[1]:1,]<-tmp
as.im(tmp,win)
}
Il faut également créer une autre fonction de normalisation des données:
Rescale=function(x){
tmp<-(x-minValue(x))
tmp<-tmp/maxValue(tmp)
tmp
}
Il faut tout d’abord transformer le mnt en une image dont les valeurs sont comprises entre 0 et 0.0002:
rMNT_BO_norm=Rescale(rMNT_BO)*0.0002
rMNT_BO_norm=as.rasterToim(rMNT_BO_norm)
Ensuite on réalise un semis selon un loi de poisson contrainte par les valeurs du mnt:
vPLOT_BO_mnt=rpoispp(lambda=rMNT_BO_norm)
On réalise la tesselation de ce semis de points:
vTESS_BO_mnt=dirichlet(vPLOT_BO_mnt)
Enfin, le graphique du résultat
par(mfcol=c(1,3),mar=rep(1,4))
plot(rMNT_BO_norm,ribbon=F,main="")
plot(rMNT_BO_norm,ribbon=F,col=grey(100:1/100),main="")
plot(vPLOT_BO_mnt,pch=19,col="blue",add=T)
plot(vETENDUE_BO,add=T,border="red")
plot(rMNT_BO_norm,ribbon=F,col=grey(100:1/100))
plot(vPLOT_BO_mnt,pch=19,col="blue",add=T)
plot(vTESS_BO_mnt,add=T,col="darkblue")
En vous aidant de l’aide de la librairie raster et en trouvant la (ou les) fonctions adéquates:
?raster
Nous allons nous baser cette fois-ci sur un parcellaire conditionné par une distance minimale entre chaque parcelle:
vPLOT_BO_distmin=rSSI(r=200,win=vETENDUE_BO)
vTESS_BO_distmin=dirichlet(vPLOT_BO_distmin)
A présent, nous allons considérer que la probabilité d’avoir une culture donnée dépend des caractéristiques géo-morphologiques de la zone dans laquelle elle se trouve. Les caractéristiques géo-morphologiques ont été calculées dans la partie Analyse des parcellaires
Ensuite on définit des règles de décision pour l’allocation de chaque culture en fonction des critères géomorphologiques. On peut se baser sur le modèle randomForest qui a été calculé dans la partie Analyse des parcellaires
On réalise la même manipulation pour le parcellaire simulé:
dataSIM=extract(stack(rMNT_BO,rDERIVMNT_BO),data.frame(vPLOT_BO_distmin))
vec=predict(modelRF,dataSIM,type="response")
vec=as.integer(as.character(vec))
par(mfcol=c(1,3))
plot(vPLOT_BO_distmin,main="Position des parcelles",pch=19)
plot(vTESS_BO_distmin,main="Forme des parcelles")
plot(vETENDUE_BO,main="Occupation des parcelles") ; cpt=0
for(i in vec) {cpt=cpt+1
plot(as.owin(vTESS_BO_distmin[cpt]),
col=as.character(Correspondance[Correspondance$ID==i,]$Color),
add=T)}
Appliquer le modèle randomForest sur le fichier raster, et non plus le fichier vecteur et comparer les résultats. Vous pouvez vous aider en tapant ?raster::predict Refaites ensuite l’application du modèle pour deux résolutions différentes: 5 m et 50 m.
On se base sur la matrice de transition calculée dans la partie Analyse des parcellaires:
vOCSOL_BO_sim_distmin=data.frame(ID=1:length(tiles(vTESS_BO_distmin)),
Annee1962=NA,Annee2009=NA,Annee2056=NA)
vOCSOL_BO_sim_distmin[,"Annee1962"]=extract(rOCSOL_BO_1962,data.frame(vPLOT_BO_distmin))
for(year in 3:4)
for(i in 1:dim(vOCSOL_BO_sim_distmin)[1])
{index=as.character(vOCSOL_BO_sim_distmin[i,year-1])
if(!is.na(index)) vOCSOL_BO_sim_distmin[i,year]=sample(x=Correspondance$ID,
size=1,
prob=as.vector(dTRANS_BO[index,]))
}
On visualise ensuite la dynamique des parcellaires:
par(mfcol=c(1,3))
for(year in 2:4)
{
plot(vETENDUE_BO,main=paste("Année",(year-2)*47+1962)) ; cpt=0
vec=vOCSOL_BO_sim_distmin[,year]
for(i in vec) {cpt=cpt+1 ; plot(as.owin(vTESS_BO_distmin[cpt]),
col=as.character(Correspondance[Correspondance$ID==i,]$Color),
add=T)}
}
Plutôt que considérer les probabilités de passage parcelle par parcelle, on peut les appliquer directement à toutes les cellules des cartes raster. La fonction ?getValues permet d’accéder à toutes les valeurs du raster