H3 Hexagonal Spatial Indexing Explained
10 min read · updated August 11, 2026
H3 replaces a coordinate pair with a single 64-bit integer that names a hexagonal cell. That swap turns spatial joins into equality joins and neighbour queries into arithmetic — and it costs you two things that a square grid would not.
What an H3 index is
H3 projects the sphere onto an icosahedron, tiles each face with hexagons, and gives every cell an integer. Uber’s resolution table records that the library uses a spherical earth model with the authalic radius from WGS84/EPSG:4326, and that every resolution contains exactly twelve pentagons centred at icosahedron vertices.
The index itself is structured, not a hash. It carries a mode, a resolution, a base-cell number, and then one three-bit digit per resolution step down to the cell’s own resolution. That layout is what makes the hierarchy operations cheap: H3’s indexing documentation notes that changing precision is “implemented with only a few bitwise operations, making them very fast”. Truncating digits walks up the hierarchy; there is no lookup and no query.
The practical consequence is that a spatial join stops being spatial. Instead of testing points against polygons, you index both sides to a resolution and join on an integer. That is an equality join, so it uses an ordinary B-tree, partitions cleanly, and shuffles well across a cluster — which is why H3 shows up in data warehouses rather than in GIS desktops.
Resolutions in metres
Sixteen resolutions, 0 through 15, each subdividing the previous by seven. The cell count follows exactly:
cells(r) = 2 + 120 * 7^r r = 0 : 2 + 120 * 1 = 122 r = 7 : 2 + 120 * 823543 = 98,825,162 r = 9 : 2 + 120 * 40353607 = 4,842,432,842
The published average areas and edge lengths are what turn a resolution into a decision. Three worth memorising, from the H3 resolution table:
- Resolution 7 — average area 5.16 km², average edge 1.41 km. A neighbourhood or a small town centre. Roughly 98.8 million cells cover the earth.
- Resolution 9 — average area 0.105 km², average edge 0.20 km. A city block. This is the usual working resolution for urban demand and supply data.
- Resolution 12 — average area 0.000307 km², about 307 m², average edge 10.8 m. A building footprint.
You can sanity-check any of these against the hexagon formula. A regular hexagon of edge a has area (3√3/2)a² ≈ 2.598a². At resolution 9:
a = 0.200786 km a^2 = 0.040315 km^2 area = 2.598 x 0.040315 = 0.10474 km^2 (published: 0.105 km^2)
The word “average” is doing real work in that table. Cells are not the same size: the gnomonic projection onto icosahedron faces stretches cells away from each face’s centre, so actual areas vary by roughly a factor of two across a resolution. If you are computing a density — events per square kilometre per cell — you must divide by that cell’s own area, not by the table’s average, or you will manufacture a density gradient that follows the icosahedron rather than the data.
Why hexagons, concretely
The reason is neighbour distance, and it is easy to see in arithmetic. On a square grid of pitch d, a cell has eight surrounding cells at two different distances:
square grid, pitch d: 4 edge-sharing neighbours at d 4 corner-sharing neighbours at d * sqrt(2) = 1.414 d ratio of far to near neighbour: 1.414 hex grid, centre spacing d: 6 edge-sharing neighbours at d ratio: 1.000
H3’s aggregation documentation states the property directly: “For hexagons, all neighbors are equidistant”, and it notes hexagons are “optimally space-filling”, so a polygon fills with hexagons at a smaller margin of error than with squares.
That 41% spread on a square grid is not a rounding detail once you do anything involving flow or diffusion. Smooth a heat map with a one-ring kernel on a square grid and the diagonal contributions are weighted as if they were the same distance as the orthogonal ones, which stretches the result along the axes. Model movement between adjacent cells and the diagonal moves are cheaper than they should be. On a hex grid the one-ring kernel is isotropic by construction, and successive rings approximate circles rather than squares.
The cost is that hexagons have no axes. You cannot index a hex grid with an integer row and column that behave like Cartesian coordinates, you cannot slice a raster into hexagons, and every rendering library wants rectangles. H3 hides most of this behind gridDisk and gridDistance, but the moment you want to treat cells as a matrix, the geometry stops helping you.
The hierarchy is approximate
This is the detail that catches people, and H3 documents it in plain terms: “Hexagons do not cleanly subdivide into seven finer hexagons. However, by alternating the orientation of grids a subdivision into seven cells can be approximated.”
So a parent cell’s seven children do not tile the parent. The central child sits inside; the six others straddle the parent boundary, each partly outside. H3’s own wording is that “while geographic containment is approximate, logical containment in the index is exact” — the integer arithmetic always agrees with itself, but the geometry does not agree with the integers.
What this means in practice: summing a metric over the children of a cell reproduces the parent’s value exactly if the metric was assigned by index, and only approximately if it was assigned by geometry. Aggregate ride counts by indexing each ride at resolution 10 and rolling up by truncation, and every count is conserved. Aggregate by clipping a polygon to resolution-10 cells and then to resolution-9 cells independently, and the two answers differ around every boundary. Pick one direction — index once at the finest resolution you will ever need, then roll up — and the inconsistency disappears.
It also means H3 cells are a poor fit for anything requiring exact area accounting: a polygon covered by cells is covered with overhang at the edges. For containment questions with hard boundaries, the cell set is a prefilter and the polygon test is still the answer, exactly as in a geofencing implementation.
The twelve pentagons
You cannot tile a sphere with hexagons alone. Euler’s formula forces exactly twelve pentagons into any such tiling, and H3 puts them at the twelve icosahedron vertices — which the project sited over ocean, deliberately, so that almost no populated place falls on one.
“Almost” is not “none”, and code that assumes six neighbours will eventually hit a cell with five. gridDisk returns fewer cells than the hexagonal formula predicts near a pentagon, distance calculations distort around it, and some traversal operations are undefined across the pentagon’s distorted edges. The library is explicit about this rather than hiding it: the correct handling is to test for the pentagon case and accept the smaller ring, not to pad it.
For most applications the pentagons never matter, and the honest reason is that they sit in the Pacific and the Southern Ocean rather than that the problem was solved. If your data is global — shipping, fisheries, satellite tracks — write the branch. If your data is a city, do not spend a line on it. Where the question is nearest neighbours rather than aggregation, the cell ring is a candidate generator and not the answer; see geospatial nearest-neighbour search.