Skip to main content
Geohash and H3: Spatial Indexes Explained, with Real Data
Guides

Geohash and H3: Spatial Indexes Explained, with Real Data

Geohash and H3 turn coordinates into cell IDs. We bucketed 8,466 Dutch post boxes with both and measured cell sizes, radius queries and hierarchy.

Brent van der Heiden12 min read
#geohash#h3#h3 index#spatial index#geohash precision#uber h3#geospatial#openstreetmap

A spatial index turns a coordinate into a cell ID. Two points in the same cell share an ID, so you can group, count and join location data with an ordinary GROUP BY or a key lookup instead of geometry maths. Geohash and H3 are the two cell systems developers meet most often.

They split the world in very different ways. To see what that means in practice, we took one real dataset, all 8,466 post boxes mapped in OpenStreetMap in the European Netherlands, and bucketed it with both systems at several sizes. This article explains how each index works and reports what we measured: cell sizes, occupancy, radius queries and how the hierarchies behave.

How geohash works

A geohash names a rectangle. The encoder starts with the whole world, longitude from -180 to 180 and latitude from -90 to 90, and halves one range at a time, alternating between longitude and latitude. Each halving writes one bit: 1 for the upper half, 0 for the lower. Every five bits become one character from the alphabet 0123456789bcdefghjkmnpqrstuvwxyz.

That gives geohash two useful properties, which the Redis GEOHASH documentation states plainly: you can shorten a geohash by removing characters from the right and it still points to the same area, and strings with a similar prefix are nearby, while strings with different prefixes can be nearby too.

This is the encoder we use on this site, from src/lib/geohash.ts, trimmed:

const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';

function encodeGeohash(lat, lon, precision = 7) {
  let latR = [-90, 90], lonR = [-180, 180];
  let hash = '', bits = 0, value = 0, even = true;
  while (hash.length < precision) {
    const range = even ? lonR : latR;
    const coord = even ? lon : lat;
    const mid = (range[0] + range[1]) / 2;
    if (coord >= mid) { value = (value << 1) | 1; range[0] = mid; }
    else { value = value << 1; range[1] = mid; }
    even = !even;
    if (++bits === 5) { hash += BASE32[value]; bits = 0; value = 0; }
  }
  return hash;
}

encodeGeohash(52.3731, 4.8926, 7); // 'u173zq4', Dam Square, Amsterdam

Because the grid is regular in degrees, a geohash cell has a fixed size in degrees and a variable size on the ground. Lines of longitude converge towards the poles, so cells get narrower as latitude rises. We computed the sizes on a sphere of radius 6,371 km, the same one H3 uses:

PrecisionHeightWidth at equatorWidth at 52°NArea at equatorArea at 52°N
419.5 km39.1 km24.1 km764 km²471 km²
54.89 km4.89 km3.01 km23.9 km²14.7 km²
6611 m1.22 km752 m0.746 km²0.459 km²
7153 m153 m94 m0.0233 km²0.0144 km²
819.1 m38.2 m23.5 m729 m²449 m²

The equatorial columns match the table in the Elasticsearch geohash grid documentation. At 52°N, the latitude of Utrecht, every cell covers 62% of its equatorial area. At 60°N, Oslo and Helsinki, it is exactly half.

How H3 works

H3 names a hexagon. Uber built it for analysing rides in cities and announced it in June 2018. It projects the Earth onto an icosahedron, starts with 122 base cells (110 hexagons and 12 pentagons), and subdivides each cell into roughly seven children per resolution, down to resolution 15.

Hexagons have one property squares lack. In Uber's words, hexagons have only one distance between a hexagon centre point and its neighbours', compared to two distances for squares. A geohash cell has four neighbours across an edge and four more across a corner; an H3 cell has six, all equally far away.

The price is the hierarchy. Seven hexagons cannot tile a larger hexagon exactly, so an H3 parent covers only approximately the same ground as its children. Cell areas also vary somewhat with position on the icosahedron: the H3 resolution table puts the ratio between the largest and smallest hexagon at close to 2 at fine resolutions, much less than geohash's variation between the equator and the Arctic.

ResolutionAverage area (global)Measured in the NetherlandsCells worldwide
5253 km²214 km²2,016,842
636.1 km²30.6 km²14,117,882
75.16 km²4.36 km²98,825,162
80.737 km²0.623 km²691,776,122
90.105 km²0.0891 km²4,842,432,842
100.0150 km²0.0127 km²29,602,062,586

The Dutch column is the mean area of the cells our post boxes landed in. Cells in the Netherlands are about 15% smaller than the global average, which is the icosahedron distortion at work. Within the country the spread is small: resolution 8 cells ranged from 0.605 to 0.646 km².

In JavaScript, with the current h3-js (version 4 names):

import { latLngToCell, cellToParent, gridDisk, cellArea } from 'h3-js';

const cell = latLngToCell(52.3731, 4.8926, 8);   // '881969c9b3fffff'
const parent = cellToParent(cell, 7);            // the coarser cell it belongs to
const nearby = gridDisk(cell, 1);                // the cell plus its 6 neighbours
const km2 = cellArea(cell, 'km2');               // area of this exact cell

The same post boxes, bucketed both ways

The dataset is every node tagged amenity=post_box in the European Netherlands, downloaded from the Overpass API on 29 September 2026. Post boxes follow population, so they cluster in cities and thin out in the countryside, which is the usual shape of location data.

We counted how many cells each system needs and how full they are:

IndexCell area hereOccupied cellsMedian per cellMost in one cellCells with one post box
Geohash 4468 km²115524655.2%
Geohash 514.6 km²2,03526232.6%
Geohash 60.458 km²7,0871684.2%
Geohash 70.0143 km²8,4131399.4%
H3 res 630.6 km²1,16048313.2%
H3 res 74.36 km²3,65612156.2%
H3 res 80.623 km²6,6301779.0%
H3 res 90.0891 km²8,2961398.0%

Three findings from the table:

  • The two ladders do not line up. Geohash cells shrink 32 times per character; H3 cells shrink about 7 times per resolution. Geohash 5 (14.6 km²) falls between H3 resolutions 6 and 7, so if you need a cell size between those steps, only one of the systems has it.
  • Fine cells stop adding information quickly. At geohash 7 and H3 resolution 9, 98% or more of occupied cells hold exactly one post box. For this dataset, anything finer turns the index into a list of unique IDs.
  • The densest cells are in Amsterdam. The fullest geohash 5 cell, u173z, covers the city centre and holds 62 post boxes. The fullest H3 resolution 7 cell, 21 post boxes, is centred in Amsterdam-Oost, which our reverse geocoder resolves to Van Musschenbroekstraat.

The most common job for a spatial index is "find everything within 500 m of this point". Neither system answers it directly. You look up a set of cells, collect the candidates, and then check the real distance. The trade-off is how many cells you touch and how many wasted candidates you have to check.

3,648 of the 8,466 post boxes have at least one other post box within 500 m, which gives 6,034 neighbour pairs, counted from both ends. We tested several lookups against that ground truth:

LookupCells searchedNeighbours foundCandidates checked per real neighbour
Geohash 6, own cell only140.8%0.56
Geohash 6, cell + 8 neighbours9100%5.4
Geohash 5, cell + 8 neighbours9100%84
H3 res 8, own cell only148.8%0.80
H3 res 8, cell + 1 ring7100%5.7
H3 res 8, cell + 2 rings19100%13.0
H3 res 9, cell + 3 rings37100%4.5

The first and fourth rows are the classic mistake. Grouping by cell and treating "same cell" as "nearby" missed 59% of real neighbours with geohash 6 and 51% with H3 resolution 8, because a post box near a cell edge has its neighbours in the next cell.

The rows that found everything differ in their guarantee, which we computed by sampling points inside one cell and measuring the distance to the edge of the searched area:

  • Geohash 6 plus its 8 neighbours covers at least 611 m in every direction here, the height of one cell, so a 500 m search is always complete.
  • H3 resolution 8 plus one ring covers at least about 465 m from a point near a cell corner. It found every pair in this dataset, but a 500 m search is not guaranteed. Two rings cover 1.2 km and cost more than twice as many candidates.
  • H3 resolution 9 plus three rings covers at least 710 m and checked the fewest candidates, 4.5 per real neighbour, at the cost of 37 key lookups.

The geohash block has one more catch: its reach depends on latitude. At 60°N a precision 6 cell is 611 m wide, so the guarantee holds, but further north the width shrinks below 500 m. Compute the reach from the smaller of cell width and height at your latitude.

Hierarchies: exact prefixes vs approximate parents

Geohash nesting is exact by construction. Every one of our 8,466 post boxes has a precision 7 geohash whose first six characters are its precision 6 geohash. You can roll counts up from fine to coarse by cutting characters.

H3 nesting is approximate. For each post box we compared two things: the resolution 8 cell that contains it, and the parent of the resolution 9 cell that contains it. For 604 post boxes, 7.1%, the two differ. The same happened for 600 points between resolutions 8 and 7, and for 554 points between 10 and 7.

That is expected behaviour, since a parent is defined by the centre of its children, but it matters when you aggregate. Counts rolled up from resolution 9 to 8 with cellToParent put about 7% of points in a neighbouring cell compared with indexing at resolution 8 directly. Pick one method and use it everywhere in a pipeline.

The rectangle hierarchy has its own sharp edge. Two post boxes in Klarenbeek, Gelderland, stand 54 m apart, at Zutphenseweg and Oude Ardeweg. Their 9-character geohashes are u1k2cvzdp and u1k2fjbhh: they share only four characters, because a precision 5 cell boundary runs between them. Grouped by five-character prefix, they land in two different 15 km² cells.

Which one to use

  • Use geohash when you need a string key that works in any database, exact parent and child relationships, or compatibility with systems that already speak it (Redis, Elasticsearch, many mobile SDKs). Account for latitude when you pick a precision.
  • Use H3 when cells need comparable areas across a large region, when you smooth or compare neighbouring cells, or when you draw hexbin maps. Store the resolution you aggregate at, and index each point directly at every resolution you need instead of deriving parents.
  • For radius searches with either, look up the cell plus a ring of neighbours, then filter on real distance. Check that the ring covers your radius at your latitude.

Method and limits

  • Data: 8,471 nodes tagged amenity=post_box returned by the Overpass API for the Netherlands (OpenStreetMap data as of 29 September 2026, 21:43 UTC). We dropped 5 in the Caribbean Netherlands and kept 8,466. © OpenStreetMap contributors, ODbL.
  • Encoders: geohash from this site's own src/lib/geohash.ts; H3 from h3-js 3.7.2, which has the same cell grid as version 4 under older function names.
  • Distances: great-circle distance on a sphere of 6,371 km. Cell areas are spherical, as H3 computes them.
  • What this does not measure: query speed in a real database, which depends on the storage engine more than the cell shape, or how complete OpenStreetMap's post box map is.

To see the two scales in practice, draw a circle with the radius map or measure a cell-sized polygon with the area calculator. The coordinates tool converts any point you want to encode. For the maps these indexes usually feed, see how to build a heat map in JavaScript and the choropleth map guide. For the box a radius search starts from, read what a bounding box is.

Preguntas frecuentes

What is a geohash?

A geohash is a short string that names a rectangle on the Earth. The encoder splits the longitude and latitude ranges in half again and again, writes one bit per split, and turns every five bits into a character from a 32-character alphabet. Each extra character makes the rectangle smaller, and a shorter prefix always names the larger rectangle that contains it. Amsterdam's city centre sits in geohash u173z at five characters.

How big is a geohash cell at each precision?

At the equator a precision 5 geohash is about 4.9 by 4.9 km, precision 6 is 1.2 km by 610 m, and precision 7 is about 153 by 153 m. Cells get narrower away from the equator because lines of longitude converge. At 52 degrees north, in the Netherlands, a precision 6 cell is about 750 m wide and 610 m tall and covers 0.46 km², and at 60 degrees north every cell has half its equatorial area.

What is the difference between geohash and H3?

Geohash divides the world into rectangles on a latitude and longitude grid, so cell size depends on latitude and a prefix is an exact parent. H3, released by Uber in 2018, divides the world into hexagons on an icosahedron, so cells have nearly equal area everywhere and every neighbour is at the same distance, but a parent cell does not exactly contain its children. In our test with 8,466 Dutch post boxes, 7.1% of points fell in a different resolution 8 cell than the parent of their resolution 9 cell.

Which H3 resolution or geohash precision should I use?

Pick the cell size that matches the question. For city-level heat maps, H3 resolution 7 (about 5 km² on average) or geohash precision 5 works. For neighbourhoods, H3 resolution 8 (0.74 km²) or geohash 6. For street-level work, H3 resolution 9 or 10, or geohash 7. In our test, cells at H3 resolution 9 and geohash 7 already held one post box each in 98% or more of cases, so going finer only adds IDs.

¿Le resultó útil? Compártalo.

Sobre el autor

Brent van der Heiden

Escrito por

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. Before that he co-founded MapMetrics, a community mapping and navigation app. He writes about geospatial infrastructure, map data, AI search visibility, and how location data powers the products people rely on every day.

Ver todos los artículos →
Volver al blog