11 - LiDAR Point Cloud Simulation¶
This chapter describes how to use LESS to simulate airborne LiDAR (ALS) and terrestrial LiDAR (TLS) point cloud data.
LiDAR Simulation Principle¶
LiDAR (Light Detection and Ranging) acquires three-dimensional spatial information by emitting laser pulses and recording echo signals. The process of LESS simulation is:
- Calculate the emission position and direction of each laser pulse based on the scanner parameters and platform trajectory.
- Calculate the surface intersection point for Mesh, and calculate the laser propagation interval in the medium for
TurbidBoundary - Calculate pulse broadening, beam divergence, volume scattering, path extinction and multiple echoes
- Output the three-dimensional point cloud and retain the complete waveform.
Mesh and statistical vegetation¶
LiDAR uses the geometric and optical properties already in the scene, eliminating the need to create another set of canopies. explicit blade usage
Mesh; statistical canopy uses TurbidBoundary and OpticalVegetation:
tree.add_component(
"crown",
less.TurbidBoundary("tree.obj", group="crown"),
)
tree.set_property(
"crown",
less.OpticalVegetation(
leaf=less.Lambertian(
reflectance=0.35,
transmittance=0.15,
),
leaf_area_density=1.5,
leaf_angle_distribution="spherical",
),
)
The echoes from the statistical canopy are formed by backscattering from the leaves distributed continuously along the laser path. Ground and Mesh echoes in Attenuation also occurs when passing through the tree canopy. The transmit path of single station LiDAR coincides with the receive path, so the model uses Correlated propagation probabilities in the same gap, instead of treating uplink and downlink occlusion as two independent events. This processing corresponds to The occlusion hiding effect at zero phase angle does not require the user to set the empirical hotspot factor.
Mesh and TurbidBoundary can appear in the same scene or the same Object. Three backends
Use the same statistical canopy waveform definition.
Three components of the LiDAR system¶
LESS's LiDAR simulation consists of three components:
Scanner ——Scanner parameters¶
import less
scanner = less.Scanner(
beam_divergence=0.5, # Beam divergence angle (mrad)
pulse_rate=200_000, # Pulse repetition frequency (Hz)
scan_freq=80.0, # Scan frequency (Hz)
scan_angle=27.0, # Half scan angle (°)
wavelengths=[1064], # Laser wavelength (nm)
beam_samples=8, # Number of beam samples (analog beam divergence)
)
| Parameters | Meaning | ALS typical values | TLS typical values |
|---|---|---|---|
beam_divergence |
Beam divergence angle (mrad) | 0.3-0.5 | 0.1-0.3 |
pulse_rate |
Pulse frequency (Hz) | 100k-400k | - |
scan_freq |
Scan frequency (Hz) | 50-200 | - |
scan_angle |
Half scan angle (°) | 15-30 | - |
wavelengths |
Laser wavelength (nm) | 1064 | 1550 |
beam_samples |
Number of beam sampling points | 4-16 | 1-4 |
angular_resolution |
TLS angular resolution (°) | - | 0.05-0.2 |
Platform ——Platform type¶
# drone
platform = less.UAV(altitude=50.0, speed=5.0)
# manned aircraft
platform = less.Aircraft(altitude=1000.0, speed=60.0)
# Ground tripod
platform = less.Tripod(height=1.5)
# Vehicle mounted
platform = less.Vehicle(height=2.5, speed=5.0)
Legs/Positions ——Flight path or scan position¶
# ALS: Define flight route (start point → end point)
legs = [
less.Waypoint(-10, 25, active=True), # starting point
less.Waypoint(60, 25, active=True), # end
]
# TLS: Define scan sites
legs = [
less.ScanPosition(
x=25, y=25, # site location
azimuth_range=(0, 360), # Horizontal scan range
zenith_range=(5, 80), # Vertical scanning range
),
]
Complete example: Airborne LiDAR (ALS)¶
import less
import numpy as np
# ──Constructing a forest scene───────────────────────────────────────────
scene = less.Scene()
scene.size = 50.0
scene.repetitive = False # Neither the cycle nor the LiDAR workflow is currently publicly released
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())
tree = less.Object("ash", mesh=less.examples.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=40, car=10, cw=0.012, cm=0.008, N=1.5))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.08))
rng = np.random.RandomState(42)
positions = less.place.random(scene, n=60, min_dist=3.0, seed=42)
n = len(positions)
scene.add(tree, positions=positions,
scales=less.place.uniform_scale(n, 0.6, 1.5, seed=42),
rotations=less.place.random_rotation(n, seed=42))
sensor_als = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.5,
pulse_rate=200_000,
scan_freq=80.0,
scan_angle=27.0,
wavelengths=[1064],
beam_samples=8,
),
platform=less.UAV(altitude=50.0, speed=5.0),
legs=[
less.Waypoint(-10, 25, active=True),
less.Waypoint(60, 25, active=True),
],
range_resolution=0.15, # Distance resolution (m)
name="ALS LiDAR",
)
# ──Simulation──────────────────────────────────────────────────
print("Start ALS simulation...")
pointcloud = scene.simulate(sensor_als)
# ──Save point cloud ─────────────────────────────────────────────
pointcloud.save("11_als_pointcloud.ply")
print(f"Point cloud size: {pointcloud.num_points} points")
print(f"X range: {pointcloud.points[:, 0].min():.1f} ~ {pointcloud.points[:, 0].max():.1f} m")
print(f"Y range: {pointcloud.points[:, 1].min():.1f} ~ {pointcloud.points[:, 1].max():.1f} m")
print(f"Z range: {pointcloud.points[:, 2].min():.1f} ~ {pointcloud.points[:, 2].max():.1f} m")
Supporting script:
scripts/11_lidar.py
Ground LiDAR (TLS)¶
TLS performs a hemispheric scan from a fixed site to obtain a high-density close-range point cloud:
sensor_tls = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.2,
wavelengths=[1550],
beam_samples=1,
angular_resolution=0.1, # Angular resolution 0.1°
),
platform=less.Tripod(height=1.5),
legs=[
less.ScanPosition(
x=25, y=25,
azimuth_range=(0, 360), # All-round scan
zenith_range=(5, 80), # 5° ~ 80° (avoiding the zenith and ground)
),
],
range_resolution=0.02, # 2 cm distance resolution
name="TLS",
)
pointcloud_tls = scene.simulate(sensor_tls)
pointcloud_tls.save("11_tls_pointcloud.ply")
TLS vs ALS comparison¶
| Features | ALS (Airborne) | TLS (Terrestrial) |
|---|---|---|
| Perspective | Top-down | Bottom-up/horizontal |
| Coverage area | Large (route length) | Small (around the site) |
| Point Density | Medium (1-20 pt/m²) | High (>100 pt/m²) |
| Canopy penetration | Good | Severe shading |
| Typical applications | Large area mapping, CHM | Trunk measurement, structural analysis |
Point cloud data structure¶
The PointCloud object contains the following properties:
pc = scene.simulate(sensor_als)
# Coordinates (N × 3)
print(pc.points.shape) # (N, 3)
print(pc.points[:5]) # xyz of first 5 points
# Points
print(pc.num_points) # Return total number of points
# strength
print(pc.intensity.shape) # (N,)
# echo order
print(pc.return_number) # echo several times
print(pc.num_returns) # The total number of echoes of this pulse
Get the complete waveform¶
pc = scene.simulate(sensor_als, product="waveform")
waveform = pc.waveform["data"]
print(waveform.shape) # pulse × range bin × wavelength
print(pc.waveform["min_range"]) # The starting point of the first distance window, m
print(pc.waveform["range_resolution"]) # Distance sampling interval, m
print(pc.waveform["wavelengths"]) # Laser wavelength, nm
point_cloud and waveform use the same set of propagation calculations. Discrete echoes are generated from the merged surface with
Extracted from the body scattering waveform, therefore the canopy echo, understory echo and ground echo in the point cloud are consistent with the saved waveform
Be consistent.
\1 Multiple route scan
Real airborne LiDAR missions typically include multiple parallel routes:
# Three parallel routes, 20m apart
legs = []
for y_offset in [15, 25, 35]:
legs.extend([
less.Waypoint(-10, y_offset, active=True),
less.Waypoint(60, y_offset, active=True),
])
sensor_multi = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.5, pulse_rate=200_000,
scan_freq=80.0, scan_angle=20.0,
wavelengths=[1064], beam_samples=8,
),
platform=less.UAV(altitude=50.0, speed=5.0),
legs=legs,
range_resolution=0.15,
name="Multi-strip ALS",
)
Application scenarios¶
| Application | Required Data | Method |
|---|---|---|
| Canopy Height Model (CHM) | ALS Point Cloud | Highest Point - Ground Point |
| LAI inversion | ALS point cloud | Gap fraction method |
| Single wood segmentation | ALS/TLS | Watershed, deep learning |
| Diameter at breast height (DBH) | TLS | Circle fitting |
| Biomass Estimation | ALS | Height-Area Statistics |
Next step¶
Related API¶
less.Scanner、less.LiDARSurveyless.Aircraft、less.UAV、less.Tripod、less.Vehicleless.Waypoint、less.ScanPositionless.LiDARProduct.save()、less.LiDARProduct.waveforms