Working with LAS/LAZ data

Point-cloud data is usually distributed in the ASPRS “LAS” format or in its compressed “LAZ” variant. PointClouds.jl reads and writes all current versions of these files (1.0 – 1.4) with close adherence to the specification. The coordinate reference system (CRS) information is also parsed and can be used to transform or filter the data.

LAS files contain the some metadata fields in the header plus a list of point data records. PointClouds.jl represents this data with the LAS type, which can load points and their attributes in a lazy manner to allow working with files that do not fit into memory. The data can be read from and written to a LAS/LAZ file, created from scratch, and updated and filtered in a lazy manner.

LAS data and its attributes

PointClouds.IO.LASType

LAS represents point-cloud data in the ASPRS “LAS” format, consisting of a collection of PointRecords as well as a number of global attributes describing the point-cloud data. Use Base.show to get a summary of the data, use indexing to access point records, use property access (e.g. las.project_id) to read global attributes, and use update to change global and per-point attributes.

Point data records

The PointRecords of a LAS can be accessed through indexing. Note that when loading LAS data from a file (e.g. with LAS(filename)), the point records are not loaded into memory by default. Instead, the data is loaded in a lazy manner once it is accessed, which allows working with files that do not fit into memory. The points field of LAS gives access to the internal representation of the point records but direct use is discouraged.

Point records are usually stored in one of 11 standardized point data record formats (PDRFs) ranging from PointRecord{0} to PointRecord{10}. While all PDRFs have many attributes (such as 3D coordinates and return intensity) in common, the PDRFs differ in the exact set of attributes they support and the precision at which the data is stored. As of LAS version 1.4, the PDRFs 0–5 are considered legacy formats and the newer PDRFs 6–10 are preferred.

Refer to the PointRecord help text for the list of functions that can be called on them to access their attributes. These accessor functions can also be called directly on the LAS with one or multiple indices to access point attributes.

Variable length records (VLRs)

The variable length records of a LAS file store information about the coordinate reference system (CRS), which can be accessed with the getcrs function. The raw VLR data can be accessed through the vlr field of LAS.

Global point cloud attributes

  • coord_scale::NTuple{3,Float64}
  • coord_offset::NTuple{3,Float64}
  • coord_min::NTuple{3,Float64}
  • coord_max::NTuple{3,Float64}
  • return_counts::Vector{UInt64}
  • version::Tuple{UInt8,UInt8}: The version of the LAS file format (1.0 to 1.4) in the form of a (major, minor) tuple.
  • source_id::UInt16
  • project_id::GUID
  • system_id::String
  • software_id::String
  • creation_date::Tuple{UInt16,UInt16}
  • has_adjusted_standard_gps_time::Bool
  • has_internal_waveform::Bool
  • has_external_waveform::Bool
  • has_synthetic_return_numbers::Bool
  • has_well_known_text::Bool
source
LAS Specification

Details about the LAS format can be found in the specification documents: 1.0, 1.1, 1.2, 1.3 R11, 1.4 R15 (PDFs, ongoing development on GitHub)

Coordinate reference system (CRS)

The getcrs function reads the CRS data from a LAS file. Note that functions such as coordinates, filter, and the PointCloud constructor can also make use of this data without loading it explicitly.

PointClouds.getcrsMethod
getcrs([T], las::LAS)

Obtain the coordinate reference system (CRS) of a LAS, either in the format contained in the LAS or converted to the type T. The LAS format stores CRS data either in the binary format defined by the GeoTIFF standard or using the well-known text (WKT) representation defined in the OpenGIS® Coordinate Transformation Service Standard. The former is represented by the custom GeoKeys type while the latter is represented by a String. Currently, only the conversion from GeoKeys to a WKT String is implemented, but this allows passing CRS information obtained with getcrs(String, las) to other libraries such as Proj.jl. Note however that the conversion does a strict interpretation of the GeoKeys data and may not be able to convert incomplete/non-standard CRS data.

source

Point data records and their attributes

The LAS format stores point in one of eleven different Point Data Record Formats (PDRFs), which are numbered from 0 through 10 (as of LAS 1.4). Each PDRF includes a slightly different set attributes such as GPS time or color information. PDRFs 0–1 (defined since LAS 1.0), 2–3 (defined in LAS 1.2) and 4–5 (defined in LAS 1.3) are considered legacy formats that may be removed in future versions of the LAS standard. PDRFs 6–10 were introduced in LAS 1.4.

In PointClouds.jl, LAS points are represented as PointRecords. Use Base.eltype to obtain the point record type of a LAS. The point attribute functions listed below can be called on individual point records or on a LAS with one or multiple indices. Apart from the 3D position, the attributes can include information about the color of the point, about the laser pulse return, about the point record classification, about the scanner & flight path, as well as some custom data.

PointClouds.IO.PointRecordType
PointRecord{F,N}

A point data record in the point data record format (PDRF) F (usually between 0 and 10) with N extra bytes (usually zero), as defined in the ASPRS “LAS” format.

Point record attributes

The attributes of point records can be accessed with the functions below; directly accessing struct fields is discouraged. Note that some attributes are not all available for all PDRFs.

These functions all have a similar signature:

attribute([T], p::PointRecord)
attribute([T], points, inds = :)

The attribute can be read from a single point record p or from the collection points at one or multiple indices inds (: by default), returning a vector of attribute values in the latter case. By default, the values are returned as normalized Float64 or Int values such that the caller does not have to be aware of the details of how the data is stored in different PDRFs. The optional type argument T can be used to specify an integer type that the raw values are converted to (without normalization), or simply set to Integer to obtain the raw values.

source

3D position

The LAS format stores coordinates for each point as 32-bit integers. To obtain the actual coordinates, the integer coordinates have to be rescaled by a global offset and scale factor that is defined in the LAS header.

PointClouds.coordinatesMethod
coordinates(Integer, p::PointRecord)
coordinates(Integer, points, inds = :)

Obtain the “raw” x-, y-, and z-coordinate as a tuple of 32-bit integers. The meaning of these coordinates depends on a scaling and a coordinate reference system that are external to the point record.

The coordinates are read from a single point p or from the collection points at one or multiple indices inds, returning a vector of values in the latter case. The first argument also be used to specify a specific integer type for the coordinates.

See also: PointRecord

source
PointClouds.coordinatesMethod
coordinates(las::LAS, inds = :; crs)

Obtain the x-, y-, and z-coordinate of one or multiple point records as a tuple of floating-point numbers. The coordinate reference system (CRS) of the LAS is used unless a different crs is specified. To obtain coordinates of multiple points, pass a range of indices or : (default) as the index argument.

Keywords

  • crs: Transform the coordinates to a new coordinate reference system crs instead of the current CRS of the LAS.

See also: LAS, getcrs

source
PointClouds.coordinatesMethod
coordinates(Function, las::LAS; crs)

Create a function that takes a PointRecord as its argument and returns the rescaled coordinates of that point; in the coordinate reference system (CRS) of las unless a different crs is specified.

See also: LAS, getcrs

source

Color attributes

PointClouds.IO.color_channelsFunction
color_channels(p::PointRecord)
color_channels(points, inds = :)

Obtain the intensity associated with each color channel as a tuple of values normalized to the range from 0 to 1. For PDRFs 2, 3, 5, and 7, this returns three values that correspond to the red, green, and blue channel whereas PDRFs 8 and 10 include a fourth value for the near infrared channel. For PDRFs that do not include color information missing is returned.

See also: PointRecord, intensity

source
color_channels(Integer, p::PointRecord)
color_channels(Integer, points, inds = :)

Obtain the “raw” intensity associated with each color channel as a tuple of unsigned 16-bit integers. For PDRFs 2, 3, 5, and 7, this returns three values that correspond to the red, green, and blue channel whereas PDRFs 8 and 10 include a fourth value for the near infrared channel. Values should be normalized to cover the range 0 to 65535. For PDRFs that do not include color information missing is returned.

See also: PointRecord, intensity

source

Laser pulse return attributes

PointClouds.IO.intensityFunction
intensity(p::PointRecord)
intensity(points, inds = :)

Obtain the intensity of the pulse return, normalized such that the dynamic range of the sensor is represented by the range from 0 to 1.

See also: PointRecord, color_channels, return_number, return_count, waveform_packet

source
intensity(Integer, p::PointRecord)
intensity(Integer, points, inds = :)

Obtain the “raw” intensity of a point record as a unsigned 16-bit integer. Values should be normalized such that the dynamic range of the sensor corresponds to the range from 0 to 65535.

See also: PointRecord, color_channels, return_number, return_count, waveform_packet

source

Classification attributes

PointClouds.IO.classificationFunction
classification([T], p::PointRecord)
classification([T], points, inds = :)

Obtain the class that has been assigned to the point data record, as a UInt8 or the integer type T, if specified. Values in the range 0–63 either correspond to an ASPRS standard point class or are reserved for future standardization, whereas the meaning of values in the range 64–255 is user definable.

The ASPRS standard point classes are created/never classified (0), unclassified (1), ground (2), low vegetation (3), medium vegetation (4), high vegetation (5), building (6), low point/noise (7), water (9), rail (10), road surface (11), wire-guard/shield (13), wire-conductor/phase (14), transmission tower (15), wire-structure connector (16), bridge deck (17), high noise (18), overhead structure (19), ignored ground (20), snow (21), and temporal exclusion (22).

See also: PointRecord, is_key_point, is_overlap, is_synthetic, is_withheld

source

Scanner & flight path attributes

PointClouds.IO.scan_angleFunction
scan_angle(p::PointRecord)
scan_angle(points, inds = :)

Obtain the angle (in degrees) of the laser beam that scanned the point. The value is defined as the angle relative to the nadir (i.e. corrected for airplane roll), with 0° pointing straight down, negative values towards the left, and positive values towards the right of the flight path. The values range from −180° to +180° in increments of 0.006° for the PDRFs 6–10, and from −90° to +90° in increments of 1° for the legacy PDRFs 0–5.

See also: PointRecord, is_left_to_right, is_right_to_left, is_edge_of_line

source
scan_angle(Integer, p::PointRecord)
scan_angle(Integer, points, inds = :)

Obtain the “raw” scan angle of a point record as a signed 8- or 16-bit integer, depending on the PDRF. The values correspond to increments of 0.006° for the PDRFs 6–10, and increments of 1° for the legacy PDRFs 0–5.

See also: PointRecord, is_left_to_right, is_right_to_left, is_edge_of_line

source
PointClouds.IO.scanner_channelFunction
scanner_channel([T], p::PointRecord)
scanner_channel([T], points, inds = :)

Obtain the channel/scanner head of a multi-channel system, as an integer between 0 and 3. This is only supported for the PDRFs 6–10 and returns missing for the legacy PDRFs 0–5.

See also: PointRecord

source
PointClouds.IO.source_idFunction
source_id([T], p::PointRecord)
source_id([T], points, inds = :)

Obtain the source ID of a point record, which is defined as an integer between 0 and 65535. This may be used to group points that were recorded in a uniform manner, e.g. during the same flight line.

See also: PointRecord

source
PointClouds.IO.gps_timeFunction
gps_time(p::PointRecord)
gps_time(points, inds = :)

Obtain the GPS time at which a point was recorded for the PDRFs 1 & 3–10, or missing if the PDRF does not include a GPS time. Refer to the has_adjusted_gps_time field of LAS for the interpretation of the time value.

See also: PointRecord

source

Custom attributes

The LAS format has two mechanisms to add additional attributes to all points. The first one is the “user data” byte that is included in all PRDFs and may contain any data the producer of the file wishes to add. The second one is the “extra bytes” section that can be used to add an arbitrary number of bytes at the end of each point.

PointClouds.IO.user_dataFunction
user_data([T], p::PointRecord)
user_data([T], points, inds = :)

Obtain the “user data” of a point record, which is defined as an integer between 0 and 255. This data is included in all PDRFs but does not have any standardized meaning.

See also: PointRecord, extra_bytes

source
PointClouds.IO.extra_bytesFunction
extra_bytes(p::PointRecord)
extra_bytes(points, inds = :)

Obtain the “extra bytes” of a point record as a tuple of UInt8s. The meaning of these bytes may be described with a variable-length record.

See also: PointRecord, user_data

source

Reading & writing LAS/LAZ files

To read a LAS/LAZ file, use Base.read:

Base.readMethod
read(io::IO, LAS; kws...)
read(filename::AbstractString, LAS; kws...)
read(url::AbstractString, LAS; kws...)

Read LAS/LAZ data from a file, URL, or an arbitrary IO input (not supported for compressed LAZ data). URLs can be passed as a string starting with "http" or as a URI from the URIs/HTTP package.

Keywords

  • read_points: If set to :lazy (default), points are only read to memory when accessed, allowing processing of point clouds that do not fit into memory. If set to true, all points are read into memory right away. If set to false, points are not read at all and only header information is available.
  • cache: Whether the downloaded data should be saved as a temporary file (if set to true), saved to a specific path (if set to a string), or not saved at all (if set to false, not supported for compressed LAZ data). Defaults to true and only applies when the input is a URL.
source

Alternatively, you can also use the LAS constructor:

To write point-cloud data to a new LAS/LAZ file, use Base.write:

Base.writeMethod
write(io::IO, las::LAS; kws...)
write(filename::AbstractString, las::LAS; kws...)

Write point-cloud data to a new LAS file or an arbitrary IO output.

Keywords

  • format: Can be set to "las" for regular uncompressed LAS data, or to "laz" to compress the output with LASzip. By default, the format is derived from the file extension or set to "las" otherwise.
source

Creating new LAS data

PointClouds.IO.LASMethod
LAS([T,] points; kws...)

Create a new LAS with points of type T <: PointRecord. The points can be passed as an AbstractVector of PointRecords or as a NamedTuple, where the keys correspond to the point attribute names and the values are AbstractVectors. The keyword arguments set the corresponding fields of the LAS type.

source

Updating & filtering LAS data

PointClouds.IO.updateMethod
update(las::LAS, [attributes]; kws...)

Create a copy of a LAS that replaces point attributes with the attributes provided as a NamedTuple. The keyword arguments update the corresponding global attributes of the LAS.

See also: update!

source
PointClouds.IO.update!Method
update!(las::LAS, [attributes]; kws...)

Update a LAS by replacing point attributes with the attributes provided as a NamedTuple. This is only supported if the point records of the LAS have been loaded into memory.

The keyword arguments update the corresponding global attributes of the LAS, of which only coord_min, coord_max, and return_counts can be modified in-place.

See also: update

source
Base.filterMethod
filter([f::Function], las::LAS; kws...)

Create a copy of a LAS that excludes point which do not meet the specified filter criteria. Filtering can be done with a function f that returns false for each PointRecord that should be discarded or with the keyword arguments to filter by extent (but not both). Note that you can also indexing (and keepat! for in-place modification) to filter a point-cloud, especially in combination with logical indexing.

Keywords

  • x or lon: A tuple (xmin, xmax) with the minimum and maximum value of the x-coordinate range that should be retained.
  • y or lat: A tuple (ymin, ymax) with the minimum and maximum value of the y-coordinate range that should be retained.
  • z: A tuple (zmin, zmax) with the minimum and maximum value of the z-coordinate range that should be retained.
  • crs: The coordinate reference system in which the extent should be applied. Set to the CRS of the LAS by default.

See also: filter!

source
Base.filter!Method
filter!([f::Function], las::LAS; kws...)

Remove points that do not meet the specified filter criteria from a LAS. This is only supported if the point records of the LAS have been loaded into memory or if they have already been filtered. See filter for details.

source