explainer
A 2D lidar scan model: ranges, rays, and visible surfaces
Turn lidar angles and ranges into points, find the nearest visible surface with ray casting, and distinguish a missing return from an invalid reading. Inspect occlusion and coordinate frames in an interactive room.
What you will learn
- Interpret a planar scan as indexed angles and range readings.
- Convert a valid range into sensor and world coordinates.
- Predict the nearest visible surface with a finite-segment ray model.
- Distinguish no return, an invalid reading, and a measured endpoint.
- Explain why a moving scanner needs per-ray timing and a motion model.
Before you start
A 2D lidar scan records range measurements along directions in a plane. A valid range and beam angle locate a surface point in the sensor frame. To place that point in a map, you also need the sensor's pose and the measurement time.
This lesson builds an ideal geometric model: cast a ray through a known scene, find its nearest surface, and apply the sensor's range limits. You can inspect each step in a small room with an optional occluder.
What a 2D lidar scan measures
Lidar uses laser light to measure distance. A direct time-of-flight instrument relates the light's round-trip delay Δt to range r through r = cΔt/2, where c is the speed of light. That is one measurement principle; lidar designs differ. Dellaert and Hutchinson's sensing chapter introduces ranging and geometric scan simulation.
We will start after the sensor has produced a range reading. The model uses thin rays and perfectly detectable, opaque line segments in one horizontal plane. It ignores beam width, material-dependent reflections, and measurement noise.
A planar scan does not describe every obstacle around a robot. An object entirely above or below the scan plane can remain absent from its data. Even inside that plane, a foreground surface can hide another surface.
Give each beam an angle and a frame
For uniformly spaced beams, index i determines the sensor-frame bearing:
αᵢ = α_min + i Δα
Our scan has 13 beams, indexed 0 through 12. They span −90° to 90° in 15° steps. Beam 6 points along sensor +x; positive angles turn counterclockwise toward sensor +y, with +z out of the plane.
The ROS 2 LaserScan definition specifies angles in radians and ranges in meters. Its zero bearing points along +x, and angles increase around +z. The lab uses degrees on sliders for readability, then converts them to radians for computation.
The scan's frame identifies where those angles and endpoints live. A sensor mount can have a translation and rotation relative to the robot body. Here we explicitly use an identity sensor-to-body transform: their origins and axes coincide.
Convert a valid range into a point
A finite, valid range r at bearing α gives the sensor-frame endpoint:
p_S = (r cos α, r sin α)
Let the sensor's world position be (xₛ, yₛ) and its heading be θ. Rotate the endpoint and add the sensor position:
p_W = (xₛ, yₛ) + R(θ)p_S
p_W = (xₛ + r cos(θ + α), yₛ + r sin(θ + α))
The coordinate-frames lesson explains the rotation and origin shift. For a real mount, compose the body pose with the fixed sensor extrinsic using homogeneous transformations.
These equations transform a measured point. They do not estimate the sensor pose. The lab supplies the pose exactly so you can isolate the scan geometry.
Find the nearest visible surface
The opposite calculation predicts a reading from a known pose and map. Write the world ray as p(t) = o + td, where o is the sensor position, d is a unit direction, and t ≥ 0 measures distance in meters. Represent a surface segment by endpoints a and b.
Define v = b − a and the planar cross product cross(u, v) = uₓvᵧ − uᵧvₓ. When cross(d, v) is nonzero, solving o + td = a + λv gives:
t = cross(a − o, v) / cross(d, v)
λ = cross(a − o, d) / cross(d, v)
Keep an intersection only when t ≥ 0 and 0 ≤ λ ≤ 1. Those conditions reject a surface behind the sensor and crossings beyond a finite segment's endpoints. Parallel rays need a separate check; collinear segments can overlap the ray.
Choose the smallest accepted distance across all segments. Apply range limits to that first surface. A surface too close to measure still blocks surfaces behind it in this opaque model, so discarding it must not expose a farther wall.
The lab includes segment endpoints and uses a small numerical tolerance around contacts. It implements the finite-segment version of the ray-intersection approach introduced in the sensing chapter.
Calculate an occluded return
Place the sensor at (1, 2) m, with heading θ = 0. The room spans 0 to 6 m on both world axes. Add a vertical occluder at x = 3 m, from y = 1 to 3 m.
For the forward beam α = 0, the ray is (1 + t, 2). It reaches the occluder at t = 2 m and the east wall at t = 5 m. The nearest surface produces the ideal range 2 m, sensor endpoint (2, 0) m, and world endpoint (3, 2) m.
Removing the occluder exposes the wall and changes the reading to 5 m. Reducing maximum range to 1.5 m produces no finite return. The point (2.5, 2) m marks where that limited ray ends in the drawing; it is not a measured wall point.
Now set θ = 30° and α = −30°. The world bearing is still 0°, so the world endpoint remains (3, 2) m. In sensor coordinates, the same hit becomes (2 cos 30°, −2 sin 30°) = (1.732051, −1) m.
Inspect the scan in the lab
Start with the default occluder and select the 0° beam. Choose Wall only to expose the east wall. The selected range changes from 2 to 5 m, while the other rays continue to report their own nearest surfaces.
Reset, then set sensor heading to 90°. The forward beam reaches the north wall at 4 m. Its sensor endpoint is (4, 0) m, while its world endpoint is (1, 6) m.
The Short range preset sets maximum range to 1.5 m. All 13 rays then have no return. Invalid selected beam removes one reading from the default scan, leaving 12 finite returns and one invalid reading.
The chart shows the entire known scene, including surfaces the scan cannot currently measure. Only the endpoint dots represent returned points. Dashed lines end at a range limit, and an invalid reading contributes no ray or endpoint.
Separate missing returns from invalid readings
A scan entry needs a meaning as well as a number. ROS REP 117 distinguishes three special distance states:
| State | REP 117 convention | What you can locate |
|---|---|---|
| Finite valid return | Range within usable limits | A measured surface endpoint |
| No return within range | +Inf | No finite measured endpoint |
| Invalid or erroneous reading | NaN | No usable range or free-space claim |
| Detection too close to quantify | −Inf | An unquantified close detection |
Check the sensor driver's documented behavior before interpreting its values. In particular, replacing missing readings with the maximum range creates artificial points. A finite return exactly at the upper limit can be valid; it differs from an explicit no-return state.
In our ideal scene, no return means the ray meets no segment inside the range limit. Real light–surface interactions need more care. SICK's glass-measurement guidance shows that glass can reduce returned energy and introduce reflections; it also describes selectable echo handling.
The lab's nearest opaque surface rule models one ideal return. It does not simulate transparent materials, multiple echoes, dust, or device-specific detection thresholds. Treating every missing real reading as proof of empty space would exceed this model.
Account for motion during a scan
The lab treats all beams as simultaneous and holds the sensor pose fixed. A scanning instrument can acquire different rays at different times. Under a uniform timing model, tᵢ = t₀ + i Δt_ray.
The LaserScan source definition assigns the header timestamp to the first ray. time_increment describes spacing between measurements, while scan_time describes time between scans. Those quantities have different roles.
If the sensor moves, use its pose at each ray's time: p_W,i = T_W_S(tᵢ)p_S,i, using homogeneous point coordinates. Expressing every point in a common reference frame through these time-dependent transforms is the basis of scan deskewing.
Deskewing needs a motion estimate and consistent timestamps. It cannot recover an unknown moving object's shape merely by correcting sensor motion. A single pose applied to a moving scan can bend or smear otherwise straight surfaces.
Connect the scan model to mapping and localization
The forward model answers: given this pose and this scene, what should the sensor read? Particle-filter localization can compare predicted observations under different pose hypotheses with the actual measurements. Measurement noise and map errors belong in that comparison.
Occupancy-grid mapping uses a different direction of inference: given pose and scan observations, update beliefs about space. A ray before a valid return and its hit endpoint provide different evidence. Occluded space behind a return remains unobserved by that beam.
ICP can align valid endpoint sets across scans. Before registration, preserve frame conventions and filter unusable readings. The scan model itself performs none of these estimation tasks; its room and sensor pose are known inputs.
Occlusion can leave scans with only partial overlap. The ICP failure experiments isolate cropped point coverage and repeated geometry so you can see how they affect the fit, even with valid point coordinates.
Reproduce the ray calculation in Python
This standard-library program computes nearest hits in the same room. The sensor stays away from every segment, so parallel rays cannot overlap a surface in these examples. The lab's general intersection helper also handles collinear overlap.
from math import cos, sin, radians
origin = (1.0, 2.0)
walls = [((6, 0), (6, 6)), ((6, 6), (0, 6)),
((0, 6), (0, 0)), ((0, 0), (6, 0))]
occluder = ((3, 1), (3, 3))
def cross(a, b):
return a[0] * b[1] - a[1] * b[0]
def scan_ray(heading=0, beam=0, maximum=6, blocked=True):
angle = radians(heading + beam)
direction = (cos(angle), sin(angle))
candidates = []
for a, b in walls + ([occluder] if blocked else []):
edge = (b[0] - a[0], b[1] - a[1])
offset = (a[0] - origin[0], a[1] - origin[1])
denominator = cross(direction, edge)
if abs(denominator) < 1e-12:
continue
distance = cross(offset, edge) / denominator
fraction = cross(offset, direction) / denominator
if distance >= 0 and 0 <= fraction <= 1:
candidates.append(distance)
first = min(candidates, default=float("inf"))
return first if 0.1 <= first <= maximum else None
print(f"Blocked forward: {scan_ray():.6f} m")
print(f"Wall-only forward: {scan_ray(blocked=False):.6f} m")
print(f"Heading 90 deg: {scan_ray(heading=90):.6f} m")
print(f"Beam 45 deg: {scan_ray(beam=45):.6f} m")
print(f"Short range has endpoint: {scan_ray(maximum=1.5) is not None}")
r = scan_ray(heading=30, beam=-30)
sx, sy = r * cos(radians(-30)), r * sin(radians(-30))
print(f"Angled sensor point: ({sx:.6f}, {sy:.6f}) m")
Expected output:
Blocked forward: 2.000000 m
Wall-only forward: 5.000000 m
Heading 90 deg: 4.000000 m
Beam 45 deg: 5.656854 m
Short range has endpoint: False
Angled sensor point: (1.732051, -1.000000) m
Try two scan-model checks
Exercise 1: Transform a measured endpoint. A sensor sits at world position (2, 1) m with heading 90°. A valid beam at sensor angle −30° reports 4 m. Find its sensor and world endpoints.
Check the endpoint transformation
The sensor point is (4 cos(−30°), 4 sin(−30°)) = (3.464102, −2) m. A 90° rotation maps (x, y) to (−y, x), giving (2, 3.464102) m before translation.
Add (2, 1) m to obtain the world point (4, 4.464102) m. Its distance from the sensor remains 4 m.
Exercise 2: Does the beam hit the occluder? Use the default room, sensor at (1, 2) m, and heading 0°. Select beam angle 30°. Decide which surface comes first, then classify the reading if maximum range is 4 m.
Check visibility and the range limit
At x = 3 m, this ray has y = 2 + 2 tan 30° = 3.154701 m. It passes above the occluder's upper endpoint at y = 3 m.
The east wall comes first at r = 5/cos 30° = 5.773503 m, at world point (6, 4.886751) m. With a 4 m limit, the reading is no return within range and has no measured endpoint. The range-limit point on the drawing is not an obstacle observation.
Run these message and frame ideas through a real mapper in the SLAM Toolbox tutorial. Its separate room fixture supplies LaserScan messages, repairs a missing mounting transform, and checks the resulting map.
Sources and further study
- Dellaert and Hutchinson, Sensing for Autonomous Vehicles: lidar ranging and geometric ray models.
- ROS 2 Jazzy LaserScan message source: frame, angle, range, and timing fields.
- ROS REP 117, Informational Distance Measurements: special distance values and their meanings.
- SICK, picoScan150: Looking through glass: a concrete example of reflections and echo selection beyond opaque-ray geometry.
Continue with ICP's point-set alignment, or turn these rays into evidence about space with occupancy-grid mapping.