lidar
stable2D lidar point cloud utilities for coordinate conversion, filtering, statistics, merging, downsampling, and generating simulated circular scans.
use plugin lidar::{polar_to_cartesian, cartesian_to_polar, filter_by_distance, …} Functions (13)
- polar_to_cartesian Convert polar coords to Cartesian {x, y}
- cartesian_to_polar Convert Cartesian coords to polar
- filter_by_distance Keep points within a distance range
- filter_by_angle Keep points within an angle range
- average_distance Compute mean distance of all points
- scan_stats Compute min, max, avg, centroid of a scan
- merge_scans Combine two point arrays into one
- downsample Keep every Nth point
- point_count Count points in a scan
- nearest_point Find the closest point to a position
- distance_between Euclidean distance between two Cartesian points
- sort_by_angle Sort points by angle ascending
- create_scan Generate a uniform circular scan
Overview
lidar is a dependency-free toolkit for working with 2D lidar scans as plain
point clouds. A scan is just an array of point tables, and every point is
accepted in either polar form (angle_deg, distance) or Cartesian form
(x, y) — the functions normalize between the two automatically, so you can
mix sources without converting first. There are no opaque handles or sensor
sessions: data flows in and out as ordinary tables you can store, slice, and
pass around freely.
The mental model is a pipeline: build or capture a scan (create_scan),
narrow it with the filter_* and downsample helpers, reshape it with
merge_scans and sort_by_angle, then inspect it with scan_stats,
average_distance, nearest_point, or the coordinate-conversion utilities.
Distances are unitless — pick a unit (millimeters are typical) and stay
consistent across every call.
Common patterns
Generate a scan, narrow it to a region of interest, and summarize it:
use plugin lidar::{create_scan, filter_by_angle, scan_stats}
let scan = create_scan(360, 1000.0)
let front = filter_by_angle(scan, -45.0, 45.0)
let stats = scan_stats(front)
print("front arc: {stats["count"]} points, avg {stats["avg_dist"]}")
Fuse two sensors, drop noise outside a usable distance band, and downsample:
use plugin lidar::{create_scan, merge_scans, filter_by_distance, downsample}
let left = create_scan(180, 800.0)
let right = create_scan(180, 1200.0)
let merged = merge_scans(left, right)
let clean = filter_by_distance(merged, 200.0, 2000.0)
let sparse = downsample(clean, 2)
print("usable points: {point_count(sparse)}")
Sort a scan by angle, then find the obstacle closest to the sensor origin:
use plugin lidar::{create_scan, sort_by_angle, nearest_point}
let scan = sort_by_angle(create_scan(360, 1000.0))
let closest = nearest_point(scan, 0.0, 0.0)
print("nearest obstacle at {closest["angle_deg"]} deg, {closest["distance"]} away")
Convert polar coords to Cartesian {x, y}
Converts polar coordinates to Cartesian. Returns a table with x and y fields. Angle is in degrees, distance is in the same unit used for all subsequent operations (typically mm).
use plugin lidar::{polar_to_cartesian}
let pt = polar_to_cartesian(45.0, 1000.0)
print("x={pt["x"]} y={pt["y"]}")
Project a single beam reading onto the X axis to test how far an obstacle sits directly ahead:
use plugin lidar::{polar_to_cartesian}
let ahead = polar_to_cartesian(0.0, 1500.0)
print("forward clearance: {ahead["x"]}")
Convert Cartesian coords to polar
Converts Cartesian coordinates to polar. Returns a table with angle_deg and distance fields.
use plugin lidar::{cartesian_to_polar}
let polar = cartesian_to_polar(707.0, 707.0)
print("angle={polar["angle_deg"]} dist={polar["distance"]}")
Keep points within a distance range
Returns only the points whose distance is in the inclusive range [min, max]. Input points can be in polar (angle_deg, distance) or Cartesian (x, y) format.
use plugin lidar::{create_scan, filter_by_distance}
let scan = create_scan(360, 1000.0)
let nearby = filter_by_distance(scan, 0.0, 500.0)
print("Nearby points: {nearby}")
Reject both too-close sensor noise and far-field clutter by bounding the band from both sides, then count what survives:
use plugin lidar::{create_scan, filter_by_distance, point_count}
let scan = create_scan(360, 1000.0)
let usable = filter_by_distance(scan, 200.0, 800.0)
print("in-band points: {point_count(usable)}")
Keep points within an angle range
Returns only the points whose angle is in the inclusive range [min_deg, max_deg].
use plugin lidar::{create_scan, filter_by_angle}
let scan = create_scan(360, 1000.0)
let front = filter_by_angle(scan, -45.0, 45.0)
Compute mean distance of all points
Computes the mean distance of all points in the scan. Returns 0.0 for an empty scan.
use plugin lidar::{create_scan, average_distance}
let scan = create_scan(360, 1000.0)
let avg = average_distance(scan)
print("Average distance: {avg}")
Compute min, max, avg, centroid of a scan
Computes statistics over a point cloud. Returns a table with min_dist, max_dist, avg_dist, count, centroid_x, and centroid_y.
use plugin lidar::{create_scan, scan_stats}
let scan = create_scan(360, 1000.0)
let stats = scan_stats(scan)
print("Min: {stats["min_dist"]} Max: {stats["max_dist"]} Count: {stats["count"]}")
Use the centroid to estimate where the surrounding geometry is biased relative to the sensor:
use plugin lidar::{create_scan, filter_by_angle, scan_stats}
let arc = filter_by_angle(create_scan(360, 1000.0), 0.0, 90.0)
let s = scan_stats(arc)
print("centroid at ({s["centroid_x"]}, {s["centroid_y"]})")
Combine two point arrays into one
Combines two point arrays into a single array. Points from scan1 come first, followed by scan2. Useful for fusing data from multiple sensors.
use plugin lidar::{create_scan, merge_scans}
let front = create_scan(180, 800.0)
let back = create_scan(180, 1200.0)
let full = merge_scans(front, back)
Keep every Nth point
Keeps every factor-th point, reducing the scan resolution. A factor of 2 halves the point count; factor of 1 returns all points.
use plugin lidar::{create_scan, downsample}
let scan = create_scan(360, 1000.0)
let sparse = downsample(scan, 4)
print("Reduced to {sparse} points")
Count points in a scan
Returns the number of points in a scan table.
use plugin lidar::{create_scan, point_count}
let scan = create_scan(360, 1000.0)
print("Points: {point_count(scan)}")
Find the closest point to a position
Finds and returns the point in points closest to the Cartesian target position (x, y). Returns nil if the scan is empty. The returned table has angle_deg, distance, x, and y.
use plugin lidar::{create_scan, nearest_point}
let scan = create_scan(360, 1000.0)
let closest = nearest_point(scan, 0.0, 0.0)
print("Nearest at angle {closest["angle_deg"]}")
Query relative to an arbitrary point — for example, the closest return to a target the robot is tracking:
use plugin lidar::{create_scan, nearest_point}
let scan = create_scan(360, 1000.0)
let near_target = nearest_point(scan, 700.0, 700.0)
print("closest to target: dist {near_target["distance"]}")
Euclidean distance between two Cartesian points
Computes the Euclidean distance between two Cartesian points.
use plugin lidar::{distance_between}
let d = distance_between(0.0, 0.0, 3.0, 4.0)
print("Distance: {d}")
Sort points by angle ascending
Returns a new table of points sorted by angle_deg in ascending order. Useful before rendering or comparing scans.
use plugin lidar::{create_scan, filter_by_angle, sort_by_angle}
let scan = create_scan(360, 1000.0)
let sorted = sort_by_angle(scan)
Generate a uniform circular scan
Generates a simulated uniform circular scan with num_points points, each at distance units, evenly spaced around 360 degrees. Each point has angle_deg, distance, x, and y fields.
use plugin lidar::{create_scan, scan_stats}
let scan = create_scan(360, 1000.0)
let stats = scan_stats(scan)
print("Centroid: ({stats["centroid_x"]}, {stats["centroid_y"]})")
Build a low-resolution scan as a quick test fixture and confirm its size:
use plugin lidar::{create_scan, point_count}
let coarse = create_scan(8, 500.0)
print("generated {point_count(coarse)} evenly-spaced points")