The planet is divided into a hierarchy of hexagonal cells using Uber's H3 indexing system. Each cell is a discrete, queryable unit of geography with a unique identifier.
Hexagons tile the sphere more uniformly than squares. Every neighbor is equidistant from the center, which eliminates the directional bias inherent in rectangular grids. This matters when measuring density, proximity, and movement across cells.
H3 provides 16 resolution levels (0-15). Spatial Agents uses resolutions 3 through 7, each serving a different analytical purpose.
| Resolution | Edge Length | Use Case | Time Bin |
|---|---|---|---|
| 3 | ~59.8 km | Regional density heatmap | 1 day |
| 4 | ~22.6 km | Shipping lanes and corridors | 1 hour |
| 5 | ~8.5 km | Port and airport approach | 5 minutes |
| 6 | ~3.2 km | Harbor and terminal detail | 1 minute |
| 7 | ~1.2 km | Berth and gate level | Live |
Each resolution has a paired time window. Coarse resolutions aggregate data over longer periods (daily at res 3), while fine resolutions capture near-real-time snapshots (live streaming at res 7). This pairing keeps tile sizes manageable while maximizing temporal fidelity where it matters most.
{tile_dir}/{resolution}/{cell_id}/{temporal_bin}.json
Each tile contains metadata (cell ID, resolution, counts, bounding box) plus the full vessel and aircraft records within that cell and time window.
When a new position record arrives from any feed, it is immediately assigned H3 cell IDs at all five resolutions. This happens once at ingest time, so downstream queries by cell are simple dictionary lookups rather than spatial computations.
A region is defined as one res-4 cell (the primary, where the data matters most) plus its six adjacent res-4 neighbors as a buffer ring. The seven-cell tile gives ~70 km of coverage across the region with a clean visual hierarchy: solid outline for the primary, dashed for buffers.
The active set is configured as a list — typically two slots, e.g.
["san_francisco", "chicago"] — and the AIS subscription, ADS-B
poll, NWS alert filter, and FAA TFR tagging all derive their geographic
filters from this single source.
Slot 0 is pinned to san_francisco (legacy v3.1 client contract).
Slot 1 is mutable at runtime via POST /regions/swap — the server
geocodes a city name through Nominatim, snaps the resulting lat/lng to the
nearest res-4 H3 cell, computes the 7-cell tile, and atomically updates the
active set. AIS reconnects with the new bbox union, ADS-B picks up the new
region in its next rotation, and per-region caches are purged.
AIS budget. aisstream.io's free tier subscribes to a single bbox set; every additional region widens the union and increases the inbound message rate. Two regions is a comfortable steady state — ten would saturate the connection and force us into a paid tier.
ADS-B budget. OpenSky's free tier rate-limits per-IP.
Each active region adds ~1 poll per 45 s rotation slot, so N regions
means each region is polled every 45 × N seconds. Past
three or four regions, freshness degrades faster than users tolerate.
The swap pattern is the answer. Keep the active set small (slot 0 + slot 1), and let users pivot slot 1 to whatever city they care about right now. A 112-second cooldown between swaps prevents thrashing the upstream feeds.
Render hexes as polygons using h3_cell_to_boundary,
never as marker icons centered on the cell — actual cell shapes distort
with latitude.
Translucent fill + crisp outline. Fill at 8–18% opacity conveys "this area is in the set"; the outline at 70–90% opacity carries the hex shape. Solid stroke for primary cells, dashed for buffer cells.
Mixed-resolution compact sets stay mixed. When a polygon (weather alert, TFR) is converted to H3, the result is a minimal cell set at varying resolutions — render each cell at its native resolution.
| Endpoint | Returns |
|---|---|
/health | Per-region geometry (GeoJSON MultiPolygon), primary cell, buffer cells, H3 sample sets, advisories |
POST /regions/swap | Replace slot 1 with a new city. Server geocodes, computes the 7-cell tile, updates feeds. 112 s cooldown. |
/regions | Diagnostic snapshot: active list, version hash, cooldown remaining |
/api/tiles/info/{h3_cell} | Cell metadata, center, boundary, neighbors |
/api/tiles/bbox | All cells within a bounding box |
/api/tiles/position | Cell IDs for a lat/lng at all resolutions |
/api/tiles/stats | Total tile count and storage size |
/tiles/{path} | Pre-computed static tile JSON files |