@@ -84,9 +84,11 @@ A single LAS file can be loaded in R with the `lidR::readLAS` function, which re
* `data`: point cloud attributes.
* `crs`: projection information.
The bounding box of the data cona be accessed with the function `sf::st_bbox`.
The bounding box of the data can be accessed with the function `sf::st_bbox`.
It is advisable to fill the projection information if missing, as spatial objects computed from the point cloud will inherit the information. A warning is issued when loading this file because unexpected values are present in the scan angle field.
It is advisable to fill the projection information if missing, as spatial objects computed from the point cloud will inherit the information.
A warning is issued when loading the following example file because unexpected values are present in the scan angle field.
```{r ALS.load, include = TRUE}
# read file
...
...
@@ -144,16 +146,16 @@ If one considers that the power transmitted by the emitter $P_t$ is constant and
In case the aircraft trajectory is available, it is possible to normalize amplitude values from the range ($R$) effect. One must however be aware that the resulting distribution of amplitude values might not be homogeneous because in areas with longer range the signal to noise ratio is lower.
```{r ALS.intensity, eval=TRUE, fig.width=6}
```{r ALS.intensity, eval=TRUE, fig.width=4}
hist(point_cloud$Intensity, xlab = "Raw value", main = "Histogram of Intensity")
```
**Echo position within returns from the same pulse**: `ReturnNumber` is the order of arrival of the echo among the `NumberOfReturns` echoes associated to a given pulse. Echoes are sometimes referred to as:
* single if `NumberOfReturns = ReturnNumber = 1`
* first if `ReturnNumber = 1`
* last if `NumberOfReturns = ReturnNumber`
* intermediate if `1 < ReturnNumber < NumberOfReturns`
* *single* if `NumberOfReturns = ReturnNumber = 1`
* *first* if `ReturnNumber = 1`
* *last* if `NumberOfReturns = ReturnNumber`
* *intermediate* if `1 < ReturnNumber < NumberOfReturns`
The contingency table of `ReturnNumber` and `NumberOfReturns` should have no values below the diagonal, and approximately the same values in each column.
**`ScanAngleRank`** is the scan angle associated to the pulse. Origin of values might correspond to the horizontal or vertical. In most cases it is the scan angle relative to the laser scanner, but sometimes values relative to nadir are indicated. In the case of this file values are from 60 to 120: the scan range is ± 30 degrees on both sides of the scanner, with 90 the value when pulses are emitted downwards.
```{r ALS.scanangle, eval=TRUE, fig.width=6}
```{r ALS.scanangle, eval=TRUE, fig.width=4}
hist(point_cloud$ScanAngleRank, main = "Histogram of scan angles", xlab = "Angle (degrees)")
```
**`UserData`** is a field to store additional information, but it is limited in capacity. In the latest version of LAS files, additional attributes can be added.
...
...
@@ -249,9 +251,9 @@ For checking purposes one might be interested to map the following features, in
The spatial resolution of the map should be relevant with respect to the announced specifications, and reach a trade-off between the amount of spatial detail and the possibility to evaluate the whole dataset at a glance.
The `lidR::grid_metrics` function is very efficient to derive statistics maps from the point cloud. Point statistics are computed for all pixels at the given resolution, based on points contained in the cells. In this case the density variation observed between the tiles is due to the fact that points from a given flight strip where written in the eastern tile but not in the western one.
The `lidR::pixel_metrics` function is very efficient to derive statistics maps from the point cloud. Point statistics are computed for all pixels at the given resolution, based on points contained in the cells. In this case the density variation observed between the tiles is due to the fact that points from a given flight strip where written in the eastern tile but not in the western one.
From those maps summary statistics can be derived, e.g. the percentage of cells with no ground points (`r p_no_ground`). Checking that the spatial distribution of pulses is homogeneous can be informative.
hist(terra::values(metrics$npulses), main = "Histogram of pulse density", xlab = "Pulse density /m2", ylab = "Number of 5m cells")
# par(fig=c(3,10,0,10)/10)
terra::plot(metrics$npulses < 5, main = "Areas with pulse density < 5 /m2")
```
...
...
@@ -310,7 +315,7 @@ Digital elevation models (DEMs) are raster images where each cell value correspo
### Digital terrain model (DTM)
It represents the altitude of the bare earth surface. It is computed using points classified as ground. For better representation of certain topographical features, some additional points or lines might be added. The function `lidR::grid_terrain` proposes different algorithms for computation, and works either with catalogs or LAS objects.
It represents the altitude of the bare earth surface. It is computed using points classified as ground. For better representation of certain topographical features, some additional points or lines might be added. The function `lidR::rasterize_terrain` proposes different algorithms for computation, and works either with catalogs or LAS objects.
```{r dem.terrain, include = TRUE, message = FALSE}
# create dtm at 0.5 m resolution with TIN algorithm
...
...
@@ -320,7 +325,7 @@ dtm
Functions from the `terra` package are available to derive topographical rasters (slope, aspect, hillshade, contour lines) from the DTM.
@@ -26,7 +26,7 @@ The code below presents a workflow to prepare inventory data for the calibration
Licence: GNU GPLv3 / [Source page](https://gitlab.irstea.fr/jean-matthieu.monnet/lidartree_tutorials/-/blob/master/R/area-based.1.data.preparation.Rmd)
Required `R` packages : `ggplot2`, `sf`, `ggmap`
Required `R` packages : `ggplot2`, `sf`, `ggmap`, `lidaRtRee` (tested with version `r packageVersion("lidaRtRee")`) and `lidR` (tested with version `r packageVersion("lidR")`)
Many thanks to Pascal Obstétar for checking code and improvement suggestions.
@@ -88,7 +88,7 @@ Two types of vegetation metrics can be computed.
## Point cloud metrics
Point cloud metrics are computed with the function `lidaRtRee::clouds_metrics`, which applies the function `lidR::cloud_metrics` to all point clouds in a list. Default computed metrics are those proposed by the function [`lidR::stdmetrics`](https://github.com/Jean-Romain/lidR/wiki/stdmetrics). Additional metrics are available with the function `lidaRtRee::aba_metrics`. The buffer points, which are located outside of the plot extent inventoried on the field, should be removed before computing those metrics.
Point cloud metrics are computed with the function `lidaRtRee::clouds_metrics`, which applies the function `lidR::cloud_metrics` to all point clouds in a list. Default computed metrics are those proposed by the function [`lidR::stdmetrics`](https://github.com/r-lidar/lidR/wiki/stdmetrics). Additional metrics are available with the function `lidaRtRee::aba_metrics`. The buffer points, which are located outside of the plot extent inventoried on the field, should be removed before computing those metrics.
```{r computeMetrics, include=TRUE}
# define function for later use
...
...
@@ -159,7 +159,7 @@ model_aba$stats
The function computes values predicted in leave-one-out cross-validation, by using the same combination of dependent variables and fitting the regression coefficients with all observations except one. Predicted values can be plotted against field values with the function `lidaRtRee::aba_plot`. It is also informative to check the correlation of prediction errors with other forest or environmental variables.
The model seems to fail to predict large values, and the prediction errors are positively correlated with basal area.
The prediction errors seem positively correlated with basal area.
For computation of point cloud metrics, the same function used in `lidaRtRee::clouds_metrics` is now supplied to `lidR::grid_metrics`. Some metrics are displayed hereafter.
For computation of point cloud metrics, the same function used in `lidaRtRee::clouds_metrics` is now supplied to `lidR::pixel_metrics`. Some metrics are displayed hereafter.
for (i in c("Tree_meanH", "Tree_sdH", "Tree_density"))
{
raster::plot(metrics_trees[[i]], main = i)
terra::plot(metrics_trees[[i]], main = i)
}
```
### Terrain metrics
Terrain metrics can be computed from the altitude values of ground points. A wrapping function is required to pass `lidaRtRee::terrain_points_metrics` to `lidR::grid_metrics`.
Terrain metrics can be computed from the altitude values of ground points. A wrapping function is required to pass `lidaRtRee::terrain_points_metrics` to `lidR::pixel_metrics`.
If the metrics maps are to be displayed in external software, the "tif" format can be used to produce one single with all bands. Meanwhile band names are not retained, so it is useful to save them in a separated `R` archive.
...
...
@@ -414,7 +418,7 @@ If the metrics maps are to be displayed in external software, the "tif" format c
Then the function `lidaRtRee::aba_predict` is used to produce the map. If the input is an object containing the stratified model and if the metrics map contains the strata layer, then the model directly maps the output by retaining the corresponding model in each strata.
Then the function `lidaRtRee::aba_predict` is used to produce the map. If the input is an object containing the stratified model and if the metrics map contains the strata layer, then the model directly maps the output by retaining the corresponding model in each stratum.
# option to read only xyzc attributes (coordinates, intensity, echo order and classification) from files
...
...
@@ -124,11 +125,11 @@ s_step <- 0.5
The first step is to compute the canopy height model from the ALS data, and remove artefacts by thresholding extreme values and applying a median filter.
In the previous part, a pixel that fulfills the maximum height criterion but does not fulfill the distance ratio criterion (i.e. it is too close to surrounding vegetation based on the distance / vegetation height ratio) is removed from gap surface. For edge detection, it seems more appropriate to integrate those pixels in the gap surface, otherwise some edges would be detected inside flat areas. The difference is exemplified in the following plots. The second image exhibits more gaps, because some gaps which are not reconstructed do not reach the minimum surface. The gaps in the second image are also larger, because gaps extend to all neighboring pixels complying with the height threshold, even if they do not comply with the distance criterion.
raster::plot(gaps1$gap_id > 0, main = "Gaps", legend = FALSE)
raster::plot(gaps1r$gap_id > 0, main = "Gaps extended by reconstruction", legend = FALSE)
terra::plot(chm, main = "Canopy heightmodel")
terra::plot(gaps1$gap_id > 0, main = "Gaps")
terra::plot(gaps1r$gap_id > 0, main = "Gaps extended by reconstruction")
```
Edge detection is performed by extraction of the difference between a binary image of gaps and the result of a morphological erosion or dilation applied to the same image.
raster::plot(edge_inside, main = "Edges (detection by erosion)", legend = FALSE)
raster::plot(edge_outside, main = "Edges (detection by dilation)", legend = FALSE)
terra::plot(edge_inside, main = "Edges (detection by erosion)", legend = FALSE)
terra::plot(edge_outside, main = "Edges (detection by dilation)", legend = FALSE)
```
Percentage of edges can be calculated as the ratio between edge pixels surface and total surface, multiplied by 100. The result is dependent on the raster resolution. Obtained percentage for the "erosion" method is `r round(sum(raster::values(edge_inside))/(nrow(edge_inside)*ncol(edge_inside))*100, 1)`, whereas it is `r round(sum(raster::values(edge_outside))/(nrow(edge_outside)*ncol(edge_outside))*100, 1)` for method "dilation".
Percentage of edges can be calculated as the ratio between edge pixels surface and total surface, multiplied by 100. The result is dependent on the raster resolution. Obtained percentage for the "erosion" method is `r round(sum(terra::values(edge_inside))/(nrow(edge_inside)*ncol(edge_inside))*100, 1)`, whereas it is `r round(sum(terra::values(edge_outside))/(nrow(edge_outside)*ncol(edge_outside))*100, 1)` for method "dilation".
`lidaRtRee_tutorials` is a repository that provides tutorials for forest analysis with airborne laser scanning (ALS or lidar remote sensing) data, using functions from `R` packages [lidR](https://github.com/Jean-Romain/lidR/)(also available on [CRAN](https://cran.r-project.org/package=lidR)) and [lidaRtRee](https://gitlab.irstea.fr/jean-matthieu.monnet/lidaRtRee)(also available on [CRAN](https://cran.r-project.org/package=lidaRtRee)). Tutorials are available as `Rmarkdown` and `html` files. Datasets useful for code running are also provided.
`lidaRtRee_tutorials` is a repository that provides tutorials for forest analysis with airborne laser scanning (ALS or lidar remote sensing) data, using functions from `R` packages [lidR](https://github.com/r-lidar/lidR/)(also available on [CRAN](https://cran.r-project.org/package=lidR)) and [lidaRtRee](https://gitlab.irstea.fr/jean-matthieu.monnet/lidaRtRee)(also available on [CRAN](https://cran.r-project.org/package=lidaRtRee)). Tutorials are available as `Rmarkdown` and `html` files. Datasets useful for code running are also provided.
# Available tutorials
...
...
@@ -8,4 +8,4 @@ They are listed on the [wiki home page](https://gitlab.irstea.fr/jean-matthieu.m
# Acknowledgements
`lidaRtRee_tutorials` development was funded by the [ADEME](https://www.ademe.fr/en)(french Agency for Ecological Transition) through the [PROTEST](https://protest.inrae.fr/) project (grant 1703C0069 of the GRAINE program).
`lidaRtRee_tutorials` development (2018-2021) was funded by the [ADEME](https://www.ademe.fr/en)(french Agency for Ecological Transition) through the [PROTEST](https://protest.inrae.fr/) project (grant 1703C0069 of the GRAINE program).