lidR v2.2.2


  1. We introduced a bug in v2.2.0 in the catalog processing engine. Empty chunks triggered and error i[1] is 1 which is out of range [1,nrow=0] internally. It now works again.

  2. Fix heap-buffer-overflow in lasrangecorrection() when throwing an error about invalid range.

lidR v2.2.1


  1. imager was used to drive the mcwatershed() algorithm. imager is an orphaned package that generated a warning on CRAN. Consequently mcwatershed() has been removed. In attempt to provide an informative message to users, the function still exists but generates an error. Anyway this method was weak and buggy and it was a good reason to remove it…

  2. In version 2.2.0 we missed to put the parameter r in point_metrics(). It is not yet supported but will be.


  1. LAScatalog processing engine:


  1. In the catalog processing engine, the graphical progression map is now able to plot the actual shape of the chunks. In the case of lasclip it means that discs and polygons are displayed instead of bounding boxes.

  2. Multi-layers VRTs are returned as RasterBrick instead of RasterStack for consistency with in memory raster that are returns as RasterBrick.

  3. grid_ functions now try to preserve the layer names when returning a VRT built from files written on disk. This works only with file formats that support to store layer name (e.g. not GTiff).

  4. There are now more than 900 unit tests for a coverage of 91%.


  1. Fix access to not mapped memory in one unit test (consequentless for users).

  2. In lasclip() the template XCENTER actually gave the Y coordinate. It is now the correct X coordinate of the center of the clipped region.

  3. In lasclip() the template YCENTER was not actually defined. It is now the correct Y coordinate of the center of the clipped region.

  4. Fix heap-buffer-overflow in lasrangecorrection(). The range was likely to be badly computed for points that have a gpstime later than the last sensor position

lidR v2.2.0 (Release date: 2020-01-06)


  1. LAScatalog processing engine:

    opt_output_file(ctg) <- "/home/user/data/norm/*_norm"  # {*} is valid as well
    # instead of
    opt_output_file(ctg) <- "/home/user/data/norm/{ORIGINALFILENAME}_norm"
    ctg = readLAScatalog("~/folder/LASfiles/")
    ctg@input_options$alt_dir = c("/home/Alice/data/", "/home/Bob/remote/project1/data/")
    ctg$newattr <- 1 # is now allowed
    ctg$GUID <- TRUE # is still forbidden
    #> Erreur : LAScatalog data read from standard files cannot be modified 
    ctg$processed <- TRUE
    ctg$processed[3:5] <- FALSE
  2. 3D rendering:

    plot(las, color = "Intensity")
    plot(las, color = "ReturnNumber")
    plot(las, color = "Classification")
  3. New function point_metrics() - very similar to grid_metrics() but at the point level. The ‘metrics’ family is now complete. cloud_metrics() computes user-defined metrics at the point cloud level. grid_metrics() and hexbin_metrics() compute user-defined metrics at the pixel level. voxel_metrics computes user-defined metrics at the voxel level. point_metrics() computes user-defined metrics at the point level.

  4. lasnormalize():

  5. New function sensor_tracking() to retrieve the position of the sensor in the sky.

  6. New function lasrangecorrection() to normalize intensity using the sensor position (range correction)

  7. catalog_select now also allows files to process to be flagged interactively:

    ctg <- catalog_select(ctg, method = "flag_processed")
    ctg <- catalog_select(ctg, method = "flag_unprocessed")
  8. grid_terrain()


  1. LAS() now rounds the values to 2 digits if no header is provided to fit with the default header automatically generated. This ensures that a perfectly valid LAS object is built out of external data. This change is made by reference, meaning that the original dataset is also rounded.

    pts <- data.frame(X = runif(10), Y = runif(10), Z = runif(10))
    las <- LAS(pts) # 'las' contains rounded values but 'pts' as well to avoid data copying
  2. lasmetrics() is deprecated. All las* functions return LAS objects except lasmetrics(). For consistency across the package lasmetrics() becomes cloud_metrics().

  3. grid_metrics3d() and grid_hexametrics() are deprecated. They are renamed voxel_metrics() and hexbin_metrics() for naming consistency.

  4. The example dataset Topography.laz is now larger and include attributes gpstime, PointSourceID and some classified lakes.


  1. Internally the package used a QuadTree as spatial index in versions <= 2.1.3. Spatial index has been rewritten and changed for a grid partition which is twice as fast as the former QuadTree. This change provides a significant boost (i.e. up to two times faster) to many algorithms of the package that rely on a spatial index. This includes lmf(), shp_*(), wing2015(), pmf(), lassmooth(), tin(), pitfree(). Benchmark on a Intel Core i7-5600U CPU @ 2.60GHz × 2.

    # 1 x 1 km, 13 pts/m², 13.1 million points
    tree_detection(las, lmf(3))
    #> v2.1: 1 core: 80s - 4 cores: 38s
    #> v2.2: 1 core: 38s - 4 cores: 20s
    # 500 x 500 m, 12 pt/m², 3.2 million points
    lassnags(las, wing2015(neigh_radii = nr, BBPRthrsh_mat = bbpr_th))
    #> v2.1: 1 core: 66s - 4 cores: 33s
    #> v2.2: 1 core: 43s - 4 cores: 21s
    # 250 x 250 m, 12 pt/m², 717.6 thousand points
    lasdetectshape(las3, shp_plane())
    #> v2.1 - 1 cores: 12s - 4 cores: 7s
    #> v2.2 - 1 cores:  6s - 4 cores: 3s
  2. Internally the Delaunay triangulation has been rewritten with boost instead of relying on the geometry package. The Delaunay triangulation and the rasterization of the Delaunay triangulation are now written in C++ providing an important speed-up (up to three times faster) to tin(), dsmtin() and pitfree(). However, for this to work, the point cloud must be converted to integers. This implies that the scale factors and offset in the header must be properly populated, which might not be the case if users have modified these values manually or if using a point cloud coming from a format other than las/laz. Benchmark on an Intel Core i7-5600U CPU @ 2.60GHz × 2.

    # 1.7 million ground points
    grid_terrain(las, 0.5, tin())
    #> v2.1: 1 core: 48s - 4 cores: 37s
    #> v2.2: 1 core: 22s - 4 cores: 20s
    # 560 thousand first returns (1.6 pts/m²)
    grid_canopy(las, res = 0.5, dsmtin())
    #> v2.1: 1 core: 8s - 4 cores: 7s
    #> v2.2: 1 core: 3s - 4 cores: 3s
    # 560 thousand first returns (1.6 pts/m²)
    grid_canopy(las, res = 0.5, pitfree(c(0,2,5,10,15), c(0, 1.5)))
    #> v2.1: 1 core: 30s - 4 cores: 28s
    #> v2.2: 1 core: 11s - 4 cores: 9s
  3. There are more than 100 new unit tests in testthat. The coverage increased from 68 to 87%.

  4. The vignette named Speed-up the computations on a LAScatalog gains a section about the possible additional speed-up using the argument select from readLAS().

  5. The vignette named LAScatalog formal class gains a section about partial processing.

  6. Harmonization and review of the sections ‘Supported processing options’ in the man pages.


  1. Several minor fixes in lascheck() for very improbable cases of LAS objects likely to have been modified manually.

  2. Fix colorization of boolean data when plotting an object of class lasmetrics3d (returned by voxel_metrics()) #289

  3. The LAScatalog engine now calls raster::writeRaster() with NAflag = -999999 because it seems that the default -Inf generates a lot of trouble on windows when building a virtual raster mosaic with gdalUtils::gdalbuildvrt().

  4. plot.LAS() better handles the case when coloring with an attribute that has only two values: NA and one other value.

  5. lasclip() was not actually able to retrieve the attributes of the Spatial*DataFrame or sf equivalent when using opt_output_file(ctg) <- "/dir/{PLOTID}".

  6. lasmergespatial() supports ‘on disk’ rasters #285 #306

  7. opt_stop_early() was not actually working as expected. The processing was aborted without logs. It now prevent the catalog processing engine to stop even when an error occurs.

  8. In tree_detection() if no tree is found (e.g. in a lake) the function crashed. It now returns an empty SpatialPointDataFrame.

  9. The argument keep_lowest in grid_terrain returned dummy output full of NAs because NAs have the precedence on actual numbers.

lidR v2.1.4 (Release date: 2019-10-15)


  1. grid_terrain() gains an argument full_raster = FALSE.

  2. lasnormalize() gains an argument ... to tune raster::extract() and use, for example, method = "bilinear".


  1. In lasground() if last_returns = TRUE and the LAS is not properly populated i.e. no last return, the classification was not actually computed. The expected behavior was to use all the points. This is now the case.

  2. lasclip() is now able to clip into a LAS objects using SpatialPoints or sf POINT. It previously worked only into LAScatalog objects.

  3. lasaddextrabyte_manual() was not actually working because the type was not converted to a numeric value according to the LAS specifications.

  4. Fix double precision floating point error in grid_* function in some specific cases. This fix affect also highest() and other raster-based algorithms #273.

  5. lasreoffset() now checks for integer overflow and throws an error in case of invalid user request #274.

  6. Tolerance for internal point_in_triangle() have been increased to fix double precision error in rasterization of a triangulation. This fixes some rare NAs in pitfree(), dsmtin() and tin().

  7. The NAs are now correctly interpreted when writing a GDAL virtual raster #283.

  8. Fix lasmergespatial() with ‘on disk’ rasters #285.

  9. Fix pitfree() with a single triangle case #288.


  1. pitfree() handles more errors and fails more nicely in some specific cases #286.

lidR v2.1.3 (Release date: 2019-09-10)


  1. New functions lasrescale() and lasreoffset() to modify the scale factors and the offsets. The functions update the header and recompute the coordinates to get the proper rounded values in accordance with the new header.

  2. readLAS() throw (again) warnings for invalid files such as files with invalid scale factors, invalid bounding box, invalid attributes ReturnNumber and so on.


  1. readLAScatalog() is 60% faster

  2. The progress bar of the LAScatalog processing engine has been removed in non interactive sessions and replaced by regular but more informative prints. This allows to track the state of the computation with a stream redirection to a file when running a script remotely for example.

    R -f script.R &> log.txt &


  1. Fix an infinite loop in the knn search when k > number of points. This bug may affect lasdetectectshape(), wing2012() and other functions that rely on a knn search.

  2. Using remote futures now works for any function that supports a LAScatalog input. Previously remote evaluation of futures failed because of the presence of return() statement in the code future#333

    plan(remote, workers = "")
  3. lasclipCircle() behaves identically for LAS and LAScatalog object. It now returns the points that are strictly inside the circle. Previously for LAS objects it also returned the point belonging on the disc.

  4. The bounding box is updated after lastransform() #270

  5. The offsets are updated after lastransform() to prevent integer overflow when writing the point cloud in .las files #272

  6. Removed deprecated C++ functions std::bind2nd as requested by CRAN.


  1. All C++ source code has been reworked in a tidy framework to clean-up 4 years of mess. It is almost invisible for regular users but the size of the package has been reduced of several MB and many new tools will now be possible to build.

lidR v2.1.2 (Release date: 2019-08-07)


  1. Fix a serious issue of uninitialized values in an internal C++ function but this issue is consequentless for the package.

lidR v2.1.1 (Release date: 2019-08-06)


  1. #266 lasmetrics has now a dispatch to LAS and LAScluster cluster objects. It means that lasmetrics can be used with catalog_apply in some specific cases where it has a meaning (see also #266):

    opt_chunk_buffer(ctg) <- 0
    opt_chunk_size(ctg) <- 0
    opt_filter(ctg) <- "-keep_first"
    opt_output_files(new_ctg) <- ""
    output <- catalog_apply(new_ctg, lasmetrics, func = .stdmetrics)
    output <- data.table::rbindlist(output)


  1. lastrees() now uses S3 dispatcher system. When trying to use it with a LAScatalog object, user will have a standard R message to state that LAScatalog is not supported instead of an uninformative message that state that ‘no slot of name “header” for this object of class “LAScatalog”’

  2. Internal code has been modified to drastically reduce probability of name intersection in catalog_apply(). For example, the use of a function that have a parameter p in catalog_apply() failed because of partial matching between the true argument p and the internal argument processing_option.

  3. lasfilterdecimate() with algorithm highest() is now more than 20 times faster. lasfiltersurfacepoints(), being a proxy of this algorithm, had the same speed-up

  4. plot for LAS objects gained the pan capability.


  1. #267. A dummy character was introduced by mistake in a variable name breaking the automatic exportation of user object in grid_metrics when used with a parallelized plan (tree_metrics() was also affected).

lidR v2.1.0 (Release date: 2019-07-13)


Several algorithms are now natively parallelized at the C++ level with OpenMP. This has for consequences for speed-up of some computations by default but implies visible changes for users. For more details see help("lidR-parallelism"). The following only explains how to modify code to restore the exact former behavior.

In versions < 2.1.0 the catalog processing engine has R-based parallelism capabilities using the future package. The addition of C++-based parallelism introduced additional complexity. To prevent against nested parallelism and give the user the ability to use either R-based or C++-based parallelism (or a mix of the two), the function opt_cores() is no longer supported. If used it generates a message and does nothing. The strategy used to process the tiles in parallel must now be explicitly declared by users. This is anyway how it should have been designed from the beginning! For users, restoring the exact former behavior implies only one change.

In versions < 2.1.0 the following was correct:

ctg <- catalog("folder/")
opt_cores(ctg) <- 4L
hmean <- grid_metrics(ctg, mean(Z))

In versions >= 2.1.0 this must be explicitly declared with the future package:

ctg <- catalog("folder/")
hmean <- grid_metrics(ctg, mean(Z))


  1. readLAS():

  2. Coordinate Reference System:

    projection(las) <- projection(raster)
  3. LAScatalog processing engine:

    An error occurred when processing the chunk 190. Try to load this chunk with:
    chunk <- readRDS("/tmp/RtmpAlHUux/chunk190.rds")
    las <- readLAS(chunk)
  4. grid_metrics():

    hmean <- grid_metrics(las, ~mean(Z), 20, filter = ~ReturnNumber == 1)
  5. New functions lasdetectshape() for water and human-made structure detection with three algorithms shp_plane(), shp_hplane(), shp_line().

  6. plot():

  7. tree_hull():

    convhulls <- tree_hulls(las, func = ~list(imean = mean(Intensity)))
  8. Miscellaneous tools:

    las    <- readLAS("file.las", filter = "-keep_first")
    header <- readLASheader(file)
    ctg    <- catalog("folder/")
    npoints(las)    #> [1] 55756
    npoints(header) #> [1] 81590
    npoints(ctg)    #> [1] 1257691
    density(las)    #> [1] 1.0483
    density(header) #> [1] 1.5355
    density(ctg)    #> [1] 1.5123
  9. Several functions are natively parallelized at the C++ level with OpenMP. See help("lidR-parallelism") for more details.

  10. New function catalog_select for interactive tile selection.

  11. lasground have lost the argument last_returns for a more generic argument filter. Retro-compatibility as been preserved by interpreting adding an ellipsis.


  1. grid_metrics(), grid_metrics3d(), tree_metrics(), tree_hull(), grid_hexametrics() and lasmetrics() expect a formula as input. Users should not write grid_metrics(las, mean(Z)) but grid_metrics(las, ~mean(Z)). The first syntax is still valid, for now.

  2. The argument named field in tree_metrics() is now named attribute for consistency with all other functions.

  3. The documentation of supported options in tree_*() functions was incorrect and has been fixed.

  4. readLAScatalog() replaces catalog(). catalog() is soft-deprecated.


  1. #264 grid_terrain now filter degenerated ground points.

  2. #238 fix a floating point precision error in p2r algorithm.

  1. When reading a file that contains extrabytes attributes and these data are not loaded (e.g. readLAS(f, select = "xyzi")) the header is updated to remove the non-loaded extrabytes. This fixes the issue #234 and enables LAS objects to be written without updating the header manually.

lidR v2.0.3 (Release date: 2019-05-02)

lidR v2.0.2 (Release date: 2019-03-02)

lidR v2.0.1 (Release date: 2019-02-02)

lidR v2.0.0 (Release date: 2019-01-02)

Why versions > 2.0 are incompatible with versions 1.x.y?

The lidR package versions 1 were mainly built upon “personal R scripts” I wrote 3 years ago. These scripts were written for my own use at a time when the lidR package was much smaller (both in term of code and users). The lidR package became a relatively large framework built on top of an unstructured base so it became impossible to develop it further. Many features and functions were missing because the way lidR was built did not allow them to be written. The new release (lidR version 2) breaks the former code to build a more robust, more consistent and more scalable framework that is intended and expected to continue for years without the need to break anything more in the future.

Old binaries can still be found here for 6 months:

Overview of the main visible changes

lidR as a GIS tool

lidR versions 1 was not a GIS tool. For example, rasterization functions such as grid_metrics() or grid_canopy() returned a data.frame. Tree tops extraction with tree_detection() also returned a data.frame. Tree segmentation with lastrees() accepted RasterLayer or data.frame as input in a very inconsistent way. Moreover, the CRS of the point cloud was useless and never propagated to the outputs because outputs were not spatial objects.

lidR version 2 consistently uses Raster* and Spatial* objects everywhere. Rasterization functions such as grid_metrics() or grid_canopy() return Raster* objects. Tree tops extraction returns SpatialPointDataFrame objects. Tree segmentation methods accept SpatialPointDataFrame objects only in a consistent way across functions. The CRS of the point cloud is always propagated to the outputs. LAS objects are Spatial objects. LAScatalog objects are SpatialPolygonDataFrame objects. In short, lidR version 2 is now a GIS tool that is fully compatible with the R ecosystem.

No longer any update by reference

Several lidR functions used to update objects by reference. In lidR versions 1 the user wrote: lasnormalize(las) instead of las2 <- lasnormalize(las1). This used to make sense in R < 3.1 but now the gain is no longer as relevant because R makes shallow copies instead of deep copies.

To simplfy, let’s assume that we have a 1 GB data.frame that stores the point cloud. In R < 3.1 las2 was a copy of las1 i.e. las1 + las2 = 2GB . This is why we made functions that worked by reference that implied no copy at all. This was memory optimized but not common or traditional in R. The question of memory optimization is now less relevant since R >= 3.1. In the previous example las2 is no longer a deep copy of las1, but a shallow copy. Thus lidR now consistently uses the traditional syntax y <- f(x).

Algorithm dispatch

The frame of lidR versions 1 was designed at a time when there were fewer algorithms. The increasing number of algorithms led to inconsistent ways to dispatch algorithms. For example:

lidR version 2 comes with a flexible and scalable dispatch method that unifies all the former functions. For example, grid_canopy() is the only function to make a CHM. There is no longer the need for a second function grid_tincanopy(). grid_canopy() unifies the two functions by accepting as input an algorithm for a digital surface model:

chm = grid_canopy(las, res = 1, algo = pitfree())
chm = grid_canopy(las, res = 1, algo = p2r(0.2))

The same idea drives several other functions including lastrees, lassnags, tree_detection, grid_terrain, lasnormalize, and so on. Examples:

ttops = tree_detection(las, algo = lmf(5))
ttops = tree_detection(las, algo = lidRplugins::multichm(1,2))
lastrees(las, algo = li2012(1.5, 2))
lastrees(las, algo = watershed(chm))
lasnormalize(las, algo = tin())
lasnormalize(las, algo = knnidw(k = 10))

This allows lidR to be extended with new algorithms without any restriction either in lidR or even from third-party tools. Also, how lidR functions are used is now more consistent across the package.

LAScatalog processing engine

lidR versions 1 was designed to run algorithms on medium-sized point clouds loaded in memory but not to run algorithms over a set of files covering wide areas. In addition, lidR 1 had a poorly and inconsistently designed engine to process catalogs of las files. For example:

lidR version 2 comes with a powerful and scalable catalog processing engine. Almost all the lidR functions can be used seamlessly with either LAS or LAScatalog objects. The following chunks of code are now possible:

ctg = catalog("folfer/to/las/file")
opt_output_file(ctg) <- "folder/to/normalized/las/files/{ORIGINALFILENAME}_normalized"
new_ctg = lasnormalize(ctg, algo = tin())

Complete description of visible changes

LAS class

LAScatalog class



ctg = catalog(folder)
output_files(ctg) <- "path/to/a/file_{XCENTER}_{YCENTER}"
laz_compression(ctg) <- TRUE
new_ctg = lasclipCircle(ctg, xc,yc, r)





ctg  <- catalog(folder)
ttop <- tree_detection(ctg, lmf(5))


ctg <- catalog(folder)
metrics <- tree_metrics(ctg, list(`Mean I` = mean(Intensity)))








class lasmetrics






Example files


Coordinate reference system

New functions

Other changes that are not directly visible

lidR v1.6.1 (2018-08-21)


lidR v1.6.0 (2018-07-20)





lidR v1.5.1 (2018-06-14)


lidR v1.5.0 (2018-05-13)





lidR v1.4.2 (2018-04-19)



lidR v1.4.1 (2018-02-01)



lidR v1.4.0 (2018-01-24)





lidR v1.3.1 (Release date: 2017-09-20)


lidR v1.3.0 (Release date: 2017-09-16)

This version is dedicated to extending functions and processes to entire catalogs in a continuous way. Major changes are:





lidR v1.2.1 (Release date: 2017-06-12)




lidR v1.2.0 (Release date: 2017-03-26)




lidR v1.1.0 (Release date: 2017-02-05)




lidR v1.0.2 (Release date: 2016-12-31)

Third submission

lidR v1.0.1 (Release date: 2016-12-30)

Second submission - rejected

lidR v1.0.0 (Release date: 201-12-16)

First submission - rejected