For AI agents: this page is also available as Markdown at /docs/geo-search.md. Every docs page is: add .md to its URL. llms.txt lists them all.
Geo search
Geo search finds documents by where they are. A location is a geo_point field and an area or route is a geo_shape field. A geo condition is a query like any other: a filter beside a text query ("coffee within 5 km"), the whole query ("everything in this polygon"), a sort key ("nearest first"), a scoring signal ("prefer closer results") or an aggregation ("matches per map cell"). It runs in the same request, over the same consistent view of the collection, as your text queries, filters and facets.
#What you can do
| Feature | What it does |
|---|---|
| Distance queries | Match points within a radius of a center. |
| Bounding boxes | Match points inside a latitude and longitude box, including boxes that cross the date line. |
| Point formats | Send points as [lon, lat], objects, GeoJSON, WKT, "lat,lon" or geohashes, and type fields by the _p and _ps suffixes. |
| Sort by distance | Order hits nearest (or farthest) first. |
| Distance decay scoring | Blend text relevance with closeness: full score near a point, fading with distance. |
| Query buckets by distance or area | Count matches within each of several radii or boxes. |
| Polygons and shapes | Match points inside, or outside, a polygon, multipolygon, circle or envelope, or along a line. |
| Grid cells | Match the points in one geohash, map tile or H3 cell. |
| Distances in hits | Each hit of a distance sort carries its distance; /v1 gets a geo_distance sort object. |
| Nearest k | The k documents nearest a point, with filters applied first. |
distance_feature | A scoring clause that rewards closeness without narrowing the matches. |
| Grid aggregations | Geohash, map tile and H3 heat-map buckets, with metrics and facets per cell. |
| Distance rings | Counts per distance band from a point. |
| Bounds and centroid | The box around the matches and their average location. |
| Shape fields | Index areas and lines, such as delivery zones, and query them by intersects, disjoint, within or contains. |
#Pages in this guide
- Geo points and queries: defining
geo_pointfields, the point formats you can send, and the distance, box, polygon, shape and grid-cell queries. - Distance sorting and nearest: sorting by distance, boosting nearby results, nearest-k search and
distance_featurescoring. - Geo aggregations: grids, distance rings, bounds, centroids and query buckets.
- Geo shapes:
geo_shapefields for areas and lines, their queries and aggregations, and the accuracy and index size settings.
#Which API to use
Geo queries have no form in Lucene syntax or SQL. Write them as Elasticsearch Query DSL, or use Solr's spatial parameters:
| Where | How |
|---|---|
WarpSearch API (/v1/collections/{c}/search) | Set "language": "es" and put geo queries in query, filters or post_filters. The geo facet types, geo metrics and the geo_distance sort object work with any language. |
| Elasticsearch and OpenSearch endpoints | Geo queries, the _geo_distance sort and geo aggregations, answered in Elasticsearch's shapes. |
Solr endpoint, or "language": "solr" | {!geofilt}, {!bbox} and sorting by geodist(). |
The MCP search tool takes the same geo queries, facets, metrics and sort; see Guide for AI agents. Searches with geo queries have no native replay form; see Native queries and replays.
#Choosing a feature
- Show only nearby results: a distance, box or polygon query in
filters. Filters do not change scores, so text relevance still orders the hits. - Show the closest first: a filter to bound the area, plus a sort by distance.
- Rank by relevance, but prefer closer: a decay function in
function_score, or adistance_featureclause beside the text query. - "The 5 nearest", whatever the distance:
geo_nearest. Until it is live, filter by a generous radius and sort by distance. - "How many within 1, 5 and 10 km": distance rings, or query buckets today.
- A heat map or clustered map markers: a grid aggregation, then a
geo_gridquery to fetch the documents of the cell someone clicks. - Areas, not points (delivery zones, regions, routes): a
geo_shapefield.
#Quickstart
The examples in this guide use one collection, places: cafes, shops and bakeries around New York, each with a location. Geo shapes adds a delivery area to some of them.
Create the collection with a geo_point field. index: "range" builds a points index, which makes distance and box queries fast on large collections:
curl -X PUT https://api.warpsearch.io/v1/collections/places \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schema": {
"fields": {
"name": "text",
"description": "text",
"category": "string",
"city": "string",
"rating": "double",
"location": {"type": "geo_point", "index": "range"}
}
}
}'
Load a few places. A point is [longitude, latitude], longitude first:
curl -X POST "https://api.warpsearch.io/v1/collections/places/documents?commit=true" \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{"id": "p1", "name": "Harbor Coffee", "description": "Espresso, pour-over and pastries",
"category": "cafe", "city": "Manhattan", "rating": 4.6, "location": [-73.9851, 40.7589]},
{"id": "p2", "name": "Bridge Roasters", "description": "Coffee roastery with a cafe under the bridge",
"category": "cafe", "city": "Brooklyn", "rating": 4.4, "location": [-73.9897, 40.7033]},
{"id": "p3", "name": "Midtown Market", "description": "Groceries, deli and a coffee bar",
"category": "grocery", "city": "Manhattan", "rating": 4.1, "location": [-73.9772, 40.7527]},
{"id": "p4", "name": "Astoria Bakery", "description": "Bread, pastries and coffee",
"category": "bakery", "city": "Queens", "rating": 4.7, "location": [-73.9235, 40.7644]},
{"id": "p5", "name": "Hudson Espresso", "description": "Espresso bar by the ferry",
"category": "cafe", "city": "Hoboken", "rating": 4.5, "location": [-74.0324, 40.7440]}
]'
Find the cafes within 5 km of Times Square (40.7580, -73.9855), nearest first:
curl -X POST https://api.warpsearch.io/v1/collections/places/search \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"language": "es",
"query": {
"query": {"bool": {"filter": [
{"term": {"category": "cafe"}},
{"geo_distance": {"distance": "5km", "location": "40.7580,-73.9855"}}
]}},
"sort": [{"_geo_distance": {"location": "40.7580,-73.9855", "order": "asc", "unit": "km"}}]
},
"fields": ["name", "city"]
}'
curl -X POST https://api.warpsearch.io/compat/es/places/_search \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": {"bool": {"filter": [
{"term": {"category": "cafe"}},
{"geo_distance": {"distance": "5km", "location": "40.7580,-73.9855"}}
]}},
"sort": [{"_geo_distance": {"location": "40.7580,-73.9855", "order": "asc", "unit": "km"}}],
"_source": ["name", "city"]
}'
The same body works on /compat/opensearch/places/_search.
d is in kilometers and pt is lat,lon:
curl "https://api.warpsearch.io/compat/solr/places/select" \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
--data-urlencode "q=*:*" \
--data-urlencode "fq=category:cafe" \
--data-urlencode "fq={!geofilt sfield=location pt=40.7580,-73.9855 d=5}" \
--data-urlencode "sort=geodist(location,40.7580,-73.9855) asc" \
--data-urlencode "fl=id,name,city"
The hits are p1 (Harbor Coffee, about 0.1 km away), then p5 (Hudson Espresso, about 4.2 km). Bridge Roasters, 6.1 km away, is outside the radius.
#Limits
These apply to geo queries and aggregations:
| Limit | Value |
|---|---|
| Vertices in a query shape or polygon | 10000 |
k of geo_nearest | Your plan's result window |
| Cells a grid aggregation returns | Your plan's maximum facet size. The default size is 10000, or that maximum if it is lower |
| Distinct cells a grid aggregation counts | 65536 |
| Ranges of a distance-ring aggregation | Your plan's maximum facet size |
Going past a limit answers 422 limit_exceeded, except the distinct-cell count, which fails the search with an error. See Limits and errors.
Distances are great-circle (haversine) distances. Elasticsearch's distance_type: plane is accepted with a warning. Query centers can be "lat,lon", [lon, lat], {"lat": ..., "lon": ...} or "POINT (lon lat)"; geohash centers are rejected.