Skip to main content

LiDAR Reader — lidar_gbx

Since: lightweight lidar_gbx v0.5.2 · (light-only — no heavyweight tier)

Read LAS and LAZ point-cloud files into a Spark DataFrame. The lidar_gbx reader is a lightweight pure-Python DataSource; it requires no JAR and runs on Serverless, standard (shared), and ARM clusters.

To turn the loaded points into raster products — DSM binning, CHM, and isobands — see the LiDAR / Point-Cloud DSM functions.

For an end-to-end walkthrough — reading .laz, binning ground and all-return points into DTM and DSM rasters, and differencing them into a Canopy Height Model — see the Wireless Coverage example.

Lightweight only

lidar_gbx is a Python DataSource with no heavyweight GDAL/JVM counterpart. Register it with register(spark) before use.

Format name​

lidar_gbx

Supported files​

.las and .laz point clouds. Both compressed (LAZ) and uncompressed (LAS) files are supported. Pass a single file path or a directory; a directory is enumerated recursively and one partition per file is planned.

Modes​

lidar_gbx exposes two read modes, selected via the mode option (default: "points"):

ModeDescriptionOutput schema
metadataOne row per file — header-level statistics only; never loads points. Cheap on large archives.See Metadata schema
pointsOne row per point, chunk-iterated. Supports class/return/decimation filters.See Points schema

Options​

OptionDefaultApplies toDescription
mode"points"bothRead mode: "metadata" or "points".
classFilternonepointsComma-separated LAS classification codes to keep (e.g. "2" for ground, "2,3,4,5" for ground + low/medium/high vegetation). Points not matching the filter are dropped. Absent = keep all.
returnFilternonepointsInteger return number to keep (1-based: 1 = first return, 2 = second, …). Absent = keep all returns.
decimate"1"pointsKeep every N-th point (e.g. "10" = 10 % density). 1 = no decimation.
chunkSize"1000000"pointsPoints read per internal batch (controls per-task memory, not partition count).
dimensionsallpointsComma-separated subset of the base point columns to return (e.g. "x,y,z,classification"), emitted in schema order. The reader reads only the requested dimensions — plus any needed by classFilter/returnFilter — so projecting to what you need cuts per-point I/O and memory. Absent = all base columns; an unknown name errors.
Project columns for large point clouds

For big reads, request only the dimensions you need — e.g. .option("dimensions", "x,y,z,classification") — so the reader skips the rest at the source. That is cheaper than loading all base columns and pruning with .select(...) afterwards.

Register​

from databricks.labs.gbx.ds.register import register
register(spark)

Output schemas​

Metadata schema​

root
|-- path: string — full file path
|-- name: string — filename
|-- point_count: long — total points in the file
|-- x_min: double
|-- x_max: double
|-- y_min: double
|-- y_max: double
|-- z_min: double
|-- z_max: double
|-- crs: string — authority string (e.g. EPSG:27700) or WKT
|-- point_format: integer — LAS point data format ID (0-10)
|-- dimensions: array<string> — dimension names present in this file
|-- scale: array<double> — [x_scale, y_scale, z_scale]
|-- offset: array<double> — [x_offset, y_offset, z_offset]
|-- return_histogram: array<long> — point count per return number
|-- density: double — average point density (pts / m²)
|-- version: string — LAS spec version (e.g. "1.4")
|-- size: long — file size in bytes
|-- modificationTime: timestamp

Points schema​

root
|-- x: double — coordinate in the file's CRS
|-- y: double
|-- z: double
|-- intensity: integer
|-- return_number: integer
|-- number_of_returns: integer
|-- classification: integer — LAS classification code
|-- gps_time: double

Examples​

Metadata mode​

from databricks.labs.gbx.ds.register import register

register(spark)

meta = (
spark.read
.format("lidar_gbx")
.option("mode", "metadata")
.load("/Volumes/main/geobrix_samples/geobrix-examples/london/lidar/")
)
meta.select("name", "point_count", "z_min", "z_max", "crs").show()
+---------------------+-----------+------+------+----------+
|name |point_count|z_min |z_max |crs |
+---------------------+-----------+------+------+----------+
|tile_TQ3080_5m.laz |2183440 |2.14 |118.9 |EPSG:27700|
+---------------------+-----------+------+------+----------+

Points mode — ground returns only (classFilter + returnFilter)​

register(spark)

ground = (
spark.read
.format("lidar_gbx")
.option("mode", "points")
.option("classFilter", "2") # LAS class 2 = ground
.option("returnFilter", "1") # first return only
.load("/Volumes/main/geobrix_samples/geobrix-examples/london/lidar/")
)
ground.select("x", "y", "z").show(5)

Building a DSM tile via gbx_rst_binpoints_agg​

Combine lidar_gbx with the gbx_rst_binpoints_agg aggregator to grid ground or first-return points into a Digital Surface Model:

Full-density LiDAR? Use the bounded rx.bin_points_tiled

rst_binpoints_agg realizes a group's points in the worker, so a full-resolution tile of millions of returns can exhaust RAM. For that, bin with rx.bin_points_tiled(df, x, y, z, by=[…], xmin=…, …, width, height, srid, stat) — a two-stage native aggregation whose memory is the raster grid (W×H), not the point count. Same output for max/min/mean/count. See RasterX › Memory & scale and Performance.

from pyspark.sql import functions as F
from databricks.labs.gbx.pyrx import functions as prx
from databricks.labs.gbx.ds.register import register

register(spark)
prx.register(spark)

pts = (
spark.read
.format("lidar_gbx")
.option("classFilter", "2") # ground class
.load("/Volumes/main/geobrix_samples/geobrix-examples/london/lidar/")
)

dsm_bytes = pts.groupBy(F.lit(1).alias("tile_id")).agg(
prx.rst_binpoints_agg(
"x", "y", "z",
F.lit(530000.0), F.lit(180000.0), # xmin, ymin (BNG)
F.lit(532000.0), F.lit(182000.0), # xmax, ymax
F.lit(200), F.lit(200), # 200×200 px (10 m cells)
F.lit(27700), # EPSG:27700
F.lit("max"),
).alias("dsm_bytes")
)

dsm = dsm_bytes.select(
prx.rst_fromcontent("dsm_bytes", F.lit("GTiff")).alias("dsm")
)

Scale, memory, and robustness​

lidar_gbx is built to process large point clouds — hundreds of millions of returns — on Serverless:

  • Columnar reads. Points mode reads each file in vectorized chunks and hands Spark Arrow record batches — never one Python object per point — so a full-resolution read (decimate=1) avoids per-row conversion overhead that would otherwise stall the session. Metadata mode is columnar too.
  • Bounded per-task memory. Points are streamed chunkSize at a time (default 1000000), so peak memory per task is a single batch — independent of the total file size. Lower chunkSize for very large files or tighter memory; raise it to trade memory for throughput.
  • Filters and decimation apply in the reader, vectorized, before points reach Spark, so classFilter / returnFilter / decimate cut shuffle and downstream cost. decimate reads every point but emits every N-th — use it for a fast coarse pass, then drop to decimate=1 for the full-resolution product.
  • Bad nodes are skipped, not fatal. A 0-byte or unreadable .las/.laz (for example an empty Entwine Point Tile node from an interrupted download) is skipped with a warning, so one corrupt file cannot fail a distributed read over a whole archive.
  • Parallelism is per file — one InputPartition per file. A directory of many tiles parallelizes naturally across executors; a handful of very large files limits parallelism to the file count, so split or tile upstream if you need more executors engaged.
  • Spark Connect–safe. The reader makes no _jvm / sparkContext / .rdd calls and is session-free, so it runs unchanged on Serverless, standard, and ARM clusters.
Coordinate reference systems

The reader extracts the CRS from the LAS/LAZ header using parse_crs() and stores it as a canonical string in the crs output column (e.g. "EPSG:27700", "ESRI:54008", or WKT for authority-less CRSes). The srid column, where present, holds the numeric SRID — None when the CRS has no integer authority code. ESRI and OGC authorities are preserved exactly as read from the header. See Coordinate reference systems for the SRID-vs-CRS-string model.

Next Steps​