H3 Resolution Guide for Web Maps: Choosing Resolution by Zoom and Understanding Performance Tradeoffs


H3 Resolution Guide for Web Maps: Choosing Resolution by Zoom and Understanding Performance Tradeoffs

H3 is an amazing building block for web mapping because it gives you a global, hierarchical grid where every cell has a stable ID. But once you try to render H3 at scale, you immediately hit the practical question:

Which H3 resolution should I use at each zoom level?

Pick a resolution that’s too coarse and your map looks blocky. Pick one that’s too fine and the UI becomes slow: too many features, too much geometry, too many bytes.

This post is a practical guide for web maps (MapLibre/OpenLayers/Deck.gl style): how to map zoom → H3 resolution, what tradeoffs matter, and how to implement multi-resolution rendering without pain.

All code blocks are indented to avoid Markdown-breaking nested fences.


The mental model: resolution controls feature count, not just detail

H3 resolution is not “just nicer shapes”. It directly controls:

  • how many hexagons exist globally
  • how many hexagons you draw in a viewport
  • how many rows you query from your dataset
  • how much geometry you send to the client
  • how slow it feels when panning and zooming

A higher resolution means smaller hexagons, which means more features. For web maps, feature count is often the limiting factor before math or storage becomes the issue.


What “zoom” really means for H3

In slippy maps, zoom controls:

  • how much area is visible in the viewport
  • how many pixels represent a meter
  • how detailed users expect the data to look

So the trick is to match:

  • the visual density (hexagons per screen)
  • the data density (rows returned for a query)
  • the rendering budget (polygons per frame)

There is no universal mapping because it depends on:

  • device power (mobile vs desktop)
  • style complexity (fills only vs outlines + labels)
  • your data (global dataset vs single country)
  • your geometry approach (full polygon vs derived boundary rendering)

But we can still build a robust, repeatable strategy.


The core tradeoffs

Tradeoff 1: Polygon rendering cost (CPU/GPU)

Rendering thousands of polygons is expensive, especially if:

  • you draw outlines
  • you animate transitions
  • you recompute styles frequently
  • you show labels per cell

Even if the data query is instant, rendering can be the bottleneck.

Tradeoff 2: Network + payload size

If you fetch polygon geometries (as GeoJSON or similar), payloads explode at high resolution.

Even if you generate boundaries client-side, you still pay:

  • more features to draw
  • more properties to attach
Tradeoff 3: Storage and query scan cost

At higher resolution:

  • more rows per timestamp
  • larger Parquet files
  • more data scanned for a viewport/time filter

The key is to avoid scanning “global res=8 for all time” when a user is zoomed out.

Tradeoff 4: Perception vs precision

At low zoom levels, users cannot perceive fine resolution. Sending/resolving it is wasted effort.

A good default: don’t send detail that the user can’t see.


A practical goal: keep hexagons per viewport in a target range

Instead of “zoom maps to resolution” rigidly, think:

  • pick a resolution that yields ~500 to ~5,000 visible cells in the viewport

This range is often a sweet spot for interactive web maps:

  • fewer than ~500 feels too coarse
  • more than ~5,000 often becomes sluggish (depends on device)

You can tune this based on your app, but having a target range makes the logic defensible.


A pragmatic zoom → H3 resolution table (starting point)

Here is a practical starting point for many web maps that show country-to-neighborhood scale data:

  • Zoom 0–2: H3 res 1–2 (global overview)
  • Zoom 3–4: H3 res 2–3
  • Zoom 5–6: H3 res 3–4
  • Zoom 7–8: H3 res 4–5
  • Zoom 9–10: H3 res 5–6
  • Zoom 11–12: H3 res 6–7
  • Zoom 13–14: H3 res 7–8
  • Zoom 15+: H3 res 8–9 (only if you truly need it)

This is not “truth”; it’s a good default that avoids the most common mistake: using a single high resolution everywhere.


The most reliable strategy: multi-resolution datasets + switching

The best-performing architecture is:

  • store data at a “native” resolution (e.g., res 6–8)
  • precompute rollups to coarser resolutions (parents)
  • switch resolution based on zoom

This way you avoid expensive runtime aggregation and avoid scanning massive fine-resolution data when zoomed out.


Rollup approaches: parent aggregation

If your data is stored by H3 index, you can roll up to a parent resolution:

  • parent cell ID is derived from the child
  • aggregate metrics from children into parent

Typical aggregations:

  • mean/median (for indices)
  • min/max (for extremes)
  • percentiles (for distribution-aware summaries)
  • weighted mean (if you have weights)

A conceptual SQL shape (function name depends on your H3 function set):

SELECT
  h3_to_parent(h3_index, 4) AS h3_parent,
  date,
  AVG(spi_3) AS spi3_avg
FROM drought_h3
WHERE date = DATE '2025-07-01'
GROUP BY h3_parent, date;

If you precompute this offline and store it as Parquet partitioned by date, zooming out becomes fast.


Geometry strategy: don’t ship hex polygons if you don’t have to

There are three common approaches for geometry in web maps:

Option A: Ship full polygon geometry (heavy)
  • easiest to implement with GeoJSON
  • worst for payload and parsing
  • quickly becomes slow at high resolution
Option B: Store geometry once, join by ID (better)
  • keep h3_index → geom as a separate dataset
  • join metrics to geometry client-side or in query
  • still heavy if you pull too many polygons
Option C: Generate hex boundaries on the client (often best)
  • store only H3 IDs + metrics
  • compute boundary coordinates in the browser using an H3 library
  • you trade network bytes for compute, but avoid huge geometry downloads
  • works well if you keep visible feature counts reasonable

For many web apps, Option C wins because:

  • the dataset becomes lighter (IDs + values)
  • you can cache and reuse boundaries
  • you can simplify rendering logic around zoom switches

Switching resolutions without visual “popping”

Resolution switching can look jarring if you simply swap layers. You can reduce the visual jump with:

  • hysteresis (don’t switch on every tiny zoom change)
  • fade transitions between layers
  • switch only on integer zoom boundaries
  • keep styling consistent across resolutions

A simple hysteresis idea:

  • switch to finer resolution only when zoom crosses threshold + 0.3
  • switch to coarser only when zoom goes below threshold - 0.3

This prevents rapid flip-flopping during scroll.


Performance checklist for H3 web maps

Keep feature count under control
  • target a max visible cell count
  • switch resolution before counts explode
Avoid outlines at high density
  • polygon outlines are expensive
  • consider outlines only at high zoom or on hover/selection
Use fill-only at low zoom
  • fill shading communicates patterns without heavy line work
Avoid per-feature labels
  • label regions, not cells
  • show details on hover/click panel
Partition data by time and resolution
  • res=4/year=2025/month=07/part-...parquet
  • makes resolution switching cheap and predictable
Precompute rollups
  • don’t aggregate 10 million children in the browser if you can avoid it

A practical dataset layout for multi-resolution H3

A good “data product” layout for an index like SPI:

drought_h3/
  res=3/
    year=2025/month=07/part-0000.parquet
  res=4/
    year=2025/month=07/part-0000.parquet
  res=5/
    year=2025/month=07/part-0000.parquet
  res=6/
    year=2025/month=07/part-0000.parquet

Each Parquet file contains:

  • h3_index (at that resolution)
  • date
  • metric columns (spi_3, spei_3, …)
  • optionally bbox columns (less important for pure H3 ID workflows)

Your app chooses res based on zoom and only reads that partition.


If you want a practical starting mapping (then tune based on telemetry):

  • Zoom 0–2: res 2
  • Zoom 3–4: res 3
  • Zoom 5–6: res 4
  • Zoom 7–8: res 5
  • Zoom 9–10: res 6
  • Zoom 11–12: res 7
  • Zoom 13–14: res 8
  • Zoom 15+: res 9 (only if needed)

Then introduce hysteresis and a max-feature rule to avoid overload on wide screens.


How to tune: measure, don’t guess

The fastest way to find the right switch points is to log:

  • cells rendered per frame (or total features in source)
  • query time and bytes fetched per zoom
  • frame rate during pan/zoom
  • device class (desktop/mobile)

Then adjust thresholds so that:

  • 95% of interactions are smooth
  • the app degrades gracefully at extremes

Summary

H3 resolution strategy for web maps is not about “pretty detail”—it’s about controlling feature count, payload size, and query cost.

If you want a robust default:

  • publish multi-resolution H3 datasets
  • switch resolution by zoom with hysteresis
  • keep visible cells within a target range
  • avoid heavy geometry payloads (prefer H3 IDs + client boundary generation)
  • precompute rollups so zoomed-out views stay fast