R/add_attribute.R

Defines functions remove_lasattribute add_lasnir add_lasrgb add_lasattribute_manual add_lasattribute add_attribute

Documented in add_attribute add_lasattribute add_lasattribute_manual add_lasnir add_lasrgb remove_lasattribute

#' Add attributes into a LAS object
#'
#' A \link[=LAS-class]{LAS} object represents a las file in R. According to the
#' \href{https://www.asprs.org/a/society/committees/standards/LAS_1_4_r13.pdf}{LAS specifications}
#' a las file contains a core of defined attributes, such as XYZ coordinates, intensity, return number,
#' and so on, for each point. It is possible to add supplementary attributes.
#'
#' Users cannot assign names that are the same as the names of the core attributes. These functions are
#' dedicated to adding data that are not part of the LAS specification. For example, \code{add_lasattribute(las, x, "R")}
#' will fail because \code{R} is a name reserved for the red channel of a .las file that contains RGB
#' attributes. Use \code{add_lasrgb} instead.
#' \describe{
#' \item{\code{add_attribute}}{Simply adds a new column in the data but does not update the header. Thus the LAS
#' object is not strictly valid. These data will be temporarily usable at the R level but will not
#' be written in a las file with \link{writeLAS}.}
#'
#'\item{ \code{add_lasattribute}}{Does the same as \code{add_attribute} but automatically updates the header of
#' the LAS object. Thus, the LAS object is valid and the new data is considered as "extra bytes". This new
#' data will be written in a las file with \link{writeLAS}.}
#'
#' \item{\code{add_lasattribute_manual}}{Allows the user to manually write all the extra bytes metadata.
#' This function is reserved for experienced users with a good knowledge of the LAS specifications.
#' The function does not perform tests to check the validity of the information.
#' When using \code{add_lasattribute} and \code{add_lasattribute_manual}, \code{x} can only be of type numeric,
#' (\code{integer} or \code{double}). It cannot be of type \code{character} or \code{logical} as these are
#' not supported by the LAS specifications. The types that are supported in lidR are types 0 to 10
#' (Table 24 on page 25 of the specification). Types greater than 10 are not supported.}
#'
#' \item{\code{add_lasrgb}}{Adds 3 columns named RGB and updates the point format of the LAS object
#' for a format that supports RGB attributes. If the RGB values are ranging from 0 to 255 they are
#' automatically scaled on 16 bits.}
#' }
#'
#' @param las An object of class \link[=LAS-class]{LAS}
#' @param x a vector that needs to be added in the LAS object. For \code{add_lasattribute*} it can
#' be missing (see details).
#' @param name character. The name of the extra bytes attribute to add in the file.
#' @param desc character. A short description of the extra bytes attribute to add in the file (32 characters).
#' @param type character. The data type of the extra bytes attribute. Can be "uchar", "char", "ushort",
#' "short", "uint", "int", "uint64", "int64", "float", "double".
#' @param scale,offset numeric. The scale and offset of the data. NULL if not relevant.
#' @param NA_value numeric or integer. NA is not a valid value in a las file. At time of writing it will
#' be replaced by this value that will be considered as NA. NULL if not relevant.
#' @param R,G,B,NIR integer. RGB and NIR values. Values are automatically scaled to 16 bits if they are
#' coded on 8 bits (0 to 255).
#'
#' @return An object of class \link[=LAS-class]{LAS}
#'
#' @name add_attribute
#'
#' @examples
#' LASfile <- system.file("extdata", "example.laz", package="rlas")
#' las <- readLAS(LASfile, select = "xyz")
#'
#' print(las)
#' print(header(las))
#'
#' x <- 1:30
#'
#' las <- add_attribute(las, x, "mydata")
#' print(las)        # The las object has a new attribute called "mydata"
#' print(header(las)) # But the header has not been updated. This new data will not be written
#'
#' las <- add_lasattribute(las, x, "mydata2", "A new data")
#' print(las)        # The las object has a new attribute called "mydata2"
#' print(header(las)) # The header has been updated. This new data will be written
#'
#' # Optionally if the data is already in the LAS object you can update the header skipping the
#' # parameter x
#' las <- add_attribute(las, x, "newattr")
#' las <- add_lasattribute(las, name = "newattr", desc = "Amplitude")
#' print(header(las))
#'
#' # Remove an extra bytes attribute
#' las <- remove_lasattribute(las, "mydata2")
#' print(las)
#' print(header(las))
#'
#' las <- remove_lasattribute(las, "mydata")
#' print(las)
#' print(header(las))
NULL

#' @export
#' @rdname add_attribute
add_attribute = function(las, x, name)
{
  stopifnotlas(las)
  assert_is_a_string(name)
  stopif_forbidden_name(name)

  las@data[[name]] <- x
  return(las)
}

#' @export
#' @rdname add_attribute
add_lasattribute = function(las, x, name, desc)
{
  stopifnotlas(las)
  assert_is_a_string(name)
  assert_is_a_string(desc)
  stopif_forbidden_name(name)

  if (missing(x))
  {
    if (!name %in% names(las))
      stop(glue::glue("{name} is not an attribute of the LAS object."), call. = FALSE)

    x <- las@data[[name]]

    if (!is.numeric(x))
      stop(glue::glue("'{name}' must be numeric. LAS format specifications do not allow storing of '{class(las@data[[name]])}' extra bytes."), call. = FALSE)
  }
  else
  {
    assert_is_vector(x)

    if (!is.numeric(x))
      stop(glue::glue("'x' must be numeric. LAS format specifications do not allow storing of '{class(x)}' extra bytes."), call. = FALSE)

    las <- add_attribute(las, x, name)
  }

  header     <- as.list(las@header)
  new_header <- rlas::header_add_extrabytes(header, x, name, desc)
  new_header <- LASheader(new_header)
  las@header <- new_header
  return(las)
}

#' @export
#' @rdname add_attribute
add_lasattribute_manual = function(las, x, name, desc, type, offset = NULL, scale = NULL, NA_value = NULL)
{
  stopifnotlas(las)
  assert_is_a_string(name)
  assert_is_a_string(desc)
  assert_is_a_string(type)
  stopif_forbidden_name(name)

  types = c("uchar", "char", "ushort", "short", "uint", "int", "uint64", "int64", "float", "double")
  type = match.arg(type, types)
  type = which(type == types)

  if (missing(x))
  {
    if (!name %in% names(las))
      stop(glue::glue("{name} is not an attribute of the LAS object."), call. = FALSE)

    x <- las@data[[name]]

    if (!is.numeric(x))
      stop(glue::glue("'{name}' must be numeric. LAS format specifications do not allow storing of '{class(las@data[[name]])}' extra bytes."), call. = FALSE)
  }
  else
  {
    assert_is_vector(x)

    if (!is.numeric(x))
      stop(glue::glue("'x' must be numeric. LAS format specifications do not allow storing of '{class(x)}' extra bytes."), call. = FALSE)

    las <- add_attribute(las, x, name)
  }

  header     <- as.list(las@header)

  new_header <- rlas::header_add_extrabytes_manual(header, name, desc, type, offset, scale, NULL, NULL, NA_value)
  new_header <- LASheader(new_header)
  las@header <- new_header
  return(las)
}

#' @export
#' @rdname add_attribute
add_lasrgb <- function(las, R, G, B)
{
  stopifnotlas(las)
  stopifnot(is.integer(R), is.integer(G), is.integer(B))
  assert_are_same_length(R, G)
  assert_are_same_length(R, B)
  assert_is_of_length(R, npoints(las))

  maxr <- max(R, na.rm = TRUE)
  maxg <- max(G, na.rm = TRUE)
  maxb <- max(B, na.rm = TRUE)

  scale <- 1L
  if (maxr <= 255 & maxg <= 255 & maxb <= 255)
    scale <- 257L

  las@data$R <- R*scale
  las@data$G <- G*scale
  las@data$B <- B*scale

  format <- las@header@PHB[["Point Data Format ID"]]

  if (format %in% c(2,3,8))
  {
    # nothing to do
  }
  else if ("NIR" %in% names(las))
  {
    format <- 8L
  }
  else if ("gpstime" %in% names(las))
  {
    format <- 3L
  }
  else
  {
    format <- 2L
  }

  las@header@PHB[["Point Data Format ID"]] <- format
  return(las)
}

#' @export
#' @rdname add_attribute
add_lasnir <- function(las, NIR)
{
  stopifnotlas(las)
  stopifnot(is.integer(NIR))
  assert_is_of_length(NIR, npoints(las))

  maxnir <- max(NIR, na.rm = TRUE)

  scale <- 1L
  if (maxnir <= 255)
    scale <- 257L

  las@data$NIR <- NIR*scale
  las@header@PHB[["Point Data Format ID"]] <- 8L
  return(las)
}

#' @export
#' @rdname add_attribute
remove_lasattribute = function(las, name)
{
  stopifnotlas(las)
  assert_is_a_string(name)
  stopif_forbidden_name(name)

  if (!name %in% names(las))
  {
     message(glue::glue("{name} is not an attribute of the LAS object."))
     return(las)
  }

  eb <- las@header@VLR$Extra_Bytes$`Extra Bytes Description`[[name]]

  if (is.null(eb))
  {
    #message(glue::glue("{name} is not an extrabytes attribute of the LAS object."))
    las@data[[name]] <- NULL
    return(las)
  }
  else
  {
    las@header@VLR$Extra_Bytes$`Extra Bytes Description`[[name]] <- NULL
    las@data[[name]] <- NULL

    if (length(las@header@VLR$Extra_Bytes$`Extra Bytes Description`) == 0)
      las@header@VLR$Extra_Bytes <- NULL

    return(las)
  }
}

# type = 0 : undocumented
# type = 1 : unsigned char
# type = 2 : char
# type = 3 : unsigned short
# type = 4 : short
# type = 5 : unsigned int
# type = 6 : int
# type = 7 : unsigned int64
# type = 8 : int64
# type = 9 : float  (try not to use)
# type = 10 : double (try not to use)

Try the lidR package in your browser

Any scripts or data that you put into this service are public.

lidR documentation built on Sept. 8, 2023, 5:10 p.m.