H3 vs S2: Geospatial Indexing System Selection Playbook
Date: 2026-03-06
Category: software
Purpose: A practical guide for choosing and operating H3 or S2 in production systems (search, mobility, telemetry, risk heatmaps, and geo analytics).
Why this matters
Most geo incidents are not about map rendering. They come from subtle indexing mismatches:
- nearest-neighbor queries returning unstable candidates across zoom levels
- polygon joins that explode in compute cost during peak traffic
- edge-case misses near poles/antimeridian
- hot keys because cell size was chosen by guesswork
Choosing the right global grid system early reduces both latency and correctness debt.
One-screen mental model
- H3: hierarchical hexagon-first global grid (with 12 pentagons per resolution). Great ergonomics for k-ring neighborhood logic and smooth radial aggregations.
- S2: hierarchical spherical quads on a cube projection, indexed along a Hilbert curve. Excellent for region coverings, ordered range scans, and robust geometry tooling.
Neither is universally better. Pick based on query shape + storage model + operational constraints.
Core geometry and hierarchy
H3
- Built from an icosahedron-based DGGS.
- Resolution 0 has 122 cells (110 hexagons + 12 pentagons).
- Every resolution keeps exactly 12 pentagons.
- Child expansion:
- hexagon → 7 children
- pentagon → 6 children (5 hex + 1 pentagon)
- Total cells at resolution
r:c(r) = 2 + 120 * 7^r
- Practical range in core library: resolution 0..15.
S2
- Starts from 6 cube faces projected to a sphere.
- Each cell recursively subdivides into 4 children (quadtree-like).
- Levels typically 0..30.
- Total cells at level
L:n(L) = 6 * 4^L
- Cells are 64-bit IDs ordered along a Hilbert-like space-filling curve for locality-preserving scans.
Query-shape fit (decision table)
Prefer H3 when...
- your dominant logic is radius / neighborhood (
kRing, local smoothing, hotspot expansion) - you need intuitive hex bins for dashboards and density heatmaps
- you care about visually uniform local adjacency for operational users
- analysts will work directly with grid cells in SQL
Prefer S2 when...
- you do heavy polygon coverings (complex geofences, admin boundaries, antimeridian-aware regions)
- your datastore benefits from ordered key ranges and prefix/range scans
- you need mature spherical geometry primitives (containment/intersection distance workflows)
- you already depend on libraries/products built on S2 semantics
Distortion, uniformity, and the “don’t overpromise” rule
No global discrete grid is perfectly uniform on a sphere.
- H3: generally good local isotropy for neighborhood math, but pentagon neighborhoods are topological exceptions and area still varies by location.
- S2: quadrilateral cells with bounded distortion properties and strong region-covering behavior; area/edge lengths vary across faces/levels.
Operator rule:
- never claim “equal area everywhere” for either system
- benchmark error and performance on your real lat/lon distribution
Storage and indexing patterns
Pattern A: OLTP + geo filters
- Store raw point
(lat, lon). - Store one or more precomputed cell IDs (e.g., H3 r8/r10 or S2 L12/L14).
- Build secondary index on cell ID.
- Query flow:
- derive candidate cells from region/radius
- fetch by indexed cell IDs
- exact-geometry post-filter (great-circle distance / polygon contains)
This keeps latency predictable and correctness exact.
Pattern B: OLAP cube / telemetry lake
- Partition by coarse cell level, cluster by finer level.
- Materialize daily aggregates per cell.
- Keep “rollup ladders” (coarse/fine) for drill-down without full recompute.
Pattern C: Hybrid serving (hot + cold)
- hot path: in-memory key-value by fine cell
- cold path: warehouse/lake by coarse cell + time bucket
- reconcile with exact geometry fallback for strict endpoints
Resolution/level selection heuristics
Choose from SLO backward, not from map aesthetics.
- Define target use-case radius (or minimum polygon feature size).
- Set max candidate expansion budget (cells/query at p99).
- Pick the coarsest level that keeps false positives manageable.
- Validate in three stress zones:
- high density urban area
- sparse rural/ocean area
- antimeridian/high-latitude edge cases
A common anti-pattern is choosing too-fine cells first, then paying for fan-out, cache churn, and compaction pain forever.
Operational pitfalls (seen in production)
Treating cell match as exact containment
- Fix: always run exact geometry final filter for strict APIs.
Single-level-only modeling
- Fix: keep at least two levels (serving + aggregation) and test cross-level consistency.
Ignoring pentagon and boundary tests (H3)
- Fix: include pentagon-heavy and boundary regression fixtures.
Assuming key locality == geometric correctness (S2)
- Fix: validate region coverers and post-filter logic separately.
No migration strategy for level changes
- Fix: dual-write old/new levels, shadow-read, then cut over with rollback window.
Benchmark protocol before committing
Measure both systems on your own data with identical workloads:
- query latency p50/p95/p99
- candidate set size distribution
- false-positive ratio before exact post-filter
- storage footprint (index + aggregates)
- cache hit ratio under real traffic skew
- rebuild/backfill throughput
- incident surface: antimeridian/pole/boundary correctness
Decision quality is much better when made from benchmark deltas, not library popularity.
Practical recommendation template
Use this in architecture docs:
- Primary query shape: radius / polygon / path / NN
- Traffic profile: QPS, p99 budget, burst factor
- Correctness contract: approximate-first vs exact-required
- Chosen index: H3 or S2
- Chosen levels: serving level(s), aggregation level(s)
- Fallback rule: exact geometry post-filter strategy
- Migration plan: dual-write/read, verification metrics, rollback
If this template is missing, the team is likely taking hidden geo debt.
Rule-of-thumb cheatsheet
- Neighborhood-first analytics UI → H3 often simpler.
- Complex region coverings + ordered scan needs → S2 often stronger.
- For strict APIs, both should end with exact geometry checks.
- Choose level by p99 + candidate budget, not by visual preference.
References
- H3 overview: https://h3geo.org/docs/core-library/overview/
- H3 resolution statistics: https://h3geo.org/docs/core-library/restable/
- S2 cell hierarchy: https://s2geometry.io/devguide/s2cell_hierarchy.html
- S2 cell statistics: https://s2geometry.io/resources/s2cell_statistics.html
- S2 geometry project: https://s2geometry.io/