DEV Community

Bibek Ghimire
Bibek Ghimire

Posted on

geomap-engine: fusing AR pose + object detections into a 2D map, in Rust

Every AR toolkit (ARKit, ARCore, RoomPlan, Polycam) bundles SLAM,
object detection, and a UI into one closed, heavyweight package. If you
just want the middle piece — turn a stream of {camera pose, object
detections}
into a clean, deduplicated 2D map of "here's a chair, here's
a door, here's where they are" — there's nothing small and embeddable
that does just that.

So I built geomap-engine: an open-source Rust crate that does exactly this one job.

What it does, what it doesn't

Feed it a Frame (camera pose + intrinsics + a list of 2D bounding-box
detections) and it maintains a SceneMap: one stable entry per real
object, not one per detection.

It explicitly does not:

  • run SLAM (bring your own ARKit/ARCore/ORB-SLAM3/whatever)
  • run object detection (bring your own model)
  • have any UI

It's the glue layer that sits between those and your app/robot/tool.

How it works

  1. Projection — a 2D bbox + camera pose + intrinsics becomes an estimated 2D world position, via ray-plane intersection with the ground plane (v0.1 assumes a known/fixed camera height).
  2. Association — nearest-neighbor match against existing tracked objects, by label + position proximity.
  3. Fusion — repeated observations merge into one object: running- average position, confidence combined as independent evidence so it grows toward 1 with repeated sightings instead of just averaging.
  4. Map maintenance — a single staleness rule handles pruning one-off noise, stale objects, and objects that moved, all at once.

Try it

use geomap_engine::Engine;
use geomap_engine::proto::{CameraIntrinsics, CameraPose, Detection, Frame};

let mut engine = Engine::new();

let frame = Frame {
    pose: Some(CameraPose {
        timestamp: 0.0,
        x: 0.0, y: 0.0, z: 1.5, // 1.5m camera height above the floor
        qx: 1.0, qy: 0.0, qz: 0.0, qw: 0.0, // looking straight down
    }),
    intrinsics: Some(CameraIntrinsics { fx: 500.0, fy: 500.0, cx: 320.0, cy: 240.0 }),
    detections: vec![Detection {
        label: "chair".into(),
        confidence: 0.8,
        bbox_x: 0.45, bbox_y: 0.45, bbox_w: 0.1, bbox_h: 0.1,
    }],
};

let scene_map = engine.ingest_frame(frame);
println!("{}", scene_map.to_geojson_json().unwrap());
Enter fullscreen mode Exit fullscreen mode

Output is GeoJSON-shaped (FeatureCollection of Point features), so
you can drop it straight into geopandas/matplotlib or any map viewer
without writing a converter.

Why Rust + Protobuf

Rust for portability and eventual FFI into iOS/Android. The Frame/
SceneMap schema is Protobuf, so any front-end — Swift, Kotlin, a
Python test harness — can feed it language-agnostically without caring
what the engine is written in.

Status

v0.1 is implemented and tested — 11 tests, cargo build / cargo test
/ cargo clippy all clean, CI running on every push. It's tested against
both a hand-written fixture and a real camera trajectory from the
TUM RGB-D benchmark (with synthetic detections and realistic detector
noise layered on top), to make sure the association/fusion logic holds
up against actual sensor jitter, not just clean synthetic input.

Pre-1.0, so the API may still shift a bit.

Get it

geomap-engine = https://github.com/ghimirebibek/geomap-engine
Repo (MIT licensed)

Feedback, issues, and PRs welcome — especially from anyone who's fought
with ARKit/ARCore object placement before and has opinions about the
projection/association assumptions.

Top comments (0)