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.

By 12 min read

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

Ideal 2D lidar scan

Find the first surface along each beam

Thirteen rays sample the known scene from a sensor at (1, 2) m. Change the view, then inspect one reading and its coordinates. This is a geometric forward model with exact, opaque surfaces.

0
6
0

Beam angles run from -90° to 90° in 15° steps, measured counterclockwise from sensor +x. The body and sensor frames coincide in this example. All rays use one pose at one instant. Presets restore every control.

Scene and predicted scan in world coordinates

Ideal lidar scan with 13 finite returnsThe room spans zero to six meters on each axis. The sensor is at (1, 2) meters with heading 0 degrees. A vertical occluder lies at x equals three meters, from y equals one to three meters. Solid blue beams end at returned surface points. Dashed beams end at a range limit without a return marker. Orange identifies the selected beam when it has usable geometry. The selected result is Hit. Invalid beams have neither a ray nor an endpoint. The black arrow marks sensor forward. Both axes use the same meter scale.003366World x (m)World y (m)
Dark segments: known surfaces. Blue dots: measured endpoints. Solid rays stop at the first surface. Dashed rays show the maximum tested range with no endpoint dot. Orange highlights the selected beam, unless it is invalid. The black dot and arrow locate the sensor and its forward axis.

The selected ray first reaches occluder at 2.000000 m. Geometry behind this surface is occluded from that beam.

Selected result
Hit
Selected beam angle (rad)
0.000000
Selected world bearing (rad)
0.000000
Selected range (m)
2.000000
Hit surface
Occluder
Sensor endpoint x (m)
2.000000
Sensor endpoint y (m)
0.000000
World endpoint x (m)
3.000000
World endpoint y (m)
2.000000
Returns
13 of 13
No-return beams
0
Invalid beams
0

Usable range starts at 0.1 m. All surfaces in these presets lie farther away. A real driver can also report a detection too close to quantify; that state does not supply an exact endpoint. The scene and sensor pose here are known inputs, so this lab performs no mapping or localization.

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:

StateREP 117 conventionWhat you can locate
Finite valid returnRange within usable limitsA measured surface endpoint
No return within range+InfNo finite measured endpoint
Invalid or erroneous readingNaNNo usable range or free-space claim
Detection too close to quantify−InfAn 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

Continue with ICP's point-set alignment, or turn these rays into evidence about space with occupancy-grid mapping.