Skip to content

lidar

stable

2D 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, …}
13 functions Game
/ filter jk navigate Esc clear
Functions (13)
  1. polar_to_cartesian Convert polar coords to Cartesian {x, y}
  2. cartesian_to_polar Convert Cartesian coords to polar
  3. filter_by_distance Keep points within a distance range
  4. filter_by_angle Keep points within an angle range
  5. average_distance Compute mean distance of all points
  6. scan_stats Compute min, max, avg, centroid of a scan
  7. merge_scans Combine two point arrays into one
  8. downsample Keep every Nth point
  9. point_count Count points in a scan
  10. nearest_point Find the closest point to a position
  11. distance_between Euclidean distance between two Cartesian points
  12. sort_by_angle Sort points by angle ascending
  13. 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")
enespt-br