Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upAbout modifying the coordinates of LAS objects #372
Comments
|
I understand your concerns. Most of your questions/suggestions were actually already addressed in version 3.1 (branch
In version 3.1 the function is now documented in
In 3.1 I exported internal functions named
No sure to understand what you meant here.
The function
The scales and offsets are read from the file. When building a LAS object with
You are not supposed to do that . Or to be correct you are not supposed to do that if you don't have a good understanding of the LAS specification. But you can do it anyway. New new tools given in 3.1 simplify the task (see the documentation of las$X = las$X - xshift
las$Y = las$Y - yshift
las_quantize(las)
las = las_update(las)
I will consider that. Feel free to fork the book to propose something. Not sure to do that in a close future. Hope that help. I may not have answered all your question. Feel free to ask more clarifications |
|
I thought about it and I think it worth it to implement an on-the-fly quantization + header update when using |
|
I pushed 2 commits in
|
|
@Jean-Romain Thank you very much for your exhaustive answer and the new functionality! I like the ideas of moving these functions to a common documentation page and overloading the coordinate assignment operator and rbind function. The examples in particular should help with modifying coordinates in the correct way. Is the data.table reference assignment also covered by this?: There are a few things that I noticed about the las_tools code:
Regarding the range check you mentioned: I haven't looked at storable_coordinate_range <- function(scale, offset, verbose = FALSE) {
assertthat::is.number(scale)
assertthat::is.number(offset)
# The specification formally allows negative scale values, right?
assertthat::assert_that(!dplyr::near(scale, 0))
if (scale < 0) warning(paste0(
"Try to use positive scale values! Negative scale values are allowed but ",
"don't have any benefit over positive ones while being a bit more ",
"difficult to understand in terms of their effect."
))
# LAS specification data type for coordinate values: signed long (4 bytes)
#
# The number of unique integers storable in 4 bytes without a sign is:
# 2^(4 * 8) = 2^32 = 4294967296
#
# With signed integers one bit is used for the sign so the number of unique
# integers storable in the remaining bits is:
# 2^(4 * 8 - 1) = 2^31 = 2147483648
#
# This means that we can store the numbers from 0 to 2147483648 - 1 with both
# a positive and a negative sign. The range of valid integer values is
# therefore:
# -2147483647 to 2147483647
#
# There are some standards that define one more negative value for that range
# (e.g. C++ ISO/ANSI) but the LAS specification explicitly mentions the
# C ISO/ANSO C99 standard which defines the range noted above.
#
# See also
# https://en.wikipedia.org/wiki/Integer_(computer_science)#Long_integer
storable_min <- -2147483647 * scale + offset
storable_max <- 2147483647 * scale + offset
if (verbose) {
num_fully_usable_decimal_digits <- -ceiling(log10(abs(scale)))
formatted_min <- format(storable_min,
justify = "none",
scientific = -2,
digits = 3,
nsmall = 2,
big.mark = ",",
drop0trailing = TRUE
)
formatted_max <- format(storable_max,
justify = "none",
scientific = -2,
digits = 3,
nsmall = 2,
big.mark = ",",
drop0trailing = TRUE
)
formatted_width <- format(storable_max - storable_min,
justify = "none",
scientific = TRUE,
digits = 1,
big.mark = ",",
drop0trailing = TRUE
)
cat(glue::glue("
The range of storable coordinates is ", crayon::green(
"[{formatted_min}, {formatted_max}] (width ~ {formatted_width}).
"),
crayon::italic(
" Move this range to higher or lower values by increasing or decreasing
the offset respectively.
"),
"
The current scale value({scale}) allows for ",
crayon::green(
if (num_fully_usable_decimal_digits > 0) {
"coordinate values with {num_fully_usable_decimal_digits} accurately
stored digits after the decimal point.
"
} else if (num_fully_usable_decimal_digits < 0) {
"coordinate values that are accurately
stored from the leftmost digit down to \\
{-num_fully_usable_decimal_digits} digits before the decimal point.
"
} else {
"accurately stored integer coordinates.
"
}),
crayon::italic(
" Get x more storable digits on the right by decreasing the scale value
by x orders of magnitude. However, this will also shrink the range of
storable coordinates by moving the range's upper and lower end x orders of
magnitude closer to the range center. Increase the scale value for the
reverse effect.
"),
"
When storing coordinate values or using certain algorithms that compute on
integers, ",
crayon::bold$red(
"any digits beyond the precision mentioned above might be lost!"
),
"
This is because the coordinate values are converted to integers by
processing them with the formula ",
crayon::italic("(coordinates - offset) / scale"),
"
and rounding the resulting decimal numbers to integers!
"
))
# In the code of las_reoffset I have seen that coordinates are truncated
# by calling as.integer(). Could it perhaps be better to round them
# instead?
}
return(c("min" = storable_min, "max" = storable_max))
}
test <- storable_coordinate_range(scale = 1, offset = 0, verbose = TRUE)
test <- storable_coordinate_range(scale = 0.01, offset = 0, verbose = TRUE)
test <- storable_coordinate_range(scale = 100, offset = 0, verbose = TRUE)
test <- storable_coordinate_range(scale = 0.01, offset = 33e6, verbose = TRUE)Thanks again for your consideration and effort! |
No, there is no way to fully forbid manual modification. Operator las@data[, X := newx]
las@data[, Y := newy]
las_quantize(las)
yes
yes but only for internal usage. This is a non documented feature.
las coordinates are quantized. You don't have all the available value. From 0 to 1 with a scale factor of 0.01 you can only record 0, 0.01, 0.02, 0.03, ... 0.98, 0.99, 1. Coordinates are quantized. My reference is the wikipedia page
This is indeed an error I made but that I fixed later. I missed this one.
Actually
This is good. I'll start from your code to add some useful utility features |
|
Okay, cool. I'm happy that I could be of some help. Also, now I understand the term "quantization" :) I won't close this issue myself because I'm not sure if you still want to add any commits for it, but I don't have anything to add here. |
|
I added a test of range library(lidR)
LASfile <- system.file("extdata", "MixedConifer.laz", package="lidR")
las = readLAS(LASfile)
z = las$Z
z[250] <- 123456789.01
las$Z <- z
#> Error: Trying to store values ranging in [0, 123456789.01] but storable range is [-21474836.47, 21474836.47]
las@data$Z <- z
las_check(las)
#>
#> Checking the data
#> - Checking coordinates... ✓
#> - Checking coordinates type... ✓
#> - Checking coordinates range...
#> ✗ Z coordinates range in [0, 123456789.01] but storable range is [-21474836.47, 21474836.47]
#> - Checking coordinates quantization...
#> ✗ 1 Z coordinates were not stored with a resolution compatible with scale factor 0.01 and offset 0
#> - Checking attributes type... ✓
#> [...]Created on 2020-09-28 by the reprex package (v0.3.0) |
Dear lidR developers,
I was recently trying to shift the coordinates of a LAS object (i.e. subtract 33e+9 from the X coordinates) and encountered some difficulties. I think I've managed to do it in the end and I hope I have understood everything correctly after reading some lidR code, the LAS specification, and this explanation on stackexchange. Nonetheless, because it was not as straight forward I want to suggest three things that maybe could help others who encounter the same problem:
Update the documentation of
las_reoffsetto be even more clear about the fact that the function doesn't change coordinates but just how the coordinates are stored.Rationale: When I was looking for a
lidRfunction to change my coordinate values, I encountered thelas_reoffsetfunction and because I didn't know how the offset values of LAS files work, I was wondering why the function wouldn't do what I wanted, i.e. "offset" my coordinates. I could imagine that as long as one doesn't know about how the offset and scale values work in LAS files they would not expect that there is a function for changing the storage mode of points but not for changing the points themselves. At least this was the case for me.Maybe the documentation of
las_reoffsetandlas_rescalecould be extended by a note or warning that goes something like:"The las_reoffset function does not offset (i.e shift) coordinate values but modifies how the coordinates are stored in a LAS file on disk. Read the LAS specification and this answer (permalink: https://gis.stackexchange.com/questions/360247/rescaling-and-reoffsetting-a-point-cloud-with-lidr/360253#360253) on StackExchange for details on how this works."
Add a section to the lidR book that explains how a user has to update the LAS header after changing coordinates, i.e. adjust scale and offset if necessary, update the bounding box in both the header and the bbox slot and anything else which might be necessary.
Even more convenient than such a section in the book would of course be a function that updates the header of a LAS object after it's coordinates have been changed by the user. Maybe something like a mix of
rlas::header_updateandrlas::header_createwhereoffset + c( -(2^32) / 2, 2^32 / 2 )and about how a scale value different from 1 changes the32to something lower or higher and in any case makes it necessary to round the coordinates (I hope I have understood this part correctly)rlas::header_update) and the@bboxslot (like in the internal functionlasupdateheader)rlas::header_create) or to values defined by the userOne important question would be, how the user is allowed to modify the LAS object's coordinates. Should it be allowed to attach more points to the existing data? Should the user only be allowed to change the existing coordinates?
I have patched together some pieces of code that might serve as a first inspiration for such a function but this is in no way meant as a working example!
Thank you for your consideration!
Leon