Nothing
#' LAS utilities
#'
#' Tools to manipulate LAS objects maintaining compliance with
#' \href{https://www.asprs.org/wp-content/uploads/2019/07/LAS_1_4_r15.pdf}{ASPRS specification}
#'
#' In the specification of the LAS format the coordinates are expected to be given
#' with a certain precision e.g. 0.01 for a millimeter precision (or millifeet), meaning
#' that a file records e.g. 123.46 not 123.45678. Also, coordinates are stored as
#' integers. This is made possible with a scale and offset factor. For example,
#' 123.46 with an offset of 100 and a scale factor of 0.01 is actually stored as
#' (123.46 - 100)/0.01 = 2346. Storing 123.45678 with a scale factor of 0.01 and an offset
#' of 100 is invalid because it does not convert to an integer: (123.45678-100)/0.01
#' = 2345.678. Having an invalid LAS object may be critical in some lidR applications.
#' When writing into a LAS file, users will loose the extra precision without
#' warning and some algorithms in lidR use the integer conversion to make integer-based
#' computation and thus speed-up some algorithms and use less memory. Creation of an
#' invalid LAS object may cause problems and incorrect outputs.
#'
#' @param las An object of class LAS
#' @param x numeric. Coordinates vector
#' @param scale,offset scalar. scale and offset
#' @param xscale,yscale,zscale scalar. Can be missing if not relevant.
#' @param xoffset,yoffset,zoffset scalar. Can be missing if not relevant.
#' @param by_reference bool. Update the data in place without allocating new memory.
#' @param ... Unused.
#'
#' @examples
#' LASfile <- system.file("extdata", "example.laz", package="rlas")
#' las = readLAS(LASfile)
#'
#' # Manual modification of the coordinates (e.g. rotation, re-alignment, ...)
#' las@data$X <- las@data$X + 2/3
#' las@data$Y <- las@data$Y - 5/3
#'
#' # The point cloud is no longer valid
#' las_check(las)
#'
#' # It is important to fix that
#' las_quantize(las)
#'
#' # Now the file is almost valid
#' las_check(las)
#'
#' # Update the object to set up-to-date header data
#' las <- las_update(las)
#' las_check(las)
#'
#' # In practice the above code is not useful for regular users because the operators
#' # $<- already perform such operations on-the-fly. Thus the following
#' # syntax must be preferred and returns valid objects. Previous tools
#' # were only intended to be used in very specific cases.
#' las$X <- las$X + 2/3
#' las$Y <- las$Y - 5/3
#'
#' # Rescale and reoffset recompute the coordinates with
#' # new scales and offsets according to LAS specification
#' las <- las_rescale(las, xscale = 0.01, yscale = 0.01)
#' las <- las_reoffset(las, xoffset = 300000, yoffset = 5248000)
#' @rdname las_utilities
#' @name las_utilities
#' @family las utilities
NULL
#' @export
#' @rdname las_utilities
las_rescale = function(las, xscale, yscale, zscale)
{
xoffset <- las[["X offset"]]
yoffset <- las[["Y offset"]]
zoffset <- las[["Z offset"]]
if (!missing(xscale))
{
assert_is_a_number(xscale)
newX <- round((las@data[["X"]] - xoffset)/xscale) * xscale + xoffset
diff <- round(mean(abs(las@data[["X"]] - newX)), 4)
las@data[["X"]] <- newX
las@header@PHB[["X scale factor"]] <- xscale
message(glue::glue("X coordinates were moved by {diff} on average"))
}
if (!missing(yscale))
{
assert_is_a_number(yscale)
newY <- round((las@data[["Y"]] - yoffset)/yscale) * yscale + yoffset
diff <- round(mean(abs(las@data[["Y"]] - newY)), 4)
las@data[["Y"]] <- newY
las@header@PHB[["Y scale factor"]] <- yscale
message(glue::glue("Y coordinates were moved by {diff} on average"))
}
if (!missing(zscale))
{
assert_is_a_number(zscale)
newZ <- round((las@data[["Z"]] - zoffset)/zscale) * zscale + zoffset
diff <- round(mean(abs(las@data[["Z"]] - newZ)), 4)
las@data[["Z"]] <- newZ
las@header@PHB[["Z scale factor"]] <- zscale
message(glue::glue("Z coordinates were moved by {diff} on average"))
}
las <- lasupdateheader(las)
return(las)
}
#' @rdname las_utilities
#' @export
las_reoffset = function(las, xoffset, yoffset, zoffset)
{
xscale <- las[["X scale factor"]]
yscale <- las[["Y scale factor"]]
zscale <- las[["Z scale factor"]]
xrange <- c(las[["Min X"]], las[["Max X"]])
yrange <- c(las[["Min Y"]], las[["Max Y"]])
zrange <- c(las[["Min Z"]], las[["Max Z"]])
if (!missing(xoffset))
{
assert_is_a_number(xoffset)
newX <- suppressWarnings(as.integer(round((xrange - xoffset)/xscale)) * xscale + xoffset)
if (anyNA(newX)) stop("Incorrect xoffset: integer overflow.", call. = FALSE)
newX <- round((las@data[["X"]] - xoffset)/xscale) * xscale + xoffset
diff <- round(mean(abs(las@data[["X"]] - newX)), 4)
las@data[["X"]] <- newX
las@header@PHB[["X offset"]] <- xoffset
message(glue::glue("X coordinates were moved by {diff} on average"))
}
if (!missing(yoffset))
{
assert_is_a_number(yoffset)
newY <- suppressWarnings(as.integer(round((yrange - yoffset)/yscale)) * yscale + yoffset)
if (anyNA(newY)) stop("Incorrect yoffset: integer overflow.", call. = FALSE)
newY <- round((las@data[["Y"]] - yoffset)/yscale) * yscale + yoffset
diff <- round(mean(abs(las@data[["Y"]] - newY)), 4)
las@data[["Y"]] <- newY
las@header@PHB[["Y offset"]] <- yoffset
message(glue::glue("Y coordinates were moved by {diff} on average"))
}
if (!missing(zoffset))
{
assert_is_a_number(zoffset)
newZ <- suppressWarnings(as.integer(round((zrange - zoffset)/zscale)) * zscale + zoffset)
if (anyNA(newZ)) stop("Incorrect zoffset: integer overflow.", call. = FALSE)
newZ <- round((las@data[["Z"]] - zoffset)/zscale) * zscale + zoffset
diff <- round(mean(abs(las@data[["Z"]] - newZ)), 4)
las@data[["Z"]] <- newZ
las@header@PHB[["Z offset"]] <- zoffset
message(glue::glue("Z coordinates were moved by {diff} on average"))
}
las <- lasupdateheader(las)
return(las)
}
#' @export
#' @rdname las_utilities
las_quantize = function(las, by_reference = TRUE)
{
xscale <- las[["X scale factor"]]
yscale <- las[["Y scale factor"]]
zscale <- las[["Z scale factor"]]
xoffset <- las[["X offset"]]
yoffset <- las[["Y offset"]]
zoffset <- las[["Z offset"]]
if (isTRUE(by_reference))
{
quantize(las$X, xscale, xoffset)
quantize(las$Y, yscale, yoffset)
quantize(las$Z, zscale, zoffset)
return(invisible(las))
}
else
{
las@data[["X"]] <- quantize(las$X, xscale, xoffset, FALSE)
las@data[["Y"]] <- quantize(las$Y, yscale, yoffset, FALSE)
las@data[["Z"]] <- quantize(las$Z, zscale, zoffset, FALSE)
return(las)
}
}
#' @export
#' @rdname las_utilities
las_update = function(las)
{
stopifnotlas(las)
header <- as.list(las@header)
new_header <- rlas::header_update(header, las@data)
new_header <- LASheader(new_header)
las@header <- new_header
return(las)
}
#' @export
#' @rdname las_utilities
quantize = function(x, scale, offset, by_reference = TRUE, ...)
{
umin <- min(x)
umax <- max(x)
urange <- storable_coordinate_range(scale, offset)
if (umax > urange[2] | umin < urange[1])
stop("'x' contains unquantizable values out of the storable range.", call. = FALSE)
if (isTRUE(by_reference))
{
fast_quantization(x, scale, offset)
return(invisible(x))
}
else
{
y <- data.table::copy(x)
fast_quantization(y, scale, offset)
return(y)
}
}
#' @export
#' @rdname las_utilities
is.quantized = function(x, scale, offset, ...)
{
p <- list(...)
if (!is.null(p$sample))
{
n <- min(100L, length(x))
s <- as.integer(seq(1L, length(x), length.out = n))
x <- x[s]
}
return(fast_countunquantized(x, scale, offset) == 0L)
}
#' @export
#' @rdname las_utilities
count_not_quantized = fast_countunquantized
#' @export
#' @rdname las_utilities
storable_coordinate_range <- function(scale, offset) {
assert_is_a_number(scale)
assert_is_a_number(offset)
storable_min <- -2147483647 * scale + offset
storable_max <- 2147483647 * scale + offset
return(c("min" = storable_min, "max" = storable_max))
}
#' @export
#' @rdname las_utilities
header <- function(las)
{
return(las@header)
}
#' @export
#' @rdname las_utilities
payload <- function(las)
{
return(las@data)
}
#' @export
#' @rdname las_utilities
phb <- function(las)
{
if (!is(las, "LASheader"))
las <- header(las)
return(las@PHB)
}
#' @export
#' @rdname las_utilities
vlr <- function(las)
{
if (!is(las, "LASheader"))
las <- header(las)
return(las@VLR)
}
#' @export
#' @rdname las_utilities
evlr <- function(las)
{
if (!is(las, "LASheader"))
las <- header(las)
if (!methods::.hasSlot(las, "EVLR"))
return(NULL)
return(las@EVLR)
}
lasupdateheader = las_update
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.