Nothing
#' @export
#' @rdname rasterize
rasterize_terrain = function(las, res = 1, algorithm = tin(), use_class = c(2L,9L), shape = "convex", ...)
{
UseMethod("rasterize_terrain", las)
}
#' @export
rasterize_terrain.LAS = function(las, res = 1, algorithm = tin(), use_class = c(2L,9L), shape = "convex", ...)
{
# NOTE: the complexity of the code comes from the fact that res can be a number, a RasterLayer
# a stars or a SpatRaster, and the function must return a corresponding raster. This implies
# to use some raster agnostic wrappers
# Defensive programming
if (is_a_number(res))
assert_all_are_non_negative(res)
else if (!raster_is_supported(res))
stop("'res' must be a number or a raster.", call. = FALSE)
assert_is_algorithm(algorithm)
assert_is_algorithm_spi(algorithm)
if (is.character(shape))
shape <- match.arg(shape, c("convex", "concave", "bbox"))
else if (!is(shape, "sfc_POLYGON") & !is(shape, "sfc_MULTIPOLYGON"))
stop("Argument 'shape' must be a string or a sfc", call. = FALSE)
if (!"Classification" %in% names(las)) stop("LAS object does not contain 'Classification' attribute", call. = FALSE)
if (any(as.integer(use_class) != use_class)) stop("'use_class' is not a vector of integers'", call. = FALSE)
use_class <- as.integer(use_class)
# Ellipsis parsing
dots <- list(...)
Wdegenerated <- isTRUE(dots$Wdegenerated)
keep_lowest <- isTRUE(dots$keep_lowest)
force_crs <- isTRUE(dots$force_crs)
pkg <- if (is.null(dots$pkg)) getOption("lidR.raster.default") else dots$pkg
# Non standard evaluation (R CMD check)
. <- Z <- Zref <- X <- Y <- Classification <- NULL
# Select the ground points
ground <- las@data[Classification %in% c(use_class), .(X,Y,Z)]
if (nrow(ground) == 0) stop("No ground points found. Impossible to compute a DTM.", call. = FALSE)
ground <- check_degenerated_points(ground, Wdegenerated)
# Find where to interpolate the DTM. Generate the XY where we want a DTM
# It creates a stars object no matter the input which might be a number
# a Raster, a stars or a SpatRaster
layout <- if (!is_a_number(res)) raster_template(res) else raster_layout(las, res, format = "template")
layout <- raster_materialize(layout, values = 0, pkg = "terra")
if (inherits(layout, "Raster")) sf::st_crs(layout) <- st_crs(las) # Fix #517 because if res is a RasterLayer the crs is proj4 and it mess up everything
if (is.character(shape) && shape %in% c("convex", "concave"))
{
if (shape == "concave")
hull <- st_concave_hull(las, concavity = 10, length_threshold = 100)
else
hull <- st_convex_hull(las)
shape <- sf::st_buffer(hull, dist = raster_res(layout)[1]/2)
}
if (is(shape, "sfc"))
{
shape <- terra::vect(shape)
layout <- terra::mask(layout, shape)
}
grid <- raster_as_dataframe(layout, xy = TRUE, na.rm = TRUE)
# Interpolate the terrain providing what to interpolate (ground) and where
# to interpolate (grid)
lidR.context <- "rasterize_terrain"
ground <- LAS(ground, las@header, crs = st_crs(las), check = FALSE, index = las@index)
Zg <- algorithm(ground, grid)
Zg[is.nan(Zg)] <- NA_real_
# If it remains NAs it means that we have points very far from ground points
# and they cannot be interpolated. But we will interpolate them anyway. Previously
# the function stopped. It now forces interpolation with NN.
isna <- is.na(Zg)
nnas <- sum(isna)
if (nnas > 0)
{
nn <- knnidw(1, rmax = .Machine$double.xmax)
sub_grid <- data.frame(X = grid$X[isna], Y = grid$Y[isna])
znn <- nn(ground, sub_grid)
Zg[isna] <- znn
warning(glue::glue("Interpolation of {nnas} points failed because they are too far from ground points. Nearest neighbour was used but interpolation is weak for those points"), call. = FALSE)
}
# Quantize the elevation for not returning absurdly accurate elevation
fast_quantization(Zg, las[["Z scale factor"]], las[["Z offset"]])
# Assignment of the values to a raster. It might be a raster from raster/stars/terra
# This part of the code is raster agnostic. It returns a raster from the default format
# registered in lidR or from pkg argument or the format given in 'res'
cells <- get_group(layout, grid)
layout <- raster_layout(las, res)
layout <- raster_materialize(layout, pkg = pkg)
layout <- raster_set_values(layout, Zg, cells = cells)
# Replace the interpolated value by the lowest point (legacy code) for option
# keep_lowest that has been removed
if (keep_lowest)
{
res <- raster_res(layout)[1]
lasg <- filter_poi(las, Classification %in% c(use_class))
rmin <- template_metrics(lasg, ~list(Z = min(Z)), layout)
Z1 <- raster_values(layout)
Z2 <- raster_values(rmin)
Zg <- pmin(Z1, Z2, na.rm = TRUE)
layout <- raster_set_values(layout, Zg)
}
raster_names(layout) <- "Z"
return(layout)
}
#' @export
rasterize_terrain.LAScluster = function(las, res = 1, algorithm = tin(), use_class = c(2L,9L), shape = "convex", ...)
{
x <- readLAS(las)
if (is.empty(x)) return(NULL)
# crop the raster to the extent of the chunk because the raster can be a very large proxy
# that will be materialized by rasterize_terrain LAS
if (is_raster(res)) res <- raster_crop(res, st_adjust_bbox(x, raster_res(res)))
dtm <- rasterize_terrain(x, res, algorithm, use_class = use_class, shape = shape, ...)
dtm <- raster_crop(dtm, st_bbox(las))
return(dtm)
}
#' @export
rasterize_terrain.LAScatalog = function(las, res = 1, algorithm = tin(), use_class = c(2L,9L), shape = "convex", ...)
{
assert_is_algorithm(algorithm)
assert_is_algorithm_spi(algorithm)
if (is_a_number(res))
assert_all_are_non_negative(res)
else if (!raster_is_supported(res))
stop("'res' must be a number or a raster.", call. = FALSE)
# subset the collection to the size of the layout (if any)
if (!is_a_number(res)) las <- catalog_intersect(las, res)
# Workaround for #690 (copied from #580). If rasterize_terrain is ran in parallel it will fail with
# SpatRaster because they are not serializable. SpatRaster are converted to RasterLayer
# for multicore strategies
if (is_raster(res) && raster_pkg(res) == "terra")
{
ncores <- try_to_get_num_future_cores()
if (!is.null(ncores) && ncores >= 2L)
res <- raster::raster(res)
}
# Enforce some options
opt_select(las) <- "xyzc"
opt_filter(las) <- paste("-keep_class", paste(use_class, collapse = " "), opt_filter(las))
# Compute the alignment options including the case when res is a raster/stars/terra
alignment <- raster_alignment(res)
if (opt_chunk_size(las) > 0 && opt_chunk_size(las) < 2*alignment$res)
stop("The chunk size is too small. Process aborted.", call. = FALSE)
if (is(shape, "sfc"))
las = catalog_intersect(las, shape, subset = "flag_processed")
# Processing
options <- list(need_buffer = TRUE, drop_null = TRUE, raster_alignment = alignment, automerge = TRUE)
output <- catalog_apply(las, rasterize_terrain, res = res, algorithm = algorithm, shape = shape, use_class = use_class, ..., .options = options)
return(output)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.