← All projects

ageomaps

In development

A desktop GIS where an analysis is a workflow you can read, re-run, branch, and compare, instead of a pile of one-off clicks.

GISGeospatialRustC++QtDesktop App

At a glance

  • Outcome: A Rust desktop GIS where an analysis is a workflow you can read, re-run, branch and compare, drawn on a native Qt/MapLibre canvas through a C++ integration layer I own.
  • Status: In development and in active daily work: 7,062 commits across 127 separate days since late March 2026, counting the original Ageo and this rewrite of it. A middle iteration lived on another machine and isn’t counted, so those totals run low. No public release yet.
  • Role: Solo. I own the architecture, the native-bridge design, the debugging and validation; implementation is AI-assisted and reviewed.
  • Stack & libraries: Rust core, project-owned C++ for the Qt and MapLibre integration, GEOS and GDAL-backed engines, Nix flake toolchain.
  • Validation: Behavior is admitted through contract tests before any UI shows it, and native rendering is checked pixel by pixel. Five CI profiles, where the core can never skip and release mode turns any skipped check into a blocker.
  • Limitations: Branches form a tree with no merge. The cache replaces whole steps, so a partial hit re-runs the step in full. Reprojection falls back to an approximation for the projections that need a datum grid. Row-level diffs and the gated natural-language path are still pre-release.

The problem it’s actually for

Real spatial data is messy, and most tools answer that in one of two bad ways. They block until you clean it, or they run and say nothing.

The rule this project works to is that only four things should stop you: safety, corruption, credentials, and anything you can’t undo. Everything else stays runnable and carries its review state with it. A project reads as Ready, Needs review, or Blocked instead of a red-yellow-green light, because a severity color overstates how bad ordinary data actually is. Preview, not a full run, is the default first move for anything expensive. And where you describe a map in plain language, it drafts a plan for you to review. It never runs on its own.

That sits between lightweight web maps and the full weight of QGIS or ArcGIS Pro: real geoprocessing, with the workflow itself as a thing you can hold.

The workflow you edit is not the plan that runs

This is the decision the rest of the system hangs off.

What you edit is a faithful mirror of the file on disk, where edges can be implicit and blanks are allowed. Before a run it compiles into a second form where every input is resolved to a specific upstream output, the steps are in execution order, and every result carries an address derived from the operation, its inputs, its parameters and the versions of the tools that made it. Blanks are rejected instead of carried through. That compiled plan is what the engine runs, what the cache keys on, and what the review panel reads.

Keeping two shapes in agreement costs something, and one decision pays for it. A step keeps its identity through the compile. Because identity survives, comparing two versions of a workflow is a diff of the things you edited, and that diff threads straight into either side’s compiled plan. The plan never needs a diff of its own. The same handle answers the harder question too: change one source and the system knows exactly which steps, and everything downstream of them, have to run again.

Checkout is optimistic on purpose. A saved version pins both the address of each result and the bytes it actually had. Opening an older version restores the workflow first, and anything no longer cached shows up as dirty and recomputes in the background instead of blocking you. The costs are real and named above: no branch merge, and a partial cache hit re-runs the whole step, because at the plan level a partial hit and a full re-run look the same.

What works today

  • Vector analysis on GEOS: buffer, clip, dissolve, overlay, spatial joins, grid generation.
  • Raster and terrain: clip, reproject, resample, reclassify, raster calculate, zonal statistics, slope, aspect, hillshade, viewshed.
  • Ingest from GeoJSON, FileGDB, GeoPackage, GeoParquet, and municipal FeatureServer sources with opt-in live refresh.
  • Session and review: plan review, receipts on every run, project health, checkpoints, named saves, branch checkout and comparison.
  • A board instead of a two-up compare. The old layout had exactly two slots and no way to tell them apart. A board has any number of tiles, each holding a version, each keeping its identity as you work. Aligning two tiles promotes those two to live rendering and demotes whatever it displaces, and it tells you what it displaced instead of quietly capping.
  • Export to PDF/A-1b, PNG, SVG, PowerPoint, DOCX and static HTML through a separate worker process. A run can bank the deliverable it rendered alongside the inputs that made it.
  • Plain-language planning that compiles into the same reviewable workflow as a manual plan. Live inference is opt-in and gated by your environment.

The native bridge, which is the C++ that’s mine

The canvas is Qt and MapLibre Native, the core is Rust, and the seam between them is where the hardest systems work lives. That seam is about 5,900 lines of C++ across thirteen files, counting only code I’m responsible for, not upstream Qt or MapLibre and not generated bridge output.

  • Custom file sources for tiles, basemaps and glyphs, so the renderer pulls from the project’s own storage instead of a hosted tile service.
  • The Qt shell and render driver, keeping map rendering, the Rust core, and Qt’s main-thread rules in agreement.
  • Object lifetimes and shutdown ordering across the Rust, C++ and Qt boundary, which is the debugging that earned the layer its keep.
  • Native render validation: pixel and counter checks that the map actually drew what the workflow said it should.

To be precise about the claim: this is integration and systems code. I don’t claim any of upstream MapLibre or Qt as mine.