Where: geo search

The Elasticsearch essays · January 2026

Half the queries in the world have a where in them: restaurants near me, ships inside this corridor, incidents in this district. Elasticsearch treats location as just another field type, geo_point, latitude and longitude, and stores it in the same BKD tree that indexes every number. This essay is about what that buys: queries drawn as shapes, and aggregations drawn as maps.

1 · Queries you can draw

A geographic query is a shape, and a document matches if its point falls inside. Drag the query around the harbor and switch shapes; the colored cells are how the index sees the problem:

–
of 180 points match
–
points tested one by one
Drag the shape. Every point inside lights up, and the count updates like any other filter, because it is any other filter: composable with terms, dates and full-text in one bool query. The rectangles are the tree's leaf cells: it splits the points at the median, again and again, and each leaf records the tight box around the few points it holds, so cells are small where points crowd and empty space belongs to no cell at all. Green cells sit entirely inside the query, every point accepted unexamined; pale cells sit entirely outside, skipped wholesale. Only the orange cells the boundary crosses have their points tested one by one. Turn the cells off to see the brute-force cost. On millions of points the ratio gets dramatic: the tested set grows with the length of the boundary, not with the size of the data.

2 · Aggregating the map itself

Zoom a map application out and it doesn't draw ten million pins; it draws a heat map. That is a geotile aggregation: bucket every point by the map tile it falls in, count per bucket, and let the zoom level decide how fine the tiles are:

–
buckets returned
180
points, never sent to the client
Slide the zoom. Coarse tiles answer in a handful of buckets; each zoom level splits every tile into four and the picture sharpens, the same points re-bucketed on demand, and only tiles that hold at least one point come back. Push to the finest level and almost every point gets a bucket of its own: the aggregation has stopped summarizing, which is why maps pick the zoom from the screen. The client only ever receives tile counts, a few hundred bytes, which is how a dashboard maps a billion events without moving them. The hexagonal sibling, geohex_grid, tiles the world in H3 hexagons instead, which suffer less distortion near the poles and look better on dashboards besides.

Under both figures sits one idea: location is a number pair, indexed in the BKD tree for queries and stored as doc values for aggregations and sorting, and everything else, aggregations, ranking by distance, filters during a vector search, composes with it for free. There are richer shapes too: geo_shape indexes polygons themselves, coastlines, delivery zones, no-fly corridors, broken into triangles and indexed in the same kind of tree, so the query "which zones contain this point" is answered the same way.