# Clear environment
rm(list = ls(globalenv()))
# Load packages
library(lidR)
library(sf)
Regions of Interest
Relevant Resources
Overview
We demonstrate the selection of regions of interest (ROIs) from LiDAR data. Geometries like circles and rectangles are selected based on coordinates. Complex geometries are extracted from shapefiles to clip specific areas.
Environment
Simple Geometries
Load LiDAR Data and Inspect
We start by loading some LiDAR data and inspecting its header and number of point records.
<- readLAS(files = 'data/zrh_norm.laz') las
# Inspect the header and the number of point records
@header
las#> File signature: LASF
#> File source ID: 0
#> Global encoding:
#> - GPS Time Type: GPS Week Time
#> - Synthetic Return Numbers: no
#> - Well Know Text: CRS is GeoTIFF
#> - Aggregate Model: false
#> Project ID - GUID: 00000000-0000-0000-5053-004d00000000
#> Version: 1.1
#> System identifier:
#> Generating software: rlas R package
#> File creation d/y: 0/0
#> header size: 227
#> Offset to point data: 305
#> Num. var. length record: 1
#> Point data format: 1
#> Point data record length: 28
#> Num. of point records: 1270080
#> Num. of points by return: 471651 342346 241371 135987 56797
#> Scale factor X Y Z: 0.01 0.01 0.01
#> Offset X Y Z: 0 0 0
#> min X Y Z: 2670505 1258734 -0.88
#> max X Y Z: 2670755 1258984 48.31
#> Variable Length Records (VLR):
#> Variable Length Record 1 of 1
#> Description: by LAStools of rapidlasso GmbH
#> Tags:
#> Key 1024 value 1
#> Key 3072 value 2056
#> Extended Variable Length Records (EVLR): void
@header$`Number of point records`
las#> [1] 1270080
Select Circular and Rectangular Areas
We can select circular and rectangular areas from the LiDAR data based on specified coordinates and radii or dimensions.
# Establish coordinates
<- 2670592
x <- 1258890
y
# Select a circular area
<- clip_circle(las = las, xcenter = x, ycenter = y, radius = 30)
circle
# Inspect the circular area and the number of point records
circle#> class : LAS (v1.1 format 1)
#> memory : 3.1 Mb
#> extent : 2670562, 2670622, 1258860, 1258920 (xmin, xmax, ymin, ymax)
#> coord. ref. : CH1903+ / LV95
#> area : 2903 m²
#> points : 58.4 thousand points
#> type : airborne
#> density : 20.13 points/m²
#> density : 7.55 pulses/m²
@header$`Number of point records`
circle#> [1] 58447
# Plot the circular area
plot(circle)
We can do the same with a rectangular area by defining corner coordinates.
# Select a rectangular area
<- clip_rectangle(las = las, xleft = x, ybottom = y, xright = x + 40, ytop = y + 30) rect
# Plot the rectangular area
plot(rect)
We can also supply multiple coordinate pairs to clip multiple ROIs.
# Select multiple random circular areas
<- runif(2, x, x)
x <- runif(2, 1258840, 1258890)
y
<- clip_circle(las = las, xcenter = x, ycenter = y, radius = 10) plots
# Plot each of the multiple circular areas
plot(plots[[1]])
# Plot each of the multiple circular areas
plot(plots[[2]])
Extraction of Complex Geometries from Shapefiles
We demonstrate how to extract complex geometries from shapefiles using the clip_roi()
function from the lidR
package.
We use the sf
package to load an ROI and then clip to its extents.
# Load the shapefile using sf
<- sf::st_read(dsn = "data/roi/roi.gpkg", quiet = TRUE)
stand_bdy
# Plot the LiDAR header information without the map
plot(las@header, map = FALSE)
# Plot the stand boundary areas on top of the LiDAR header plot
plot(stand_bdy, add = TRUE, col = "#08B5FF39")
# Extract points within the stand boundary using clip_roi()
<- clip_roi(las = las, geometry = stand_bdy) stand
# Plot the extracted points within the planting areas
plot(stand)
Clipping ROIs with a catalog
We clip the LAS
data in the catalog using specified coordinate groups.
<- catalog("data/ctg_norm")
ctg
# Set coordinate groups
<- c(2670578, 2671234, 2671499, 2671755, 2671122)
x <- c(1258601, 1259050, 1259450, 1259900, 1258750)
y
# Visualize coordinate groups
plot(ctg)
points(x, y)
# Clip plots
<- clip_circle(las = ctg, xcenter = x, ycenter = y, radius = 30) rois
plot(rois[[1]])
plot(rois[[3]])
Validate clipped data
We validate the clipped LAS data using the las_check
function.
las_check(rois[[1]])
#>
#> Checking the data
#> - Checking coordinates... ✓
#> - Checking coordinates type... ✓
#> - Checking coordinates range... ✓
#> - Checking coordinates quantization... ✓
#> - Checking attributes type... ✓
#> - Checking ReturnNumber validity... ✓
#> - Checking NumberOfReturns validity... ✓
#> - Checking ReturnNumber vs. NumberOfReturns... ✓
#> - Checking RGB validity... ✓
#> - Checking absence of NAs... ✓
#> - Checking duplicated points... ✓
#> - Checking degenerated ground points... ✓
#> - Checking attribute population...
#> 🛈 'ScanDirectionFlag' attribute is not populated
#> 🛈 'EdgeOfFlightline' attribute is not populated
#> - Checking gpstime incoherances ✓
#> - Checking flag attributes... ✓
#> - Checking user data attribute... ✓
#> Checking the header
#> - Checking header completeness... ✓
#> - Checking scale factor validity... ✓
#> - Checking point data format ID validity... ✓
#> - Checking extra bytes attributes validity... ✓
#> - Checking the bounding box validity... ✓
#> - Checking coordinate reference system... ✓
#> Checking header vs data adequacy
#> - Checking attributes vs. point format... ✓
#> - Checking header bbox vs. actual content... ✓
#> - Checking header number of points vs. actual content... ✓
#> - Checking header return number vs. actual content... ✓
#> Checking coordinate reference system...
#> - Checking if the CRS was understood by R... ✓
#> Checking preprocessing already done
#> - Checking ground classification... yes
#> - Checking normalization... yes
#> - Checking negative outliers...
#> ⚠ 6 points below 0
#> - Checking flightline classification... yes
#> Checking compression
#> - Checking attribute compression...
#> - ScanDirectionFlag is compressed
#> - EdgeOfFlightline is compressed
#> - Synthetic_flag is compressed
#> - Keypoint_flag is compressed
#> - Withheld_flag is compressed
#> - UserData is compressed
las_check(rois[[3]])
#>
#> Checking the data
#> - Checking coordinates... ✓
#> - Checking coordinates type... ✓
#> - Checking coordinates range... ✓
#> - Checking coordinates quantization... ✓
#> - Checking attributes type... ✓
#> - Checking ReturnNumber validity... ✓
#> - Checking NumberOfReturns validity... ✓
#> - Checking ReturnNumber vs. NumberOfReturns... ✓
#> - Checking RGB validity... ✓
#> - Checking absence of NAs... ✓
#> - Checking duplicated points... ✓
#> - Checking degenerated ground points... ✓
#> - Checking attribute population...
#> 🛈 'ScanDirectionFlag' attribute is not populated
#> 🛈 'EdgeOfFlightline' attribute is not populated
#> - Checking gpstime incoherances
#> ✗ 5 pulses (points with the same gpstime) have points with identical ReturnNumber
#> - Checking flag attributes... ✓
#> - Checking user data attribute... ✓
#> Checking the header
#> - Checking header completeness... ✓
#> - Checking scale factor validity... ✓
#> - Checking point data format ID validity... ✓
#> - Checking extra bytes attributes validity... ✓
#> - Checking the bounding box validity... ✓
#> - Checking coordinate reference system... ✓
#> Checking header vs data adequacy
#> - Checking attributes vs. point format... ✓
#> - Checking header bbox vs. actual content... ✓
#> - Checking header number of points vs. actual content... ✓
#> - Checking header return number vs. actual content... ✓
#> Checking coordinate reference system...
#> - Checking if the CRS was understood by R... ✓
#> Checking preprocessing already done
#> - Checking ground classification... yes
#> - Checking normalization... yes
#> - Checking negative outliers...
#> ⚠ 5 points below 0
#> - Checking flightline classification... yes
#> Checking compression
#> - Checking attribute compression...
#> - ScanDirectionFlag is compressed
#> - EdgeOfFlightline is compressed
#> - Synthetic_flag is compressed
#> - Keypoint_flag is compressed
#> - Withheld_flag is compressed
#> - UserData is compressed
Independent files (e.g. plots) as catalogs
We read an individual LAS file as a catalog and perform operations on it.
# Read single file as catalog
<- readLAScatalog(folder = "data/zrh_norm.laz")
ctg
# Set options for output files
opt_output_files(ctg) <- paste0(tempdir(),"/{XCENTER}_{XCENTER}")
# Write file as .laz
opt_laz_compression(ctg) <- TRUE
# Get random plot locations and clip
<- runif(n = 4, min = ctg$Min.X, max = ctg$Max.X)
x <- runif(n = 4, min = ctg$Min.Y, max = ctg$Max.Y)
y <- clip_circle(las = ctg, xcenter = x, ycenter = y, radius = 10) rois
#> Chunk 1 of 4 (25%): state ✓
#>
Chunk 2 of 4 (50%): state ✓
#>
Chunk 3 of 4 (75%): state ✓
#>
Chunk 4 of 4 (100%): state ✓
#>
# Read catalog of plots
<- readLAScatalog(tempdir())
ctg_plots
# Set independent files option
opt_independent_files(ctg_plots) <- TRUE
opt_output_files(ctg_plots) <- paste0(tempdir(),"/{XCENTER}_{XCENTER}")
# Generate plot-level terrain models
rasterize_terrain(las = ctg_plots, res = 1, algorithm = tin())
#> Chunk 1 of 4 (25%): state ✓
#>
Chunk 2 of 4 (50%): state ✓
#>
Chunk 3 of 4 (75%): state ✓
#>
Chunk 4 of 4 (100%): state ✓
#>
#> class : SpatRaster
#> dimensions : 92, 202, 1 (nrow, ncol, nlyr)
#> resolution : 1, 1 (x, y)
#> extent : 2670512, 2670714, 1258842, 1258934 (xmin, xmax, ymin, ymax)
#> coord. ref. : CH1903+ / LV95 (EPSG:2056)
#> source : rasterize_terrain.vrt
#> name : Z
#> min value : 0
#> max value : 0
Conclusion
This concludes our tutorial on selecting simple geometries and extracting complex geometries from geopackage files (or shapefiles etc…) using the lidR
package in R.