Search
Fuzzy place search over the OSM extract, plus spatial relationship queries — which areas contain a point, and the children of an area.
Search places
Unified search endpoint supporting text search, category browsing, and route corridor search.
Text search (provide query): Runs a hybrid four-layer pipeline:
- Full-text search (FTS) via
tsvectorGIN index - Trigram fuzzy match via
pg_trgmGIN index - Abbreviation match on pre-computed
name_abbrev - Semantic vector search via
pgvector(conditional)
Results are deduplicated in priority order (FTS > abbreviation > trigram > semantic) then re-ranked with proximity decay when coordinates are provided.
Browse mode (omit query, provide categories and/or tags): Returns matching places sorted by distance. Requires a spatial constraint (lat/lng or route).
Spatial modes:
- Point:
lat+lng+ optionalradius— search within a circular area - Route corridor:
route(GeoJSON LineString) + optionalbuffer— search along a path with exponential proximity decay
Both modes can be combined with text search and/or category/tag filters.
Request Body
application/jsonRequiredquerystringSearch query text. When omitted, returns places matching categories/tags sorted by proximity (browse mode).
latnumberLatitude for point-based spatial filtering and proximity ranking (WGS 84).
lngnumberLongitude for point-based spatial filtering and proximity ranking (WGS 84).
radiusnumberSearch radius in meters around the lat/lng point. Max recommended: 50000.
routeobjectGeoJSON LineString geometry. When provided, search is constrained to a corridor around this route instead of a point radius.
buffernumberCorridor width in meters when using route mode. Places closer to the route are strongly preferred via exponential decay ranking.
1000categoriesarray<string>OSM preset category IDs to filter by (e.g. ["fuel", "cafe"]). Multiple values are OR'd together.
tagsobjectAdditional OSM tag key/value pairs that must all be present (JSONB containment).
limitnumberMaximum number of results to return.
20offsetnumberNumber of results to skip for pagination (browse mode only).
0semanticbooleanForce semantic vector search for concept queries (e.g. "somewhere quiet to study"). Requires Ollama.
falseautocompletebooleanEnable autocomplete mode — skips the slow semantic layer for low-latency typeahead.
falseResponse Body
curl -X POST "https://example.com/search" \
-H "Content-Type: application/json" \
-d '{
"query": "coffee",
"lat": 35.2271,
"lng": -80.8431,
"radius": 1000,
"route": {
"type": "LineString",
"coordinates": [
[
0,
0
]
]
},
"buffer": 500,
"categories": [
"fuel"
],
"tags": {
"cuisine": "pizza"
},
"limit": 10,
"offset": 0,
"semantic": false,
"autocomplete": false
}'const body = JSON.stringify({
"query": "coffee",
"lat": 35.2271,
"lng": -80.8431,
"radius": 1000,
"route": {
"type": "LineString",
"coordinates": [
[
0,
0
]
]
},
"buffer": 500,
"categories": [
"fuel"
],
"tags": {
"cuisine": "pizza"
},
"limit": 10,
"offset": 0,
"semantic": false,
"autocomplete": false
})
fetch("https://example.com/search", {
body
})package main
import (
"fmt"
"net/http"
"io/ioutil"
"strings"
)
func main() {
url := "https://example.com/search"
body := strings.NewReader(`{
"query": "coffee",
"lat": 35.2271,
"lng": -80.8431,
"radius": 1000,
"route": {
"type": "LineString",
"coordinates": [
[
0,
0
]
]
},
"buffer": 500,
"categories": [
"fuel"
],
"tags": {
"cuisine": "pizza"
},
"limit": 10,
"offset": 0,
"semantic": false,
"autocomplete": false
}`)
req, _ := http.NewRequest("POST", url, body)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}import requests
url = "https://example.com/search"
body = {
"query": "coffee",
"lat": 35.2271,
"lng": -80.8431,
"radius": 1000,
"route": {
"type": "LineString",
"coordinates": [
[
0,
0
]
]
},
"buffer": 500,
"categories": [
"fuel"
],
"tags": {
"cuisine": "pizza"
},
"limit": 10,
"offset": 0,
"semantic": false,
"autocomplete": false
}
response = requests.request("POST", url, json = body, headers = {
"Content-Type": "application/json"
})
print(response.text)Find areas containing a point
Returns all named area geometries whose polygons contain the given coordinate point. Results are ordered smallest-first (innermost area first), so the most specific containing region appears at index 0.
Typical use: reverse-geocode a coordinate to its administrative hierarchy (building → neighbourhood → city → county → state → country) or find which venue/campus a point is inside.
Only areas with names are returned. building:part features are excluded. Both centroid and full GeoJSON geometry are included in the response.
Query Parameters
latRequiredstringLatitude of the point to test containment for (WGS 84).
lngRequiredstringLongitude of the point to test containment for (WGS 84).
excludestringBarrelman place ID to exclude from results. Useful to omit the place you are currently viewing (e.g. the place whose detail page you are on).
Response Body
curl -X GET "https://example.com/contains?lat=string&lng=string&exclude=string"fetch("https://example.com/contains?lat=string&lng=string&exclude=string")package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://example.com/contains?lat=string&lng=string&exclude=string"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}import requests
url = "https://example.com/contains?lat=string&lng=string&exclude=string"
response = requests.request("GET", url)
print(response.text)Find children of an area
Returns places whose centroids fall within the geometry of the given parent area (e.g. all shops inside a mall, all POIs inside a university campus).
Sorting priority:
- Named places first (unnamed amenities ranked lower)
- Places with recognized categories before unclassified features
- Proximity to
lat/lngif provided, otherwise proximity to the parent area's centroid - Alphabetical by name as a tiebreaker
Category filtering applies only to unnamed children — named places inside the area are always returned, allowing landmark buildings and venues to appear even when filtering for a specific type.
building:part features are always excluded to avoid surfacing architectural sub-elements.
Query Parameters
idRequiredstringBarrelman place ID of the parent area (e.g. way/123456). Must be an area geometry (geom_type = 'area').
categoriesstringComma-separated list of OSM preset category IDs to filter unnamed children by. Named places inside the area are always included regardless of this filter.
limitstringMaximum number of children to return.
offsetstringNumber of children to skip for pagination.
latstringLatitude for proximity sorting. When provided, results closer to this point are ranked first. Falls back to parent centroid when omitted.
lngstringLongitude for proximity sorting. When provided, results closer to this point are ranked first. Falls back to parent centroid when omitted.
Response Body
curl -X GET "https://example.com/children?id=string&categories=string&limit=string&offset=string&lat=string&lng=string"fetch("https://example.com/children?id=string&categories=string&limit=string&offset=string&lat=string&lng=string")package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://example.com/children?id=string&categories=string&limit=string&offset=string&lat=string&lng=string"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}import requests
url = "https://example.com/children?id=string&categories=string&limit=string&offset=string&lat=string&lng=string"
response = requests.request("GET", url)
print(response.text)