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
- Coordinate reference system (CRS)
- Point data records and their attributes
- Reading & writing LAS/LAZ files
- Creating new LAS data
- Updating & filtering LAS data
LAS data and its attributes
PointClouds.IO.LAS
— TypeLAS
represents point-cloud data in the ASPRS “LAS” format, consisting of a collection of PointRecord
s 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 PointRecord
s 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
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.getcrs
— Methodgetcrs([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.
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 PointRecord
s. 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.PointRecord
— TypePointRecord{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.
- 3D position:
coordinates
- color information:
color_channels
(RGB for PDRFs 2/3/5/7, RGB + NIR for PDRFs 8/10) - information about the laser pulse return:
intensity
,return_number
,return_count
,waveform_packet
(PDRFs 4/5/9/10 only) - point record classification:
classification
,is_key_point
,is_overlap
(all PDRFs, based on classification for PDRF 0–5),is_synthetic
,is_withheld
- scanner/flight path information:
scan_angle
(higher resolution for PDRFs 6–10),is_left_to_right
,is_right_to_left
,is_edge_of_line
,scanner_channel
(PDRFs 6–10 only),source_id
,gps_time
(PDRFs 1 & 3–10) - custom attributes:
user_data
,extra_bytes
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.
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.coordinates
— Methodcoordinates(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
PointClouds.coordinates
— Methodcoordinates(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 systemcrs
instead of the current CRS of theLAS
.
PointClouds.coordinates
— Methodcoordinates(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.
Color attributes
PointClouds.IO.color_channels
— Functioncolor_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
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
Laser pulse return attributes
PointClouds.IO.intensity
— Functionintensity(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
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
PointClouds.IO.return_number
— Functionreturn_number([T], p::PointRecord)
return_number([T], points, inds = :)
Obtain which return of the laser pulse produced the point data record. The value is between 1 and 15 for the PDRFs 6–10, and between 1 and 5 for the legacy PDRFs 0–5.
See also: PointRecord
, intensity
, return_count
, waveform_packet
PointClouds.IO.return_count
— Functionreturn_count([T], p::PointRecord)
return_count([T], points, inds = :)
Obtain the total number of returns produced by the laser pulse. The value is between 1 and 15 for the PDRFs 6–10, and between 1 and 5 for the legacy PDRFs 0–5.
See also: PointRecord
, intensity
, return_number
, waveform_packet
PointClouds.IO.waveform_packet
— Functionwaveform_packet(p::PointRecord)
waveform_packet(points, inds = :)
Obtain the raw waveform packet of a point record for PDRFs 4/5/9/10, or missing
if the PDRFs does not include waveform packets. Note that PointClouds.jl currently has very limited functionality for handling waveform data.
See also: PointRecord
, intensity
, return_number
, return_count
Classification attributes
PointClouds.IO.classification
— Functionclassification([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
PointClouds.IO.is_key_point
— Functionis_key_point(p::PointRecord)
is_key_point(points, inds = :)
Check whether the point is marked as a key point that should not be removed when reducing the point density.
See also: PointRecord
, classification
, is_overlap
, is_synthetic
, is_withheld
PointClouds.IO.is_overlap
— Functionis_overlap(p::PointRecord)
is_overlap(points, inds = :)
Check whether the point is marked as overlap, e.g. of two flight paths. This is recorded as a classification (class 12) or as a separate flag (PDRFs 6–10 only) to allow for further classification of overlap points.
See also: PointRecord
, classification
, is_key_point
, is_synthetic
, is_withheld
PointClouds.IO.is_synthetic
— Functionis_synthetic(p::PointRecord)
is_synthetic(points, inds = :)
Check whether the point is marked as obtained through “synthetic” means (e.g. photogrammetry) rather than direct measurement with a laser pulse.
See also: PointRecord
, classification
, is_key_point
, is_overlap
, is_withheld
PointClouds.IO.is_withheld
— Functionis_withheld(p::PointRecord)
is_withheld(points, inds = :)
Check whether the point is marked as withheld/deleted and should be skipped in further processing.
See also: PointRecord
, classification
, is_key_point
, is_overlap
, is_synthetic
Scanner & flight path attributes
PointClouds.IO.scan_angle
— Functionscan_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
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
PointClouds.IO.is_left_to_right
— Functionis_left_to_right(p::PointRecord)
is_left_to_right(points, inds = :)
Check whether the scanner mirror was moving left to right relative to the in-track direction (increasing scan angle) when the point was recorded.
See also: PointRecord
, scan_angle
, is_right_to_left
, is_edge_of_line
PointClouds.IO.is_right_to_left
— Functionis_right_to_left(p::PointRecord)
is_right_to_left(points, inds = :)
Check whether the scanner mirror was moving right to left relative to the in-track direction (decreasing scan angle) when the point was recorded.
See also: PointRecord
, scan_angle
, is_left_to_right
, is_right_to_left
PointClouds.IO.is_edge_of_line
— Functionis_edge_of_line(p::PointRecord)
is_edge_of_line(points, inds = :)
Check whether the point was recorded at the edge of a scan line.
See also: PointRecord
, scan_angle
, is_left_to_right
, is_right_to_left
PointClouds.IO.scanner_channel
— Functionscanner_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
PointClouds.IO.source_id
— Functionsource_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
PointClouds.IO.gps_time
— Functiongps_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
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_data
— Functionuser_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
PointClouds.IO.extra_bytes
— Functionextra_bytes(p::PointRecord)
extra_bytes(points, inds = :)
Obtain the “extra bytes” of a point record as a tuple of UInt8
s. The meaning of these bytes may be described with a variable-length record.
See also: PointRecord
, user_data
Reading & writing LAS/LAZ files
To read a LAS/LAZ file, use Base.read
:
Base.read
— Methodread(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 totrue
, all points are read into memory right away. If set tofalse
, 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 totrue
), saved to a specific path (if set to a string), or not saved at all (if set tofalse
, not supported for compressed LAZ data). Defaults totrue
and only applies when the input is a URL.
Alternatively, you can also use the LAS
constructor:
PointClouds.IO.LAS
— MethodLAS(io)
LAS(filename)
LAS(url; cache = true)
Read LAS/LAZ data from a file, URL, or an arbitrary IO
input (not supported for compressed LAZ data). See Base.read(input, LAS)
for details.
To write point-cloud data to a new LAS/LAZ file, use Base.write
:
Base.write
— Methodwrite(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.
Creating new LAS data
PointClouds.IO.LAS
— MethodLAS([T,] points; kws...)
Create a new LAS
with points of type T <: PointRecord
. The points
can be passed as an AbstractVector
of PointRecord
s or as a NamedTuple
, where the keys correspond to the point attribute names and the values are AbstractVector
s. The keyword arguments set the corresponding fields of the LAS
type.
Updating & filtering LAS data
PointClouds.IO.update
— Methodupdate(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!
PointClouds.IO.update!
— Methodupdate!(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
Base.filter
— Methodfilter([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
orlon
: A tuple(xmin, xmax)
with the minimum and maximum value of the x-coordinate range that should be retained.y
orlat
: 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 theLAS
by default.
See also: filter!
Base.filter!
— Methodfilter!([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.