Skip to content

ADR-0030: Offline vector map pack for the location picker

Context

ADR-0026 shipped a tile-free canvas grid picker because ADR-0015 forbids the INTERNET permission — an online tile map or geocoder is unavailable by construction. The canvas works but gives no geographic context: no streets, no neighborhoods, no coastlines. Users placing or reading a transaction location deserve a real basemap, still with zero network.

Decision

The location picker renders offline vector tiles from a single-file PMTiles archive, with a three-tier fallback:

  1. Bundled overview (always). A z≤9 Malaysia extract (~5.3 MB) ships in APK assets under assets/maps/ and is copied once to filesDir/maps/ on first use — pmtiles://asset:// does not work because PMTiles needs byte-range reads that AssetManagerFileSource cannot provide.
  2. Imported full pack (opt-in). Settings › Data gains "Offline map pack": the user picks any .pmtiles document; the header is validated (Pmtiles.isV3Header) while streaming into place atomically. Provenance (display name) lives in the encrypted settings table (KEY_MAP_PACK_NAME). We publish dated whole-Malaysia packs as GitHub release artifacts cut from Protomaps' public daily builds (scripts/cut-map-pack.sh, z0–13 ≈ 114 MB measured).
  3. Canvas fallback (ADR-0026 unchanged) when even the overview is unavailable.

Rendering uses MapLibre Native (via the official maplibre-compose wrapper, which bundles MapLibre Android ≥ 13 with native pmtiles://file:// support). The pin stays fixed at screen center — pan/zoom moves the target, exactly the ADR-0026 drag interaction translated to a real map. Style JSON, glyphs (Noto Sans Regular, OFL), and attribution ship in assets; nothing loads over the network.

Consequences

  • The app keeps no INTERNET permission; map data reaches devices only via the APK (overview) or user-initiated file import (full pack).
  • Attribution "© OpenStreetMap contributors" is embedded in the style's source and always rendered by MapLibre's overlay (ODbL compliance).
  • Full-pack zoom is capped at z14 client-side; the published pack targets z0–13 (~114 MB measured for the MY bbox; z14 ≈ 226 MB, z15 ≈ 456 MB).
  • .pmtiles country packs are never committed to git; only the small bundled overview asset is.
  • maplibre-compose is pre-1.0; upgrades may require code motion. The Vulkan renderer needs a physical device or Vulkan-capable emulator image.
  • The canvas picker remains reachable (Loading / CanvasFallback states), so the feature degrades gracefully on any device.

Alternatives considered

  • Online tiles behind an opt-in permission — rejected: reverses ADR-0015's core guarantee and reopens the Play-review surface.
  • Raster MBTiles + osmdroid — rejected: ~340 MB at z12 alone, ~22 GB at z15 for the same bbox, fixed style, blurry between zooms.
  • Mapsforge prebuilt maps — viable renderer but weaker Compose story and a second ecosystem to maintain; MapLibre + PMTiles won on format (single-file, cloud-friendly) and active maintenance.
  • Bundle the full pack in the APK / Play Asset Delivery — rejected: couples map data to app releases, bloats every install by hundreds of MB, and adds CI complexity for a closed-testing-scale app.