8.1 Leçon

Au module 5, vous avez appris les fonctions essentielles pour lire et visualiser des données spatiales matricielles sous R. Le présent module vous amènera maintenant à manipuler conjointement des données matricielles et vectorielles.

Dans un premier temps, cette leçon vous enseignera le fonctionnement d’opérations de base sur les données matricielles.

Dans un second temps, cette leçon vous guidera dans la résolution d’une problématique qui nécessite de manipuler des données matricielles. Au cours des différentes étapes permettant de résoudre la problématique, vous mettrez en pratique les diverses fonctions R apprises jusqu’à maintenant. Plus précisément, nous allons pousser plus loin la problématique étudiée au module 7 en répondant aux deux questions suivantes:

Parmi les quatre parcs nationaux de la région de Sherbrooke, lequel dispose du plus haut sommet?

Quel est le profil topographique du sentier se rendant au plus proche de ce sommet?


8.1.1 Télécharger les données

Les données


Dans cette leçon, nous allons utiliser le modèle d’élévation numérique aussi appelé Digital Elevation Model (DEM). Cette couche d’information spatiale est produite par le gouvernement du Canada (accessible sur ce portail). Cette couche est une matrice de données (raster) contenant des valeurs d’élévation en mètres.

Nous allons également nous servir de la base de données vectorielles des sentiers de randonnées de la SÉPAQ (source), ainsi que celle des parcs nationaux de la région de Sherbrooke identifiés au module 7.

Afin de faciliter le téléchargement de ces données, l’ensemble de ces couches d’informations spatiales peuvent être téléchargée en cliquant sur un seul lien: données pour le module 8. Une fois téléchargé, le dossier compressé (zip) doit être dézippé dans votre répertoire de travail. Le dossier Module8_donnees comprend deux sous-dossiers et un fichier:

  • parcs_sherbrooke
  • DEM.tif
  • sentiers_sepaq


8.1.2 Opération de bases

Dans cette section, nous allons nous familiariser avec les opérations fréquemment utilisées sur les données matricielles.

8.1.2.1 Importer et visualiser les données

Dans allons d’abord importer les différentes couches d’informations spatiales dans l’environnement R. Nous commençons par charger les bibliothèques requises pour importer les données spatiales vectorielles (sf), pour importer les données spatiales matricielles (raster) et pour visualiser ces données (mapview).

library(sf)
library(raster)
library(mapview)


Ensuite, nous utilisons la fonction st_read() pour importer le shapefile parcs_sherbrooke qui contient les quatre parcs se situant dans un rayon de 70 km de la municipalité de Sherbrooke (voir la problématique du module 7).

parcs <- st_read("Module8/Module8_donnees/parcs_sherbrooke/parcs_sherbrooke.shp")
Reading layer `parcs_sherbrooke' from data source 
  `D:\Dropbox\Teluq\Enseignement\Cours\SCI1031\Developpement\Structure_test\sci1031\Module8\Module8_donnees\parcs_sherbrooke\parcs_sherbrooke.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 4 features and 18 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -323500 ymin: 151800 xmax: -201900 ymax: 226200
Projected CRS: NAD83 / Quebec Lambert


Enfin, nous importons la couche d’élévation DEM pour la région d’intérêt en utilisant la fonction raster().

dem <- raster("Module8/Module8_donnees/DEM.tif")


Nous visualisons ensuite les deux objets spatiaux afin de valider l’importation en utilisant la fonction mapview().

mapview(dem) + mapview(parcs, zcol = "TRQ_NM_")


Notez que l’attribut TRQ_NM_ correspond aux noms des parcs nationaux.

8.1.2.2 Filtrer les cellules d’un raster

Une des opérations les plus fréquentes sur les raster est celle de filtrer les cellules (ou pixels). Ceci peut être fait dans le but de sélectionner des cellules possédant une valeur d’attribut particulière. Par exemple, à partir d’une couche matricielle des différentes classes d’utilisation du sol pour une région donnée, nous pourrions vouloir sélectionner les pixels de la classe “Eaux” ou encore ceux de la classe “Terres humides”.

Filtrer les cellules peut également être fait dans le but de sélectionner des cellules sur la base de leur localisation spatiale. Par exemple, nous pourrions vouloir choisir les cellules d’un raster qui sont à l’intérieur des limites administratives d’une municipalité.

Dans les sous-sections qui suivent, nous présenterons des fonctions qui permettent de réaliser ces deux types d’opération de filtrage de données matricielles.

Filtrer en utilisant la valeur des cellules
Fonction which()

Au module 5, nous avons vu comment accéder et manipuler les données de raster en utilisant, entre autres, les fonctions summary() et getValues(). Il est ainsi possible d’avoir une idée de la distribution des valeurs du raster dem en utilisant la ligne de commande suivante :

summary(getValues(dem))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     30     211     303     314     410    1174 
   NA's 
   3839 


Nous pouvons apercevoir que certaines cellules du raster dem contiennent des valeurs négatives. Notez que ces valeurs ne sont pas aberrantes et signifient simplement que ces pixels se retrouvent en dessous du niveau de la mer. Pour la suite de la leçon (et à titre d’exemple), nous allons exclure ces valeurs négatives. En d’autres termes, nous allons appliquer un filtre sur les cellules du raster, filtre qui ne laissera passer que les valeurs positives.

Plus précisément, nous allons utiliser la fonction which() pour filtrer les données du raster dem. Rappelons que la fonction which() identifie la position des éléments de valeur TRUE dans un vecteur logique (voir le module 7 pour un rappel). Utilisons donc cette fonction pour trouver les cellules qui ont des valeurs d’élévation négatives.

ind <- which(getValues(dem) < 0)
ind
integer(0)


La ligne de commande ci-dessus retourne les indices des cellules qui satisfont la condition demandée (c’est-à-dire avoir une valeur négative).

Pour savoir combien de cellules possèdent une valeur d’élévation négative, nous pouvons tout simplement compter le nombre d’éléments contenus dans le vecteur retourné par la fonction which(). Si les cellules identifiées étaient nombreuses ou si nous avions besoin de conserver ce nombre en mémoire pour l’utiliser dans la suite de notre analyse, nous pourrions obtenir ce compte en utilisant la fonction générale length() qui retourne la taille d’un vecteur:

nombre <- length(ind)
nombre
[1] 0


Notons qu’il est aussi possible d’accéder aux valeurs de ces cellules en utilisant les indices ind identifiés:

getValues(dem)[ind]
numeric(0)


Nous observons que les valeurs sont belles et bien négatives!

Pour filtrer ces cellules aux valeurs négatives, c’est-à-dire exclure ces valeurs de notre analyse, nous allons remplacer leur valeur par le terme NA. Rappelons que NA signifie non applicable.

dem[ind] <- NA


Voyons maintenant comment cette modification aux valeurs de certaines cellules altère les statistiques générales sur les valeurs du raster dem :

summary(getValues(dem))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     30     211     303     314     410    1174 
   NA's 
   3839 


L’élévation minimale est maintenant de 2 et le nombre de NA a augmenté de 0. Ceci confirme que notre filtre a bien été appliqué.

La fonction which() peut aussi être utilisée pour filtrer des données selon des expressions logiques plus complexes. Nous pouvons, par exemple, remplacer getValues(dem) < 0 par getValues(dem) < 0 | getValues(dem) > 1000 pour exclure également les cellules avec des valeurs plus grandes que 1000 m. De plus, il est aussi possible de changer les valeurs des cellules identifiées en d’autres valeurs que NA.


Fonction reclassify

La fonction reclassify() de la bibliothèque raster permet également de filtrer des données matricielles et de leur assigner de nouvelles valeurs.

Démontrons son utilisation par un cas simple. Nous allons catégoriser le niveau d’élévation, c’est-à-dire les valeurs du raster dem, en trois catégories:

  • catégorie 1: classe d’élévation faible allant de 0 à 250 m,
  • catégorie 2: classe d’élévation modérée allant de 251 à 500 m,
  • catégorie 3: classe d’élévation forte allant de 501 à 1200 m.

Avant de pouvoir utiliser la fonction reclassify(), il est nécessaire de construire une matrice indiquant les bornes limites des différentes classes.

nouvelles_classes <- matrix(c(0, 250, 1, 250, 500, 2, 500, 1200, 3), nrow = 3, ncol = 3, byrow = TRUE)
colnames(nouvelles_classes) <- c("Limite_min", "Limite_max", "nouvelles_classes")
nouvelles_classes
     Limite_min Limite_max nouvelles_classes
[1,]          0        250                 1
[2,]        250        500                 2
[3,]        500       1200                 3


Cette matrice est utilisée comme argument de la fonction reclassify() afin d’assigner les nouvelles classes aux valeurs du raster dem.

nouvelles_classes_dem <- reclassify(dem, nouvelles_classes, rigth = FALSE)


Notez que l’utilisation par défaut de la fonction reclassify() inclue la borne supérieure mais pas la borne inférieure de l’intervalle de reclassification (]limite_lim, limite_max]). L’ajout de l’argument rigth = FALSE vient spécifier que nous souhaitons le contraire, c’est-à-dire l’inclusion de la borne inférieure mais pas de la borne supérieure ([limite_lim, limite_max[). Ainsi, nous avons précisé que les valeurs incluses dans la nouvelle classe 1 vont de 0 à 249 m plutôt que de 1 à 250 m.

Visualisons la nouvelle classification du domaine de valeurs du raster d’élévation dem à l’aide de la fonction mapview():

mapview(nouvelles_classes_dem)


Notez que nous pouvons aussi utiliser NA pour exclure certaines cellules:

nouvelles_classes2 <- matrix(c(0, 250, NA, 250, 500, 2, 500, 1200, 3), nrow = 3, ncol = 3, byrow = TRUE)
mapview(reclassify(dem, nouvelles_classes2))


Filtrer en utilisant les coordonnées spatiales des cellules

Une des opérations les plus fréquentes sur les raster est de filtrer les cellules en fonction de leurs coordonnées spatiales. Dans cette section, nous allons apprendre à utiliser les fonctions crop() et mask() pour réaliser ces manipulations.

Fonction crop()

La fonction crop() vous permet de rogner un raster, autrement dit d’utiliser un rectangle pour filtrer les cellules selon qu’elles soient ou non à l’intérieur de ce dernier.

Pour utiliser la fonction crop(), nous avons besoin de deux objets. Le premier objet est le raster à rogner et le second objet est le rectangle avec lequel le raster sera rogné. Ce rectangle est un objet de classe Extent (étendue en français). Un tel objet peut être créé en utilisant les coordonnées de ses coins inférieur-gauche (xmin, ymin) et supérieur-droit (xmax, ymax):

ext <- extent(c(-72, -71.5, 45.2, 45.8))
ext 
class      : Extent 
xmin       : -72 
xmax       : -71.5 
ymin       : 45.2 
ymax       : 45.8 


Avec la fonction plot(), nous pouvons visualiser la partie du raster qui sera rognée. Notez que la fonction mapview() ne peut pas être utilisée pour visualiser des objets de classe Extent.

plot(dem)
plot(ext, add = TRUE) 


Il est très important de relever que de manière implicite les valeurs des coordonnées utilisées dans extent() sont exprimées dans le SCR de notre raster dem.

Utilisons maintenant la fonction crop() dont le premier argument est le raster à rogner et le second argument est l’objet de classe Extent.

dem_cr <- crop(dem, ext)


La sortie est un raster (que nous appelons dem_cr) qui est recadré selon ext. Les cellules qui ne sont pas dans ce rectangle sont exclues.

mapview(dem_cr)


La fonction crop() accepte en second argument, non seulement les objets de classe Extent, mais aussi tout objet à partir duquel un objet de classe Extent peut être extrait. Ceci signifie que le second argument peut aussi comprendre les objets spatiaux de classe RasterLayer et de classe sf.

En guise d’exemple, nous allons maintenant rogner le raster dem en utilisant les limites du premier parc de la SÉPAQ identifié dans les données vectorielles parcs. Il est en effet possible d’extraire un objet de classe Extent à partir de ces données. Vérifions-le en utilisant la fonction extent():

extent(parcs[1,])
class      : Extent 
xmin       : -215252 
xmax       : -201928 
ymin       : 195972 
ymax       : 226160 


Avant d’utiliser la fonction crop() nous avons besoin de re-projeter le raster dem dans le SCR des données vectorielles parcs (notez que nous pourrions aussi faire l’inverse). L’opération qui suit utilise de façon implicite l’étendue spatiale de parcs[1, ] pour rogner dem_lcc.

dem_lcc <- projectRaster(dem, crs = crs(parcs))
dem_cr_p <- crop(dem_lcc, parcs[1, ])
mapview(dem_cr_p)


Fonction mask()

La fonction mask() permet de découper un raster avec un polygone de n’importe quelle forme et non uniquement selon un rectangle. Pour illustrer cette fonction nous allons d’abord créer un polygone avec la fonction st_as_sf() de la bibliothèque sf (voir le module 4) :

# `mat` est une matrice 7x2 des coordonnées du polygone.
mat <- matrix(c( -72.5, 45.8,
                   -72, 45.5,
                 -72.5, 45.2,
                 -71.5, 45.4,
                 -71.7, 45.6,
                 -71.5, 45.7,
                 -72.5, 45.8), ncol = 2, byrow = TRUE)

# nous transformons mat en un data frame puis en un objet de class `sf`
pol <- st_as_sf(
    data.frame(
      var = 1,
      geom = st_sfc(st_polygon(x = list(mat)))
      ),
      crs = st_crs(dem) 
  )


Notons que nous avons utilisé le même SCR que dem. Regardons ce à quoi ressemble le polygone que nous venons de créer.

mapview(dem)+mapview(pol)


La fonction mask() permet de sélectionner uniquement les cellules du raster dem qui sont à l’intérieur du polygone pol (passé en second argument):

dem_ma <- mask(dem, pol)
mapview(dem_ma)


La fonction mask() est dotée d’un argument inverse qui, s’il prend la valeur TRUE, permet de sélectionner toutes les cellules d’un raster qui sont à l’extérieur du polygone fourni:

dem_ma_inv <- mask(dem, pol, inverse = TRUE)
mapview(dem_ma_inv)


La fonction mask() permet ainsi de filtrer les données matricielles de façon plus complexe qu’avec la fonction which(). En effet, les valeurs du raster dem_ma correspondent aux valeurs du sous-ensemble des cellules de dem qui sont à l’intérieur du polygone pol de forme complexe. Nous avons donc filtré spatialement les données de dem et nous avons assigné ce raster à la variable dem_ma que nous pouvons alors utiliser comme tout autre raster.

summary(getValues(dem_ma))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    100     221     259     270     304     839 
   NA's 
  38445 


Terminons cette section sur les opérations de filtre par trois remarques importantes:

  1. L’étendue du raster retourné par la fonction crop() sera différente de celle du raster initial (sauf si l’étendue initiale est utilisée pour rogner). À l’inverse, la fonction mask() préserve l’étendue spatiale.

Vous pouvez en faire la vérification:

extent(dem)
class      : Extent 
xmin       : -72.72 
xmax       : -70.85 
ymin       : 45 
ymax       : 46.06 
extent(dem_cr)
class      : Extent 
xmin       : -72 
xmax       : -71.5 
ymin       : 45.2 
ymax       : 45.8 
extent(dem_ma)
class      : Extent 
xmin       : -72.72 
xmax       : -70.85 
ymin       : 45 
ymax       : 46.06 


  1. Le temps de calcul pour réaliser l’opération crop() est souvent très rapide, ce qui n’est pas le cas de l’opération mask() quand le polygone est complexe. Il est parfois possible d’avoir des gains d’efficacité en faisant appel à crop() avant d’utiliser mask().


  1. Dans la situation où seules les valeurs des cellules filtrées nous intéresse, il est possible d’utiliser la fonction extract() plutôt que la fonction mask(). La fonction extract() de la bibliothèque raster retourne une liste pour laquelle chaque élément donne les valeurs des cellules extraites pour chaque couche du raster.

Par exemple, le raster dem possède une seule couche, ainsi la liste retournée par la fonction extract() ne possède qu’un seul élément. Toutefois, cet élément est un vecteur de taille 6075 listant la valeur de chacune des cellules extraites.

[1] 6075
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    100     221     259     270     304     839 
dem_ex <- extract(dem, pol)

length(dem_ex[[1]])

summary(dem_ex[[1]])


8.1.2.3 Combiner des rasters

Une opération qui peut s’avérer utile est celle de combiner des rasters, c’est-à-dire former un seul raster à partir de deux rasters ou plus. Par exemple, une telle opération pourrait être nécessaire si, pour une problématique donnée, nous devions combiner des rasters d’élévation de chaque région administrative du Québec pour obtenir un seul raster couvrant l’entièreté de la province.


Fonction merge()

La fonction merge() de la bibliothèque raster permet de combiner deux rasters ou plus. Cette fonction s’utilise différemment de la fonction générale merge() vue au module 7 pour combiner des data frame.

En guise d’exemple, combinons les rasters dem_ma et dem_ma_inv créés plus haut en applicant la fonction mask() au raster dem :

dem_merge <- merge(dem_ma, dem_ma_inv)




Notez que pour combiner des rasters avec la fonction merge() ceux-ci doivent avoir la même résolution et posséder le même système de coordonnées de référence (SCR).


Il est possible que deux rasters que l’on souhaite combiner couvrent certaines régions communes. C’est-à-dire que certains pixels se superposent. Si la valeur des pixels se superposant diffère, la fonction merge() choisira la valeur que prend le pixel dans le raster donné en premier argument.

Par exemple, créons un raster qui soit identique à dem_cr mais pour lequel tous les pixels ont une altitude 100 m plus grande :

#Ajoutons 100m à tous les pixels de dem_cr
dem_cr_plus <- dem_cr + 100


Ensuite, combinons dem_cr_plus à dem_ma avec la fonction merge(). Le raster combiné prendra des valeurs différentes sur les pixels qui se superposent selon l’ordre des arguments :

dem_merge_ordre1 <- merge(dem_cr_plus, dem_ma)
dem_merge_ordre2 <- merge(dem_ma, dem_cr_plus)



Fonction mosaic()

La fonction mosaic() de la bibliothèque raster est similaire à merge() mais offre plus de flexibilité pour définir la valeur des pixels qui se chevauchent. En particulier, elle possède un troisième argument servant à préciser la fonction avec laquelle la valeur des pixels redondants est calculée.

Par exemple, la fonction pourrait choisir la valeur la plus grande, la valeur la plus petite, ou encore la moyenne des valeurs. La fonction pourrait aussi être définie par l’usager et prendre une forme plus spécifique.

dem_mosaic_min  <- mosaic(dem_cr_plus, dem_ma, fun = min)
dem_mosaic_max  <- mosaic(dem_cr_plus, dem_ma, fun = max)
dem_mosaic_mean <- mosaic(dem_cr_plus, dem_ma, fun = mean)



La fonction mosaic() requiert également que les rasters utilisés possèdent la même résolution et le même SCR.

Il existe des bibliothèques spécialisées qui offrent des fonctions plus avancées pour le traitement des pixels aux frontières des rasters qui se chevauchent (par exemple les bibliothèques landsat et satellite). L’utilisation de ces méthodes dépasse toutefois les objectifs de ce cours.


8.1.3 Problématique à résoudre

Maintenant que nous avons appris les opérations de base pour manipuler les données matricielles, nous sommes en mesure de résoudre la problématique énoncée au début de cette leçon.


Résolution de la question 1

Rappelons la première question :

Parmi les quatre parcs nationaux de la région de Sherbrooke, lequel dispose du plus haut sommet?


Les étapes de la démarche de résolution sont les suivantes:

  1. S’assurer que le raster d’élévation dem et les données vectorielles parcs sont dans le même SCR.
  2. Utiliser la fonction mask() pour extraire les cellules de dem qui sont dans les parcs nationaux considérés,
  3. Déterminer la valeur maximale d’élévation dans ce sous-ensemble de cellules extraites,
  4. Trouver les coordonnées spatiales associées à ce point de valeur d’élévation maximale.
  5. Déterminer dans quel parc se trouve les coordonnées spatiales du sommet le plus haut.


Commençons!

1. Vérication des SRC

Commençons par vérifier si les systèmes de coordonnées de référence (SCR) de dem et de parcs sont identiques:

st_crs(dem) == st_crs(parcs)
[1] FALSE


Puisque les SCR diffèrent, nous allons reprojeter le raster dem en utilisant le SCR de parcs en utilisant la fonction projectRaster(). Nous avons déjà réalisé cette opération à la section précédente et nous reprenons donc ci-dessous la ligne de commande vue plus haut :

dem_lcc <- projectRaster(dem, crs = crs(parcs))


Pour les prochaines manipulations, nous utiliserons donc dem_lcc et parcs.

2. Filtrer le raster dem

Nous cherchons ensuite à filtrer les cellules du raster d’élévation dem pour extraire celles situées à l’intérieur des limites de l’un ou de l’autre des quatre parcs nationaux considérés. Le shapefile parcs contient justement les polygones délimitant les quatre parcs. Nous pouvons donc appliquer la fonction mask() au raster dem en utilisant directement le shapefile parcs en second argument.

dem_parcs <- mask(dem_lcc, parcs)
mapview(dem_parcs)


Le raster dem_parcs obtenu par l’opération mask() possède la même étendue que le raster original dem_lcc mais ses valeurs diffèrent. Les cellules de dem_parcs prennent la valeur NA partout sauf à l’intérieur des quatre parcs nationaux où elles prennent alors la même valeur que la cellule correspondante dans dem_lcc.

3. Trouver l’élévation maximale

Nous pouvons maintenant trouver la valeur d’élévation la plus élevée en utilisant la fonction getValues() suivie de la fonction max().

vmax <- max(getValues(dem_parcs), na.rm = TRUE)
vmax
[1] 1083


Nous avons ainsi obtenu l’élévation maximale de ces parcs qui est de 1083 m. Nous cherchons alors les coordonnées spatiales associées à cette valeur. Nous pouvons faire appel à la fonction which() pour trouver l’indice de la cellule (ou les indices des cellules, s’il y en a plusieurs) en question.

ind_max <- which(getValues(dem_parcs) ==  max(getValues(dem_parcs), na.rm = TRUE))
ind_max 
[1] 31561


Notons qu’il existe une fonction pour identifier le maximum, which.max(), qui réalise la même opération mais requiert une syntaxe un peu plus simple.

which.max(getValues(dem_parcs))
[1] 31561


4. Déterminer les coordonnées du plus haut sommet

Pour déterminer les coordonnées de la cellule identifiée nous utilisons l’indice ind_max et la fonction as.data.frame() avec xy = TRUE et centroid = TRUE comme vu au module 5 :

df_max <- as.data.frame(dem_parcs[ind_max, drop = FALSE], xy = TRUE, centroid = TRUE, na.rm = TRUE)
df_max
        x      y layer
1 -207329 165880  1083


Deux remarques méritent d’être mentionnées concernant cette opération :

  1. L’argument drop = FALSE permet d’éviter que R ne convertisse dem_parcs[ind_max, ] en un vecteur, c’est important car nous voulons conserver sa forme matricielle.


  1. na.rm = TRUE permet d’ignorer toutes les valeurs masquées, comme nous l’avons dans d’autres fonctions (e.g. mean()).


v. Déterminer le parc possédant le point d’élévation maximale

Une fois isolées, les coordonnées x et y du pixel d’élévation maximale peuvent être utilisées pour créer un objet spatial de classe sf contenant le centroïde de ce pixel grâce à la fonction st_as_sf(). Nous nous servirons par la suite de ce point pour visualiser sa position et déterminer dans quel parc national il est situé.

sf_point_max <- st_as_sf(df_max, coords = c("x", "y"), 
  crs = st_crs(dem_parcs))
sf_point_max
Simple feature collection with 1 feature and 1 field
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -207300 ymin: 165900 xmax: -207300 ymax: 165900
Projected CRS: NAD83 / Quebec Lambert
  layer               geometry
1  1083 POINT (-207329 165880)


Remarquez que nous avons attribué à ce point le même SRC que celui du raster dem_parcs().

En superposant, avec mapview(), les trois couches d’informations spatiales que nous venons de manipuler, nous pouvons repérer le parc dans lequel se trouve ce point d’élévation maximale.

map_point <- mapview(dem_parcs) + mapview(parcs, alpha = 0.01) + mapview(sf_point_max,  col.regions = "red")
map_point@map

FIGURE 8.1: En cliquant sur le polygone du parc contenant le point rouge (point d’élévation maximal), il est possible de constater que le point se retrouve dans le Parc national du Mont-Mégantic


Nous constatons que ce point est situé dans le Parc national du Mont-Mégantic. Au lieu de déduire sa localisation de manière visuelle, nous allons à présent réaliser une opération topologique pour isoler le polygone du parc abritant ce plus haut sommet. Pour ce faire, nous utilisons la fonction st_intersects() étudiée au module 7. Rappelons que cette fonction retourne la valeur TRUE lorsque deux objets vectoriels se recoupent et FALSE autrement.

st_intersects(parcs, sf_point_max, sparse = FALSE)
      [,1]
[1,] FALSE
[2,]  TRUE
[3,] FALSE
[4,] FALSE


Nous utilisons ensuite ce vecteur logique pour identifier le parc auquel ce sommet appartient. Notez que le nom des parcs est donné par l’attribut TRQ_NM_.

parcs[st_intersects(parcs, sf_point_max, sparse = FALSE), ]$TRQ_NM_
[1] "Parc national du Mont-Mégantic"


Cette opération peut être écrite plus simplement sous la forme:

parcs[sf_point_max, ]$TRQ_NM_
[1] "Parc national du Mont-Mégantic"


Cette dernière manipulation nous permet de répondre à notre première question: parmi les quatre parcs nationaux sélectionnés, c’est le Parc national du Mont-Mégantic qui a le plus haut sommet, sommet qui culmine à 1083 m d’altitude!

Résolution de la question 2

Nous allons maintenant répondre à la deuxième question de notre problématique:

Quel est le profil topographique du sentier se rendant au plus proche de ce sommet?


Nous allons nous placer dans la situation suivante : nous désirons faire une randonnée pédestre jusqu’au sommet que nous venons d’identifier (à tout le moins, le plus proche possible de ce sommet). Afin de nous faire une idée précise de l’effort physique que nous devrons fournir lors de cette randonnée, nous allons réaliser un profil topographique du sentier se rendant au plus proche de ce sommet.

La figure 8.2 donne un exemple de profil topographique pour le sentier de la grande traversée dans le Parc national de la Gaspésie.

Exemple de profil topographique: le sentier international des Appalaches [taille réelle](Module8/images/8_Profil_ex.png)

FIGURE 8.2: Exemple de profil topographique: le sentier international des Appalaches taille réelle


Voici les étapes que nous allons suivre en vue de répondre à la question posée:

  1. Créer un shapefile du Parc national du Mont-Mégantic, parc_megantic, à partir de parcs.
  2. Importer les données des sentiers estivaux de la SEPAQ et des données d’élévation plus précises.
  3. S’assurer que les nouveaux objets importés et parc_megantic ont le même SCR.
  4. Isoler les sentiers estivaux du Parc national du Mont-Mégantic.
  5. Trouver le chemin le plus proche du sommet identifié à la question 1.
  6. Extraire les cellules d’élévation de dem_lcc sur ce sentier.
  7. Réaliser un profil d’élévation.


Commençons!

1. Créer un shapefile du Parc national du Mont-Mégantic

Puisque le profil topographique que nous désirons créer se situe à l’intérieur du Parc national du Mont-Mégantic, isolons le polygone constituant ce parc à partir du shapefile parcs regroupant les quatre parcs. Rappelons que le Parc national du Mont-Mégantic est celui qui possède le plus haut sommet. En nous basant sur les commandes réalisées plus haut, nous pouvons définir le shapefile parc_megantic de la façon suivante:

parc_megantic <- parcs[st_intersects(parcs, sf_point_max, sparse = FALSE), ]
# Ou plus simplement:
# parc_megantic <- parcs[sf_point_max, ]


2. Importer les données

Maintenant importons les données vectorielles pour tous les sentiers de la SÉPAQ. Ce shapefile se trouve dans le dossier Module8_donnees que vous avez téléchargé au début de la leçon :

sentiers <- st_read("Module8/Module8_donnees/sentiers_sepaq/Sentier_ete_l.shp")
Reading layer `Sentier_ete_l' from data source 
  `D:\Dropbox\Teluq\Enseignement\Cours\SCI1031\Developpement\Structure_test\sci1031\Module8\Module8_donnees\sentiers_sepaq\Sentier_ete_l.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 3606 features and 22 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -822400 ymin: 150300 xmax: 437600 ymax: 689800
Projected CRS: NAD83 / Quebec Lambert
mapview(sentiers)


Notre objectif est de savoir comment l’élévation varie le long du sentier qui conduit le plus près possible du plus haut sommet. Ainsi, en plus des données sur la localisation des sentiers, nous avons besoin des données d’élévation. Or, au lieu d’utiliser le raster dem_lcc dont la résolution spatiale est de 521 par 741 m2, nous allons nous procurer une autre couche d’élévation de résolution plus fine afin d’obtenir un profil topologique plus précis.

Pour ce faire, nous utiliserons la fonction get_elev_raster() de la bibliothèque elevatr. Cette fonction permet d’accéder aux données d’élévation disponibles sur le service d’infonuagique d’Amazon (AWS) et cela à différents niveaux de résolution.

La fonction get_elev_raster() requiert deux arguments. Le premier argument spécifie la localisation des données recherchées. Dans notre cas, la localisation est définie par le polygone parc_megantic. Le deuxième argument spécifie le niveau de résolution des données recherchées, c’est-à-dire le zoom, une valeur allant de z = 1 à z = 14.

# install.packages("elevatr")
# install.packages("progress") #Installer aussi!
library(elevatr)
elv_megantic <- get_elev_raster(parc_megantic, z = 13) 



Nous avons ainsi obtenu le raster d’élévation elv_megantic pour le Parc national du Mont-Mégantic. La résolution spatiale de cette couche est plus fine que celle de la couche dem_lcc :

res(elv_megantic)
[1] 3.353 3.353


Ce qui correspond à environ 3.4 par 3.4 m2.


3. Vérication des SRC

Vérifions ensuite que les SCR utilisés pour répondre à la question 2 sont tous identiques en utilisant la fonction st_crs() :

st_crs(sentiers) == st_crs(parc_megantic)
[1] TRUE
st_crs(elv_megantic) == st_crs(parc_megantic)
[1] TRUE


Puisque c’est le cas, aucune transformation de SRC n’est nécessaire et nous pouvons poursuivre la résolution de notre problème.


4. Isoler les sentiers estivaux du Parc national du Mont-Mégantic

À présent, nous allons isoler les sentiers du parc du Mont-Mégantic qui se trouvent dans sentiers. Commençons par inspecter la table des attributs :

head(sentiers)
Simple feature collection with 6 features and 22 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: 391200 ymin: 610200 xmax: 437600 ymax: 645500
Projected CRS: NAD83 / Quebec Lambert
  No_etab Code_etab No_reseau Code_res        Reseau
1      15       PAN         2       PQ Parc national
2      15       PAN         2       PQ Parc national
3      15       PAN         2       PQ Parc national
4      15       PAN         2       PQ Parc national
5      15       PAN         2       PQ Parc national
6      15       PAN         2       PQ Parc national
   Nom_etab     Maj Source   Usager     Etat
1 Anticosti 2011-02    n/a Pédestre Existant
2 Anticosti 2011-02    n/a Pédestre Existant
3 Anticosti 2011-02    n/a Pédestre Existant
4 Anticosti 2011-02    n/a Pédestre Existant
5 Anticosti 2011-02    n/a Pédestre Existant
6 Anticosti 2011-02    n/a Pédestre Existant
                 Toponyme1 Toponyme2 Toponyme3
1 Le Canyon-de-la-Chicotte      <NA>      <NA>
2      Lac-Baie-de-la-Tour      <NA>      <NA>
3          Les Télégraphes      <NA>      <NA>
4             Le Garde-Feu      <NA>      <NA>
5    La Grotte-à-la-Patate      <NA>      <NA>
6       Observation-la-Mer      <NA>      <NA>
  Toponyme4      Niv_diff Secteur Usager_2 Toponyme5
1      <NA> Intermédiaire    <NA>     <NA>      <NA>
2      <NA> Intermédiaire    <NA>     <NA>      <NA>
3      <NA> Intermédiaire    <NA>     <NA>      <NA>
4      <NA>        Facile    <NA>     <NA>      <NA>
5      <NA> Intermédiaire    <NA>     <NA>      <NA>
6      <NA>        Facile    <NA>     <NA>      <NA>
  Toponyme6 Usager_3 Usager_4 Shape_Leng
1      <NA>     <NA>     <NA>     6647.8
2      <NA>     <NA>     <NA>      601.4
3      <NA>     <NA>     <NA>     3051.5
4      <NA>     <NA>     <NA>     2499.2
5      <NA>     <NA>     <NA>     1548.7
6      <NA>     <NA>     <NA>     1973.8
                        geometry
1 MULTILINESTRING ((393116 61...
2 MULTILINESTRING ((435372 63...
3 MULTILINESTRING ((436291 63...
4 MULTILINESTRING ((396510 61...
5 MULTILINESTRING ((398004 64...
6 MULTILINESTRING ((409244 64...


La colonne Nom_etab contient le nom des parcs que nous pouvons lister rapidement avec la fonction unique() qui enlève les doublons.

unique(sentiers$Nom_etab)
 [1] "Anticosti"                          
 [2] "Duchesnay"                          
 [3] "Lac-Simon"                          
 [4] "Aiguebelle"                         
 [5] "Bic"                                
 [6] "Île-Bonaventure-et-Rocher-Percé"    
 [7] "Îles-de-Boucherville"               
 [8] "Frontenac"                          
 [9] "Gaspésie"                           
[10] "Grands-Jardins"                     
[11] "Hautes-Gorges-de-la-Rivière-Malbaie"
[12] "Jacques-Cartier"                    
[13] "Miguasha"                           
[14] "Mont-Mégantic"                      
[15] "Mont-Orford"                        
[16] "Mont-Tremblant"                     
[17] "Mont-Saint-Bruno"                   
[18] "Monts-Valin"                        
[19] "Oka"                                
[20] "Plaisance"                          
[21] "Pointe-Taillon"                     
[22] "Fjord-du-Saguenay"                  
[23] "Lac-Témiscouata"                    
[24] "Yamaska"                            
[25] "Saguenay-Saint-Laurent"             
[26] "Opémican"                           
[27] "Ashuapmushuan"                      
[28] "Chic-Chocs"                         
[29] "Laurentides"                        
[30] "La Vérendrye, Secteur Outaouais"    
[31] "La Vérendrye, Secteur Abitibi"      
[32] "Mastigouche"                        
[33] "Matane"                             
[34] "Auberge de Montagne des Chic-Chocs" 
[35] "Papineau-Labelle"                   
[36] "Port-Daniel"                        
[37] "Portneuf"                           
[38] "Rouge-Matawin"                      
[39] "Port-Cartier-Sept-Îles"             
[40] "Saint-Maurice"                      


Nous observons que le Parc national du Mont-Mégantic est identifié par le nom “Mont-Mégantic”. Pour isoler ses sentiers nous pouvons procéder de deux façons. La première façon consiste à utiliser la fonction subset() (vue au module 7) pour filtrer le shapefile sentiers et sélectionner seulement les sentiers pour lesquels l’attribut Nom_etab prend la valeur “Mont-Mégantic”:

sentiers_megantic <- subset(sentiers, Nom_etab == "Mont-Mégantic")


La deuxième façon est d’utiliser un filtre spatial avec les limites du Parc national du Mont-Mégantic contenues dans parc_megantic :

sentiers_megantic <- sentiers[parc_megantic, ]
mapview(sentiers_megantic)


Nous avons ainsi obtenu, quelle que soit la méthode choisie, un objet vectoriel, sentiers_megantic, qui contient les sentiers du Parc national du Mont-Mégantic.


5. Trouver le sentier le plus proche du sommet le plus haut

Nous allons maintenant utiliser la fonction st_nearest_feature() de la bibliothèque sf pour déterminer quel sentier (LINES) se trouve le plus proche du sommet (POINT). Notez qu’en anglais “near” signifie proche et “feature” signifie éléments ou plus précisément une entité spatiale (aussi appelée une géométrie) dans le présent contexte. La fonction st_nearest_feature(x,y) comprend deux arguments x et y et retourne l’indice de l’entité spatiale dans y qui est le plus près de l’entité x.

id_nearest <- st_nearest_feature(sf_point_max, sentiers_megantic)
id_nearest
[1] 6


Nous pouvons ainsi trouver le sentier recherché à partir de l’indice que nous venons d’identifier.

sentier_top <- sentiers_megantic[id_nearest, ]
sentier_top
Simple feature collection with 1 feature and 22 fields
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -207500 ymin: 165100 xmax: -206500 ymax: 166000
Projected CRS: NAD83 / Quebec Lambert
     No_etab Code_etab No_reseau Code_res
1251      12       MME         2       PQ
            Reseau      Nom_etab     Maj Source
1251 Parc national Mont-Mégantic 2010-08    GPS
       Usager     Etat                Toponyme1
1251 Pédestre Existant Sentier du Mont-Mégantic
             Toponyme2 Toponyme3 Toponyme4 Niv_diff
1251 Les Trois-Sommets      <NA>      <NA>     <NA>
                       Secteur Usager_2 Toponyme5
1251 Secteur de l'Observatoire     <NA>      <NA>
     Toponyme6 Usager_3 Usager_4 Shape_Leng
1251      <NA>     <NA>     <NA>       1456
                           geometry
1251 MULTILINESTRING ((-207526 1...


Observez que la géométrie de cette entité spatiale est une multiligne (MULTILINESTRING).

Notons que nous aurions pu faire la même opération directement sur l’objet sentiers, c’est-à-dire par la commande suivante:

sentiers[st_nearest_feature(sf_point_max, sentiers),]


Regardons où ce sentier se situe par rapport aux autres sentiers du parc :

mapview(sentiers_megantic) + mapview(sentier_top, color="red")


Nous observons que ce sentier n’est en fait qu’une petite portion du parcours qu’un.e randonneur.se devra faire pour se rendre le plus près possible du plus haut sommet. Or, nous voulons le sentier complet à parcourir depuis le pied de la montagne. L’attribut Toponyme1 nous donne justement le nom du sentier (Sentier du Mont-Mégantic) auquel appartient la section sentier_top que nous venons d’identifier.

Revenons maintenant au shapefile sentiers_megantic pour y extraire toutes les autres portions du sentier nommé “Sentier du Mont-Mégantic” qui ensemble formeront la randonnée complète. Pour ce faire nous utilisons la fonction subset() :

rando_sections <- subset(sentiers_megantic, Toponyme1 == "Sentier du Mont-Mégantic")


Visualisons ensuite ces différentes portions en les colorant selon un identifiant id :

rando_sections$id <- 1:5
mapview(rando_sections, zcol = "id")


Changeons l’ordre des portions afin qu’elles suivent l’ordre selon lequel elles seront parcourues à partir du sommet jusqu’au pied de la montagne (c’est-à-dire de 1 à 5 plutôt que 5,1,2,3,4) :

rando_sections <- rando_sections[c(5, 1:4),]
rando_sections$id <-1:5
mapview(rando_sections, zcol = "id")


Combinons maintenant ces cinq portions en une seule entité spatiale (MULTILINESTRING) en utilisant la fonction st_combine() de la bibliothèque sftelle que vue au module 7 :

rando <- st_combine(rando_sections)
rando
Geometry set for 1 feature 
Geometry type: MULTILINESTRING
Dimension:     XY
Bounding box:  xmin: -207700 ymin: 163500 xmax: -206200 ymax: 166100
Projected CRS: NAD83 / Quebec Lambert
MULTILINESTRING ((-207664 166072, -207663 16607...


Observez que nous avons effectivement une seule entité spatiale.

6. Extraire les cellules d’élévation le long du sentier

Pour faire le profil topographique du sentier rando, nous devons extraire des données d’élévation du parc (elv_megantic) celles qui se trouvent le long du sentier. Pour ce faire nous allons utiliser la fonction extract() de la bibliothèque raster comme suit.

topo_elv <- extract(elv_megantic, st_as_sf(rando), along = TRUE, cellnumbers = TRUE) 


Notons que cette opération peut prendre plusieurs secondes pour être complétée par votre ordinateur.

Trois remarques méritent d’être mentionnées concernant cette opération :

  1. Nous avons besoin de convertir rando qui est de classe sfc en objet de classe sf pour l’utiliser avec extract(), ce que nous faisons avec st_as_sf().


  1. along = TRUE permet d’obtenir les cellules ordonnées le long des lignes de rando_sections.


  1. cellnumbers = TRUE nous permet d’avoir les indices des cellules extraites.


Comme vu un peu plus haut, la fonction extract() retourne une liste. Ainsi, topo_elv est un objet de classe list, mais cette fois le premier élément est une matrice avec deux colonnes: la première colonne contient les identifiants des cellules, et la seconde, les valeurs d’élévation (précédemment, le résultat de extract() était simplement un vecteur avec des valeurs d’élévation).

Nous pouvons confirmer ces propos par les commandes suivantes :

class(topo_elv)       #topo_elv est un objet de classe "list"
[1] "list"
length(topo_elv)      #cette liste comprend une seule entrée
[1] 1
dim(topo_elv[[1]])    #La première entrée de la liste est une matrice de 876 lignes et de 2 colonnes.
[1] 1603    2


Attribuons des noms à chacune des colonnes de la matrice contenue dans la première entrée de la liste topo_elv 

colnames(topo_elv[[1]]) <- c("cellule_id", "elevation")
head(topo_elv[[1]])
  cellule_id elevation
v    3971831      1099
     3971831      1099
     3975080      1099
     3975081      1099
     3978330      1099
     3981579      1099


Nous avons donc toutes les valeurs d’élévation le long du sentier.


7. Obtenir le profil topographique

Rappelons qu’un profil topographique est une représentation graphique du relief qui affiche l’élévation à chaque point le long d’un sentier (voir la figure 8.2). L’abscisse d’un tel graphique (c’est-à-dire l’axe des x) correspond à la distance parcourue depuis le début de la randonnée (par exemple de 0 à 100 km) et l’ordonnée (c’est-à-dire l’axe des y) correspond à l’élévation. Maintenant que nous avons l’élévation pour chaque point le long du sentier (topo_elv), nous devons déterminer la distance parcourue depuis le début du trajet jusqu’à chaque point le long de ce sentier.

Tout d’abord, nous devons calculer la distance parcourue entre chaque paire de points qui se suivent le long du sentier. Pour ce faire, nous utiliserons la fonction st_distance() vue au module 7. Or, cette fonction ne peut être utilisée que sur un objet spatial, et comme nous venons de le constater topo_elv est un objet de classe list. Nous devons donc transformer topo_elv en objet spatial avec la fonction st_as_sf().

De plus, un objet spatial doit être défini par des coordonnées spatiales. Pour l’instant, les cellules formant le sentier dans topo_elv sont identifiées par leur indice et non par leurs coordonnées. Une étape préliminaire est donc nécessaire pour retrouver les coordonnées spatiales correspondant à chaque indice. Cela est possible grâce à la fonction xyFromCell() de la bibliothèque raster qui retourne les coordonnées spatiales associées à chaque indice dans le raster à partir duquel nous avons extrait topo_elv, c’est-à-dire elv_megantic.

df_pts <- as.data.frame(xyFromCell(elv_megantic, topo_elv[[1]][, 1]))
head(df_pts)
        x      y
1 -207662 166072
2 -207662 166072
3 -207662 166069
4 -207659 166069
5 -207659 166066
6 -207659 166062


Nous avons mis les coordonnées spatiales dans le tableau df_pts de type data.frame. Notez que l’argument topo_elv[[1]][, 1] correspond aux indices des cellules (c’est la première colonne de la première entrée de la liste topo_elv).

Nous pouvons ensuite utiliser la fonction st_as_sf() pour transformer df_pts en objet de classe sf :

topo_pts <- st_as_sf(df_pts,
    coords = c("x", "y"),
    crs = st_crs(sentiers_megantic) 
  )


Remarquez que nous avons également attribué un SCR au nouvel objet spatial topo_pts.

Nous pouvons finalement utiliser la fonction st_distance() pour calculer les distances entre points successifs de topo_pts. La fonction st_distance(x,y) nécessite obligatoirement deux objets spatiaux (x et y) en argument. Elle calcule alors la distance entre chaque paire d’éléments de x et de y, qu’elle retourne sous forme de matrice. Par exemple, l’opération suivante calcule la distance entre toutes les paires de points compris dans topo_pts :

dist_tout <- st_distance(topo_pts,topo_pts)
dist_tout[1:5,1:5]
Units: [m]
      [,1]  [,2]  [,3]  [,4]  [,5]
[1,] 0.000 0.000 3.353 4.742 7.498
[2,] 0.000 0.000 3.353 4.742 7.498
[3,] 3.353 3.353 0.000 3.353 4.742
[4,] 4.742 4.742 3.353 0.000 3.353
[5,] 7.498 7.498 4.742 3.353 0.000


L’élément dist_tout[1,1] est la distance entre le premier point de topo_pts et lui-même (donc zéro), l’élément dist_tout[1,2] est la distance entre le premier et le deuxième point de topo_pts (aussi zéro car ces deux points sont probablement très rapprochés), dist_tout[1,3] entre le premier et le troisième point de topo_pts, et ainsi de suite pour tous les points de topo_pts.

La fonction st_distance() s’utilise aussi avec l’argument by_element = TRUE. Dans ce cas, la fonction retourne un vecteur dont le premier élément correspond à la distance entre le premier élément de x et le premier élément de y; le deuxième élément, à la distance entre le deuxième élément de x et le deuxième élément de y, et ainsi de suite. Par exemple, l’opération suivante :

dist0 <- st_distance(topo_pts,topo_pts,by_element = TRUE)
dist0[1:5]
Units: [m]
[1] 0 0 0 0 0


retourne un vecteur nul puisque chaque élément correspond à la distance entre un point et lui-même.

Pour répondre à notre question, nous devons calculer la distance entre des points successifs de topo_pts (c’est-à-dire la distance entre le premier et le deuxième point, entre le deuxième et le troisième point, entre le troisième et le quatrième point, etc.). Il s’agit de calculer la distance pour des paires de points formés entre topo_pts et une version de topo_pts qui est décalée d’un élément. Nous utilisons donc la fonction st_distance() avec comme premier argument topo_pts sans le premier point, et comme deuxième argument topo_pts aussi mais cette fois sans le dernier point:

dist_pts <- st_distance(topo_pts[-1, ], topo_pts[-nrow(topo_pts),],
 by_element = TRUE)


Nous pouvons maintenant calculer la distance parcourue à chaque point le long du sentier depuis le début de la randonnée. Pour ce faire nous utilisons la fonction cumsum() qui calcule la somme cumulée des distances entre chaque point :

dist_parcourue <- cumsum(dist_pts)


Nous pouvons finalement visualiser l’élévation (la deuxième colonne de topo_elv[[1]][, 2]) en fonction de la somme cumulée des distances, dist_parcourue. Remarquons que le vecteur dist_parcourue comprend un élément de moins que le vecteur topo_elv[[1]][, 2]. Ceci s’explique par le fait que dist_pts, à partir duquel nous calculons dist_parcourue, mesure la distance entre les points (c’est-à-dire des intervalles). Par conséquent, nous ajoutons la distance initiale de 0 m au vecteur des distances parcourues.

plot(c(0, dist_parcourue), topo_elv[[1]][, 2], 
  main = "Profil topographique du Sentier du Mont-Mégantic", 
  xlab = "Distance depuis le sommet (en mètre)", 
  ylab = "Altitude (en mètre)", 
  type = "l", # pour utiliser une ligne
  lwd = 2 # augmente le trait de la ligne 
) 


Et voilà qui complète la réalisation du profil topographique du sentier menant le plus près du sommet le plus haut.