SLAM Mapping Back-office
SLAM mapping back-office for an accessible indoor navigation product. Async pipeline from 360° video to navigable map, with a C++ SLAM engine driven remotely, interactive Sim(2) georeferencing, 2D/3D visualisation and operator curation. Greenfield, end-to-end.
From 360° video to a usable map
Field operators capture buildings as equirectangular 360° video. The back-office then orchestrates the full pipeline: 3D reconstruction, interactive georeferencing, operator curation, export. At the scale of large public venues: airports, train stations, museums, industrial sites.
Equirectangular 360° MP4 (handheld camera), several segments per site, walked at a steady pace.
GCS signed URL (15 min, scoped). The client uploads straight to the bucket, the backend never proxies the file.
C++ engine driven remotely on a dedicated external host. Adaptive YAML config (indoor / outdoor). Output: a binary MSG (msgpack) + trajectory.txt.
msgpack → keyframes serialised as JSON in the jobs table. Sub-second for a typical trajectory. Map points stay on GCS for debug.
The operator pins GPS constraints on keyframes. Sim(2) solved via SVD / Umeyama as soon as ≥ 2 constraints. Solve is instant, even with a couple dozen points.
Manual validation: only trajectories flagged as "validated" reach the production map. Avoids pollution by tests and A/B retries.
GeoJSON (FeatureCollection LineString + keyframe Points) is downloadable. Consumed by the downstream mobile app.
From scratch, end-to-end
SLAM driven remotely
FastAPI invokes the C++ SLAM engine over HTTPS on a dedicated external host (native deps, GPU for bundle adjustment). Three modes: remote prod, Docker dev, mock CI. Status tracking, clean retries, HTTP threads never block.
Synchronised 2D & 3D viewers
Mapbox GL for the floor plan with trajectories as LineStrings, keyframes as Points and draggable GPS constraints. Three.js for camera frustums, a filterable point cloud and the 3D trajectory. Selection and hover propagated across both views.
Interactive Sim(2) georeferencing
1 constraint → translation only. ≥ 2 constraints → Umeyama solve (SVD on the centred covariance matrix, det forced to +1 to avoid flips). Level override when all constraints live on the same floor. Solve is instant.
Large-file friendly upload
GCS signed URL (PUT, 15 min, scoped to the exact path). The client uploads its video straight to the bucket via XHR with progress, no file transits through the backend pod, which stays lean on RAM.
Operator curation
A trajectory only reaches the production map after manual validation. Avoids pollution by retries and A/B comparisons. Reversible via a confirm dialog.
"Calm / loud" server dashboard
Infra monitoring with a simple rule: a quiet one-liner when healthy, a full panel when something is off. Covers uptime, DB, disk, RAM, SSL certs, GCP billing. Avoids Grafana fatigue.
Auth & audit
Keycloak OIDC, three roles (admin / operator / reader), full audit trail grouped by day, RFC 4180 CSV export with UTF-8 BOM (Excel FR friendly).
In-house design system
CSS tokens, systematic dark mode, shared components (EmptyState, UserChip, ConfirmDialog, Escape-to-close), unit + Playwright E2E coverage per scenario.
How it is built
A few technical calls
SLAM off-GKE, on a dedicated external host
The C++ engine has heavy native dependencies and benefits from a GPU for bundle adjustment. A GKE GPU node would have cost more than a dedicated external server. Three fallback modes (remote / Docker / mock) make it testable without depending on the prod host.
FastAPI over Spring Boot
The SLAM ecosystem (open-source C++ engine, OpenCV, msgpack) is Python-native. A JNI bridge to Java would have doubled the technical surface for no gain.
Plain Three.js, not React Three Fiber
Need fine control over the scene and per-frame perf (thousands of frustums). RTF adds a React cost per frame we cannot afford.
Zustand + TanStack Query, not Redux
Limited client state, simpler API. TanStack Query handles cache, invalidation and refetch on its own, no reason to re-implement that.
Direct upload to GCS, not via a backend proxy
Videos weigh several gigabytes. Streaming a file that size through the backend pod would OOM it. A scoped signed URL + client-side XHR keeps the backend lean and lets the bucket handle throughput.
No DB mocks in integration tests
A divergent mock masked a broken migration in prod. Now: Testcontainers / ephemeral DBs, and that class of bug cannot ship again.