% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/catalog_apply.R
\name{catalog_apply}
\alias{catalog_apply}
\alias{catalog_sapply}
\alias{catalog_map}
\title{LAScatalog processing engine}
\usage{
catalog_apply(ctg, FUN, ..., .options = NULL)

catalog_sapply(ctg, FUN, ..., .options = NULL)

catalog_map(ctg, FUN, ..., .options = NULL)
}
\arguments{
\item{ctg}{A \link[=LAScatalog-class]{LAScatalog} object.}

\item{FUN}{A user-defined function that respects a given template (see section function template)}

\item{...}{Optional arguments to FUN.}

\item{.options}{See dedicated section and examples.}
}
\description{
This function gives users access to the \link[=LAScatalog-class]{LAScatalog} processing engine.
It allows the application of a user-defined routine over a collection of LAS/LAZ files. The
LAScatalog processing engine is explained in the \link[=LAScatalog-class]{LAScatalog class}\cr\cr
\code{catalog_apply()} is the core of the lidR package. It drives every single function that can process a
\code{LAScatalog}. It is flexible and powerful but also complex. \code{catalog_map()} is a simplified version
of \code{catalog_apply()} that suits for 90\% of use cases.\cr\cr
\code{catalog_sapply()} is a previous attempt to provide simplified version of \code{catalog_apply()}. Use
\code{catalog_map()} instead.
}
\section{Edge artifacts}{


It is important to take precautions to avoid 'edge artifacts' when processing wall-to-wall
tiles. If the points from neighbouring tiles are not included during certain processes,
this could create 'edge artifacts' at the tile boundaries. For example, empty or incomplete
pixels in a rasterization process, or dummy elevations in a ground interpolation. The LAScatalog
processing engine provides internal tools to load buffered data 'on-the-fly'. \code{catalog_map()} takes
care of removing automatically the results computed in the buffered area to avoid unexpected output
with duplicated entries or conflict between values computed twice. It does that in predefined way
that may not suit all cases. \code{catalog_apply()} does not remove the buffer and leave users free
to handle this in a custom way. This is why \code{catalog_apply()} is more complex but gives more freedom
to build new applications.
}

\section{Buffered data}{


The LAS objects loaded in theses functions have a special attribute called 'buffer' that indicates,
for each point, if it comes from a buffered area or not. Points from non-buffered areas have a
'buffer' value of 0, while points from buffered areas have a 'buffer' value of 1, 2, 3 or 4, where
1 is the bottom buffer and 2, 3 and 4 are the left, top and right buffers, respectively. This allows
for filtering of buffer points if required.
}

\section{Function template}{


The parameter \code{FUN} of \code{catalog_apply} expects a function with a first argument that will be
supplied automatically by the \code{LAScatalog} processing engine. This first argument is a \code{LAScluster}.
A \code{LAScluster} is an internal undocumented class but the user needs to know only three things about
this class:
\itemize{
\item It represents a chunk of the file collection
\item The function \link{readLAS} can be used with a \code{LAScluster}
\item The function \link[raster:extent]{extent} or \link[sf:st_bbox]{st_bbox}
can be used with a \code{LAScluster} and they return the bounding box of the chunk without the buffer.
It must be used to clip the output and remove the buffered region (see examples).
}

A user-defined function must be templated like this:

\if{html}{\out{<div class="sourceCode">}}\preformatted{myfun <- function(chunk, ...) \{
   # Load the chunk + buffer
   las <- readLAS(chunk)
   if (is.empty(las)) return(NULL)

   # do something
   output <- do_something(las, ...)

   # remove the buffer of the output
   bbox <- bbox(chunk)
   output <- remove_buffer(output, bbox)
   return(output)
\}
}\if{html}{\out{</div>}}

The line \code{if (is.empty(las)) return(NULL)} is important because some clusters (chunks) may contain
0 points (we can't know this before reading the file). In this case an empty point cloud with 0 points
is returned by \code{readLAS()} and this may fail in subsequent code. Thus, exiting early from the user-defined
function by returning \code{NULL} indicates to the internal engine that the chunk was empty.

\code{catalog_map} is much simpler (but less versatile). It automatically takes care of reading the chunk
and checks if the point cloud is empty. It also automatically crop the buffer. The way it crops the
buffer suits for most cases but for some special cases it may be advised to handle this in a more
specific way i.e. using \code{catalog_apply()}. For \code{catalog_map()} the first argument is a \code{LAS} and the
template is:

\if{html}{\out{<div class="sourceCode">}}\preformatted{myfun <- function(las, ...) \{
   # do something
   output <- do_something(las, ...)
   return(output)
\}
}\if{html}{\out{</div>}}
}

\section{.options}{

Users may have noticed that some lidR functions throw an error when the processing options are
inappropriate. For example, some functions need a buffer and thus \code{buffer = 0} is forbidden.
Users can add the same constraints to protect against inappropriate options. The \code{.options}
argument is a \code{list} that allows users to tune the behaviour of the processing engine.
\itemize{
\item \code{drop_null = FALSE}: not intended to be used by regular users. The engine does not remove
NULL outputs
\item \code{need_buffer = TRUE}: the function complains if the buffer is 0.
\item \code{need_output_file = TRUE} the function complains if no output file template is provided.
\item \code{raster_alignment = ...} the function checks the alignment of the chunks. This option is
important if the output is a raster. See below for more details.
\item \code{automerge = TRUE} by default the engine returns a \code{list} with one item per chunk. If
\code{automerge = TRUE}, it tries to merge the outputs into a single object: a \verb{Raster*}, a
\verb{Spatial*}, a \verb{LAS*}, an \code{sf}, a \code{ stars} similarly to other functions of the package. This is a
fail-safe option so in the worst case, if the merging fails, the \code{list} is returned. This is
activated by \code{catalog_map} making it simpler.
\item \code{autoread = TRUE}. Introduced in v3.0.0 this option enables to get rid of the first steps of the
function i.e \code{readLAS()} and \verb{if (is.empty())}. In this case the function must take two
objects as input, first a \code{LAS} object and second a \code{bbox} from \code{sf}. This is
activated by \code{catalog_map} making it simpler.
\item \code{autocrop = TRUE}. Introduced in v4.0.0 this option enables to get rid of the buffer crop steps
of the function i.e \code{something <- remove_buffer(something, bbox)}. In this case the function must
take one \code{LAS} object as input. This is activated by \code{catalog_map} making it simpler.
}

When the function \code{FUN} returns a raster it is important to ensure that the chunks are aligned
with the raster to avoid edge artifacts. Indeed, if the edge of a chunk does not correspond to the edge
of the pixels, the output will not be strictly continuous and will have edge artifacts (that might
not be visible). Users can check this with the options \code{raster_alignment}, that can take the
resolution of the raster as input, as well as the starting point if needed. The following are accepted:

\if{html}{\out{<div class="sourceCode">}}\preformatted{# check if chunks are aligned with a raster of resolution 20
raster_alignment = 20
raster_alignment = list(res = 20)

# check if chunks are aligned with a raster of resolution 20
# that starts at (0,10)
raster_alignment = list(res = 20, start = c(0,10))
}\if{html}{\out{</div>}}
}

\examples{
# More examples might be avaible in the official lidR vignettes or
# on the github book <https://jean-romain.github.io/lidRbook/>

## =========================================================================
## Example 1: detect all the tree tops over an entire catalog
## (this is basically a reproduction of the existing function 'locate_trees')
## =========================================================================

# 1. Build the user-defined function that analyzes each chunk of the catalog.
# The function's first argument is a LAScluster object. The other arguments can be freely
# chosen by the users.
my_tree_detection_method <- function(chunk, ws)
{
  # The chunk argument is a LAScluster object. The users do not need to know how it works.
  # readLAS will load the region of interest (chunk) with a buffer around it, taking advantage of
  # point cloud indexation if possible. The filter and select options are propagated automatically
  las <- readLAS(chunk)
  if (is.empty(las)) return(NULL)

  # Find the tree tops using a user-developed method
  # (here simply a LMF for the example).
  ttops <- locate_trees(las, lmf(ws))

  # ttops is an sf object that contains the tree tops in our region of interest
  # plus the trees tops in the buffered area. We need to remove the buffer otherwise we will get
  # some trees more than once.
  bbox  <- st_bbox(chunk)
  ttops <- sf::st_crop(ttops, bbox)
  return(ttops)
}

# 2. Build a collection of file
# (here, a single file LAScatalog for the purposes of this simple example).
LASfile <- system.file("extdata", "MixedConifer.laz", package="lidR")
ctg <- readLAScatalog(LASfile)
plot(ctg)

# 3. Set some processing options.
# For this small single file example, the chunk size is 100 m + 10 m of buffer
opt_chunk_buffer(ctg) <- 10
opt_chunk_size(ctg)   <- 100            # Small because this is a dummy example.
opt_chunk_alignment(ctg) <- c(-50, -35) # Align such as it creates 2 chunks only.
opt_select(ctg)       <- "xyz"          # Read only the coordinates.
opt_filter(ctg)       <- "-keep_first"  # Read only first returns.

# 4. Apply a user-defined function to take advantage of the internal engine
opt    <- list(need_buffer = TRUE,   # catalog_apply will throw an error if buffer = 0
               automerge   = TRUE)   # catalog_apply will merge the outputs into a single object
output <- catalog_apply(ctg, my_tree_detection_method, ws = 5, .options = opt)

plot(output)

\donttest{
## =========================================================================
## Example 1: simplified. There is nothing that requires special data
## manipulation in the previous example. Everything can be handled automatically
##=========================================================================

# 1. Build the user-defined function that analyzes a point cloud.
my_tree_detection_method <- function(las, ws)
{
  # Find the tree tops using a user-developed method
  # (here simply a LMF for the example).
  ttops <- locate_trees(las, lmf(ws))
  return(ttops)
}

# 2. Build a project
LASfile <- system.file("extdata", "MixedConifer.laz", package="lidR")
ctg <- readLAScatalog(LASfile)
plot(ctg)

# 3. Set some processing options.
# For this dummy example, the chunk size is 100 m and the buffer is 10 m
opt_chunk_buffer(ctg) <- 10
opt_chunk_size(ctg)   <- 100            # small because this is a dummy example.
opt_chunk_alignment(ctg) <- c(-50, -35) # Align such as it creates 2 chunks only.
opt_select(ctg)       <- "xyz"          # Read only the coordinates.
opt_filter(ctg)       <- "-keep_first"  # Read only first returns.

# 4. Apply a user-defined function to take advantage of the internal engine
opt    <- list(need_buffer = TRUE)   # catalog_apply will throw an error if buffer = 0
output <- catalog_map(ctg, my_tree_detection_method, ws = 5, .options = opt)
}

\dontrun{
## ===================================================
## Example 2: compute a rumple index on surface points
## ===================================================

rumple_index_surface = function(las, res)
{
  las    <- filter_surfacepoints(las, 1)
  rumple <- pixel_metrics(las, ~rumple_index(X,Y,Z), res)
  return(rumple)
}

LASfile <- system.file("extdata", "Megaplot.laz", package="lidR")
ctg <- readLAScatalog(LASfile)

opt_chunk_buffer(ctg) <- 1
opt_chunk_size(ctg)   <- 140     # small because this is a dummy example.
opt_select(ctg)       <- "xyz"   # read only the coordinates.

opt    <- list(raster_alignment = 20) # catalog_apply will adjust the chunks if required
output <- catalog_map(ctg, rumple_index_surface, res = 20, .options = opt)

plot(output, col = height.colors(25))
}
}
