AngoraTek FR
← Back to Blog
Studio 9 min read

Overture's SDKs Speak TypeScript and Python. Your Mobile App Doesn't.

81,455,423 place records sit in Overture's 2026-09-23.1 release, queryable in place from a laptop, and every client the project ships for reaching them assumes the data itself is your work product. The tooling is server-shaped: a Python CLI that can mirror a release into MongoDB, an overture-core SDK foundation, an Airflow provider for pipelines, none of it loadable into an app runtime. A web team fits that world comfortably. A mobile team compiles an app that has to read map records on a device, offline, in Kotlin or Swift, and Overture publishes no client that reaches it.

So the claim this post defends, which a skeptical mobile architect is welcome to argue with: the Overture client mobile teams need is a Rust core that reads GeoParquet directly, typed against one pinned schema release, exposed to both platforms through UniFFI bindings. Not an HTTP wrapper, because there is no HTTP endpoint to wrap. Overture ships open data that you read, and the reading is the product. What follows is an architecture we believe in and have not built. The section on limits exists before the section on design, in spirit: the hard parts are visible from here.

A phone renders Overture fine. It cannot read Overture.

Rendering on mobile is solved. MapLibre Native consumes the same PMTiles tiles a web build uses, which is the path our Overture tiles post walks. Ship the tile archive and the basemap works without a raster vendor. That covers what the user sees.

It does not cover what the app knows. Consider the app this whole post keeps coming back to: a field-inspection tool that has to be useful inside a hangar with no signal. Before the offline phase it needs to cache the places around Lake Tahoe, 28,166 of them in release 2026-09-23.1, pruned from the global set by one bounding-box predicate in 2.77 seconds with DuckDB 1.5.6 on 2026-10-11. The pruning is cheap because Overture carries a bbox struct column, so the predicate discards parquet files and rows without ever touching a geometry. Any desktop script can produce that extract.

The extract is opaque structs of JSON-shaped data, and the phone has to live with the result for months. Which field carries the opening hours? Which nested name rule applies in France? And the GERS id, the join key between this place and everything else your data knows, has to come from somewhere the extract does not label on its own. An SDK answers all of that by typing it: one struct per theme and type, generated from one schema release. Without that, every mobile codebase re-derives the schema by hand and re-learns it on every Overture release.

The org already writes Rust. It has not pointed Rust at the phone.

The evidence that Rust is the right systems language for Overture data is not theoretical, because the Overture Maps org itself builds in it. omf-bifrost, inside the transportation-routing repo, builds Valhalla graph tiles straight from Overture GeoParquet. osm-pbf-parquet transcodes OSM PBF into hive-partitioned parquet. stac catalogs releases. These are production-shaped, MIT-licensed components, and none of them knows what a phone is.

The official SDK-side work points the other way. The overturemaps-py CLI writes to MongoDB with one flag. overture-core lays the foundation of an SDK. The Airflow provider schedules pipelines. All three assume a server environment where Python or TypeScript is already installed and the release is the thing being processed. Nothing in the list is loadable into an Android build or an iOS bundle.

That is the gap, stated plainly: a data lake with six themes (addresses, base, buildings, divisions, places, transportation) and a schema that moves at a monthly cadence. There is no client library for the platforms where the data has to become an app feature. The honest reading of the org's own repos is that the direction is proven and the reader is missing.

The three obvious paths run out for different reasons

The first path is embedding Python and driving overturemaps-py on device. It works in a demo and fails in review, because a full Python runtime in an app is tens of megabytes per platform, and every app-team reader of this post already knows the conversation that happens with the size budget. The second is binding DuckDB itself, which runs on both mobile platforms these days. That is genuinely viable and we would still stop short of calling it an SDK, because DuckDB answers queries and hands back untyped rows; the Kotlin and Swift layers above it would each parse the schema by hand. You would build the part this post is about and call it an integration.

The third path is the one most teams quietly run: extract server-side, ship static JSON to clients. Nothing is wrong with it technically. It costs the self-serve property that makes Overture attractive in the first place, because now a backend with a release-pinning schedule and an extract pipeline sits between the app and the data, and every schema change ships through it. Overture's pitch is that there is no vendor endpoint. This path reinstates one, staffed by you.

What a Rust core would owe the schema

Four jobs, and the first one is the product. The core generates its record types from Overture's schema repository at one pinned release, and the release is part of the type. A binary built against 2026-09-23.1 has a taxonomy.primary field on place records and no categories field, because v2.0.0 replaced the latter with taxonomy plus basic_category. When a release moves schema, the upgrade arrives as a compile error review, not as an untyped payload changing shape in production. That is the whole argument for codegen over hand-parsing, and it is also why the next section is about the pain of keeping the generator current.

The second job is bounded extracts. The query surface exposes a bounding box, and under the hood the predicate targets the bbox struct first, exactly the mechanism that pruned 81,455,423 places to 28,166 in 2.77 seconds. Extracts for Transportation segments in the same box took 18.0 seconds, and the 228,935 buildings took 37.1 seconds, so the extract manager fetches each theme per box once per release and caches the result.

The third job is a GERS-keyed cache. The Global Entity Reference System id is the join key between themes and between your data and Overture's, so the cache stores records by (release, GERS id) and never by id alone. The fourth job is release management: one string, one pinned release, one invalidation path when you move to the next one. A sketch of the call surface, not compiled code:

let overture = Overture::new(cache_dir)?.pin_release("2026-09-23.1")?;
let places = overture
    .theme(Theme::Places)
    .bbox(-121.0, 38.7, -119.6, 39.5)
    .fetch()?;
// 28,166 rows, only true of release 2026-09-23.1

One core, two phones, and the discipline we already run

UniFFI generates Kotlin and Swift bindings from one Rust interface, and that single fact is what makes the architecture cheaper than three native SDKs. The core is written and tested once. The error types and release-pin semantics cross the boundary as real platform types, so a missing field surfaces as an error value in the Kotlin and Swift call sites rather than as a nil that someone will unwrap in a hurry.

We have shipped this shape of work before, and the design rule carries over from those projects. everymap-rs, our MIT-licensed Rust client for five geospatial providers, holds 35 provider implementations behind one interface and makes unsupported capabilities fail loudly in those words. Our HERE SDK bindings for .NET MAUI taught the harder lesson: write the surface a developer would actually type first, then cut the bridge to fit it, because the bridge is the spec and we learned that one release late. In the UniFFI world that rule reads the same: the Kotlin and Swift call sites should be indistinguishable, and if a platform distinction leaks into app code, the binding did its job badly. Here that rule costs nothing, because UniFFI covers Android and iOS from one definition.

The limits, and what each one gets instead

  • Schema codegen across releases is genuinely hard, and it is the load-bearing hard part. The schema churns monthly; v2.0.0 already broke places consumers. The substitute for a solution is a policy: types generated per release and upgrades reviewed as diffs. Each schema version you choose to keep alive carries a migration note and a maintenance cost.
  • Some of the typing is optional whether the SDK likes it or not. In our Tahoe sample from 2026-09-23.1, 896 of the 28,166 places, 3.2 percent, had a NULL taxonomy.primary. The types must carry optionality everywhere the schema does, and a non-optional field where the data is honest about absence is a bug shaped like a crash.
  • The addresses theme is alpha: 474,000,000 features with no GERS ids yet, per the Overture docs. The SDK would read addresses as geometry plus source fields and refuse to promise more, or the promise would be the first thing October's release invalidated.
  • GERS ids churn. The 2026-09-23 release regenerated UUIDs for base and building_part categories, per its release notes. Caching by (release, GERS id) contains that; a cache keyed by id alone would hold dangling joins across releases and fail quietly, which is the failure the whole design exists to avoid.
  • The web story is out of scope. UniFFI covers Android and iOS; the browser would need wasm, and the honest answer for the web is that it already has a path that works, DuckDB in the browser and the tile pipeline. This architecture serves the platforms that have none.
  • Nothing here is shipped. The Rust repos named above belong to Overture's org. The client described belongs to a build we have not started, and the sketch in the code block is the only code in this post.

Pin the release, or the release pins you

If your app renders Overture tiles today and still carries a dictionary of untyped map data behind them, the schema is already moving under that dictionary a release at a time. The decision-shaped version of this post: pin the release in the type system, read GeoParquet through bounded extracts, cache on (release, GERS id), and let UniFFI carry one core to both phones. A schema breaking is survivable when the compiler reports it. The same break arriving as a shape change in production data is how mobile map code dies. Overture solved the open-data half, at a monthly cadence, with GERS ids to join on. The reader is what remains, and one language already runs where both phones run.

AngoraTek builds cross-platform clients for geospatial SDKs, from everymap-rs to the HERE .NET bindings. Tell us how your mobile build reads map data, before the next schema release decides for you.

Thanks for reading this far. If this sounds like the problem you're wrestling with, tell us about it. A principal reads every message.

Start a conversation