For AI agents: this page is also available as Markdown at /docs/geo-points-and-queries.md. Every docs page is: add .md to its URL. llms.txt lists them all.

Geo points and queries

A geo_point field holds one location, or several, per document. This page covers defining the field, the point formats you can send, and the queries that match points: distance, bounding box, polygon, shape and grid cell. The examples use the places collection from the Geo search quickstart. For areas and lines rather than points, see Geo shapes.

Define a geo_point field

A field whose name ends in _p (one point) or _ps (several points) is a geo_point field with a points index, with no definition needed: location_p just works. Any other name needs a definition:

{
  "fields": {
    "location":  {"type": "geo_point", "index": "range"},
    "branches":  {"type": "geo_point", "index": "range", "multi": true}
  }
}

Send it as the schema when you create the collection, or later with PATCH /v1/collections/{collection}/schema. See Schemas and field types.

curl -X PUT https://api.warpsearch.io/compat/es/places \
  -H "Authorization: Bearer $WARPSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mappings": {"properties": {
    "name": {"type": "text"},
    "category": {"type": "keyword"},
    "location": {"type": "geo_point"}
  }}}'
Option Effect
index: "range" Builds a points index, so geo queries read only the matching area. Without it, the same queries scan the field's column and return the same results, more slowly on large collections.
multi: true Several points per document, such as a chain's branches. A document matches when any of its points does, and is returned once.
ignore_malformed Default false. When true, a point that cannot be read is skipped and the rest of the document is indexed; in a multi-valued field only the bad points are skipped.
ignore_z_value Default true: a third coordinate (altitude) is accepted and dropped. When false, a point with a third coordinate is malformed.

Send points

A point can be written in any of these forms:

Form Example Order
Array [-73.9851, 40.7589] longitude, latitude
Object {"lat": 40.7589, "lon": -73.9851} named
GeoJSON {"type": "Point", "coordinates": [-73.9851, 40.7589]} longitude, latitude
String "40.7589,-73.9851" latitude, longitude, as in Elasticsearch
WKT "POINT (-73.9851 40.7589)" longitude, latitude
Geohash "dr5ru7" 1 to 12 characters, read as the center of the cell
{"id": "p1", "name": "Harbor Coffee", "location": [-73.9851, 40.7589]}
{"id": "p1", "name": "Harbor Coffee", "location": "40.7589,-73.9851"}

Keep the order straight: arrays, GeoJSON and WKT put longitude first, while the "lat,lon" string puts latitude first.

A multi-valued field takes an array of points, in any mix of forms:

{"id": "c1", "name": "Harbor Coffee (all branches)", "branches": [[-73.9851, 40.7589], "40.7440,-74.0324"]}

These forms are for documents. Query centers and corners take "lat,lon", [lon, lat], {"lat": ..., "lon": ...} or "POINT (lon lat)", but not geohashes.

Return points

Name a geo field in fields (or _source) and each hit returns its points as [lon, lat], whatever form you sent. The values are the stored grid points, so they can differ from the input in about the eighth decimal place.

Distance queries

A distance query matches points within a radius of a center, edges included. Distances are great-circle distances over the Earth's mean radius.

Cafes within 1 km of Times Square:

{
  "language": "es",
  "query": {"term": {"category": "cafe"}},
  "filters": [{"geo_distance": {"distance": "1km", "location": "40.7580,-73.9855"}}],
  "fields": ["name"]
}
{
  "query": {"bool": {
    "must": [{"term": {"category": "cafe"}}],
    "filter": [{"geo_distance": {"distance": "1km", "location": "40.7580,-73.9855"}}]
  }},
  "_source": ["name"]
}

d is in kilometers:

q=category:cafe&fq={!geofilt sfield=location pt=40.7580,-73.9855 d=1}&fl=id,name

Bounding boxes

A box matches points between two latitudes and two longitudes, edges included:

Everything in a box around Midtown Manhattan:

{
  "language": "es",
  "query": {"geo_bounding_box": {"location": {
    "top_left": {"lat": 40.77, "lon": -74.00},
    "bottom_right": {"lat": 40.74, "lon": -73.97}
  }}},
  "fields": ["name"]
}

This matches Harbor Coffee (p1) and Midtown Market (p3).

{
  "query": {"bool": {"filter": [{"geo_bounding_box": {"location": {
    "top_left": {"lat": 40.77, "lon": -74.00},
    "bottom_right": {"lat": 40.74, "lon": -73.97}
  }}}]}}
}

{!bbox} matches the box around a circle of d kilometers:

q=*:*&fq={!bbox sfield=location pt=40.7580,-73.9855 d=2}

The corners can be given as top_left and bottom_right, as top_right and bottom_left, as the numbers top, left, bottom and right, or as {"wkt": "BBOX (minLon, maxLon, maxLat, minLat)"}. The top must not be below the bottom. A box whose left edge is east of its right edge crosses the date line: left: 170, right: -170 covers 170 degrees east through 180 to 170 degrees west.

Combine with text and other filters

A geo query used as a query gives every match the same score, and in filters it does not affect scores at all. Put the geo condition in filters and the words in query, and text relevance orders the nearby results:

{
  "language": "es",
  "query": {"match": {"description": "coffee"}},
  "filters": [
    {"geo_distance": {"distance": "6km", "location": "40.7580,-73.9855"}},
    {"range": {"rating": {"gte": 4.0}}}
  ],
  "fields": ["name", "description"]
}

Geo queries combine with others under bool (must, filter, should, must_not), so "in this box but not within 500 m of this point" is a bool with a geo_bounding_box filter and a geo_distance in must_not. To rank closer results higher instead of cutting them off, see Boost nearby results.

Polygons and shapes

geo_polygon matches points inside a polygon given as a list of points, at least three:

{
  "language": "es",
  "query": {"match": {"description": "coffee"}},
  "filters": [{"geo_polygon": {"location": {"points": [
    {"lat": 40.69, "lon": -74.02}, {"lat": 40.69, "lon": -73.97},
    {"lat": 40.74, "lon": -73.97}, {"lat": 40.74, "lon": -74.02}
  ]}}}],
  "fields": ["name"]
}

This matches Bridge Roasters (p2), the only place that mentions coffee inside the area around lower Manhattan and Dumbo. Points can be objects, [lon, lat] arrays or "lat,lon" strings. A geo_polygon has no holes and cannot cross the date line; use geo_shape for those.

geo_shape takes any shape, as a GeoJSON object or a WKT string, and a relation. The same area as WKT, keeping the cafes outside it:

{
  "language": "es",
  "query": {"term": {"category": "cafe"}},
  "filters": [{"geo_shape": {"location": {
    "shape": "POLYGON ((-74.02 40.69, -73.97 40.69, -73.97 40.74, -74.02 40.74, -74.02 40.69))",
    "relation": "disjoint"
  }}}],
  "fields": ["name"]
}

This matches Harbor Coffee (p1) and Hudson Espresso (p5).

{
  "query": {"bool": {
    "must": [{"term": {"category": "cafe"}}],
    "filter": [{"geo_shape": {"location": {
      "shape": {"type": "Polygon", "coordinates": [[
        [-74.02, 40.69], [-73.97, 40.69], [-73.97, 40.74], [-74.02, 40.74], [-74.02, 40.69]
      ]]},
      "relation": "disjoint"
    }}}]
  }}
}
Relation On a geo_point field
intersects (default) Some point of the document is in the shape.
within Every point of the document is in the shape. For a single-valued field this is the same as intersects.
disjoint No point of the document is in the shape. Documents without the field do not match.
contains Needs a geo_shape field; rejected on points.
Shape WKT GeoJSON type
Point, several points POINT, MULTIPOINT Point, MultiPoint
Line, several lines LINESTRING, MULTILINESTRING LineString, MultiLineString
Polygon with holes, several polygons POLYGON, MULTIPOLYGON Polygon, MultiPolygon
Box BBOX (minLon, maxLon, maxLat, minLat) envelope, "coordinates": [[minLon, maxLat], [maxLon, minLat]]
Circle CIRCLE (lon lat radiusMeters) circle, "coordinates": [lon, lat], "radius": "10km"
Mixed GEOMETRYCOLLECTION GeometryCollection

Grid cells

geo_grid matches the documents with a point in one grid cell, named by exactly one of:

Key Cell Example
geohash A geohash, precision 1 to 12 {"geohash": "dr5ru"}
geotile A web map tile, zoom/x/y, zoom 0 to 29 {"geotile": "12/1206/1539"}
geohex An H3 cell, as its index in hex {"geohex": "<H3 index>"}
{
  "language": "es",
  "query": {"term": {"category": "cafe"}},
  "filters": [{"geo_grid": {"location": {"geohash": "dr5ru"}}}],
  "fields": ["name"]
}

The geohash dr5ru covers Midtown, so this matches Harbor Coffee (p1). Invalid keys are rejected. A cell matches exactly the documents that a grid aggregation bucket with the same key counts, so a click on a map cell can fetch exactly the documents that bucket counted.

On a geo_shape field, a geohash or geotile cell matches the shapes that meet the cell, edges included, so a shape on a cell edge matches both cells. geohex cells work on geo_point fields only and answer 400 on shape fields.

View this page as Markdown

Cookie settings

Optional tracking stays off until you choose it. Your choice applies to this browser for 180 days. Read the details.

Essential storage keeps you signed in, protects forms, and remembers this choice. It is always available.