Geo Functions
Geospatial Functions
These predicates run inside a geospatial search over an inverted index. They answer the common spatial questions: which shapes overlap a region, which region encloses a point, what falls within a distance band of a location.
The indexed column must be JSON (holding GeoJSON) or GEOMETRY — a plain VARCHAR is rejected for geo analyzers. The query shape passed as the other argument may be supplied either way: as GeoJSON text or as a GEOMETRY literal. In GeoJSON and WKT alike, coordinates are written [longitude, latitude] (x then y), not lat/lon.
| Function | Description |
|---|---|
ST_Intersects(field, shape) | True where the indexed geometry shares any space with shape. Commutative. |
ST_Contains(a, b) | True where a fully contains b. Direction-dependent — argument order matters. |
ST_Distance_Between(field, centroid, min, max [, incl_min [, incl_max]]) | True where the geodesic distance to centroid falls in [min, max]. |
ST_Distance_Centroid(field, centroid) | Geodesic distance (metres) from the indexed geometry to centroid. |
ST_Intersects(field, shape)
True for rows whose indexed geometry shares any space with shape — touching an edge or a single point counts. This is the workhorse predicate: a bounding box, a neighbourhood polygon or a single point all flow through the same call.
| Parameter | Type | Description |
|---|---|---|
field | indexed JSON / GEOMETRY | The indexed geometry column. |
shape | GeoJSON text or GEOMETRY | The query shape to test for overlap. |
SELECT idFROM geo_idxWHERE ST_Intersects(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'))ORDER BY id;id ---- 1
The query shape may equally be supplied as GeoJSON text instead of a GEOMETRY literal — every geo function accepts either form (GeoJSON coordinates are [longitude, latitude]):
SELECT idFROM geo_idxWHERE ST_Intersects(geo, '{"type": "Point", "coordinates": [37.6, 55.7]}')ORDER BY id;id ---- 1
How it works. ST_Intersects is commutative: ST_Intersects(field, shape) and ST_Intersects(shape, field) return the same rows, so argument order is free. Intersection includes shared boundaries — a point lying exactly on a polygon's edge intersects it. It is the complement of disjointness, so "find everything not near this region" is NOT ST_Intersects(...).
Testing a Point or MultiPoint against a LineString or MultiLineString, in either order, is not supported: the query fails with The case GEO_INTERSECTS(<some points>, <some polylines>) is numerically unstable and thus not supported.
Behavior matrix. Against a fixed query polygon — the square [0,0]–[10,10] — over a small set of shapes:
| Row | Shape | Intersects [0,0]–[10,10]? | Why |
|---|---|---|---|
red_square | polygon [0,0]–[10,10] | ✅ | identical to the query — fully overlaps |
inner_point | point (5,5) | ✅ | sits inside the square |
edge_point | point (10,5) | ✅ | lies on the right edge — boundary counts |
overlap_box | polygon [5,5]–[15,15] | ✅ | overlaps the upper-right quadrant |
far_point | point (50,50) | ❌ | well outside the square |
SELECT id, nameFROM geo_demo_idxWHERE ST_Intersects( shape, '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}' )ORDER BY id;id | name ----+------------- 1 | red_square 2 | inner_point 3 | edge_point 5 | overlap_box
ST_Contains(a, b)
True where a fully contains b — every part of b lies inside a (boundary included). Unlike ST_Intersects, ST_Contains is directional: the order of arguments decides the question being asked.
| Parameter | Type | Description |
|---|---|---|
a | GeoJSON text / GEOMETRY / indexed column | The containing ("outer") shape. |
b | GeoJSON text / GEOMETRY / indexed column | The contained ("inner") shape. |
SELECT idFROM geo_idxWHERE ST_Contains(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'))ORDER BY id;id ---- 1
How it works — argument order matters. Put the indexed field in whichever slot frames your question:
-
ST_Contains(query_shape, field)— which indexed geometries fall inside the query region? The query shape is the outer boundary; matches are the rows contained within it. Over the same[0,0]–[10,10]square:SQLSELECT id, nameFROM geo_demo_idxWHERE ST_Contains( '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}', shape )ORDER BY id;id | name ----+------------- 1 | red_square 2 | inner_point 3 | edge_point
Row Shape Inside [0,0]–[10,10]?Why red_squarepolygon [0,0]–[10,10]✅ equal shapes — contained (boundary included) inner_pointpoint (5,5)✅ strictly inside edge_pointpoint (10,5)✅ on the boundary — still contained overlap_boxpolygon [5,5]–[15,15]❌ spills outside the square far_pointpoint (50,50)❌ outside -
ST_Contains(field, query_shape)— which indexed geometries enclose this location? The field is the outer shape; a point query finds the polygon(s) covering it. Over the same data with the query point(5,5):SQLSELECT id, nameFROM geo_demo_idxWHERE ST_Contains(shape, '{"type":"Point","coordinates":[5,5]}')ORDER BY id;id | name ----+------------- 1 | red_square 2 | inner_point
Row Shape Contains point (5,5)?Why red_squarepolygon [0,0]–[10,10]✅ the point is inside it inner_pointpoint (5,5)✅ a geometry contains itself overlap_boxpolygon [5,5]–[15,15]❌ (5,5)is its corner, not its interior — not containededge_point,far_pointpoints ❌ a point can only contain itself
Swapping the arguments flips the result set, so always frame the containing shape first.
A line never contains a point: ST_Contains of a LineString or MultiLineString with a Point or MultiPoint is false even when every point lies on the line. A point or a line never contains a shape of a higher dimension either, so ST_Contains of a Point or MultiPoint with a line or a polygon, and of a line with a polygon, is false.
ST_Distance_Between(field, centroid, min, max [, incl_min [, incl_max]])
True for rows whose geodesic distance to centroid falls inside the [min, max] band — a distance ring around a point.
| Parameter | Type | Default | Description |
|---|---|---|---|
field | indexed JSON / GEOMETRY | — | The indexed geometry column. |
centroid | GeoJSON / GEOMETRY point | — | The point distances are measured from. |
min | DOUBLE (metres) | — | Lower bound of the distance band. |
max | DOUBLE (metres) | — | Upper bound of the distance band. |
incl_min | BOOLEAN | true | Whether min itself matches. |
incl_max | BOOLEAN | true | Whether max itself matches. |
SELECT idFROM geo_idxWHERE ST_Distance_Between(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'), 0, 5000)ORDER BY id;id ---- 1
How it works. Distance is geodesic — measured over the curve of the Earth in metres, not in coordinate degrees — so a 3 km band means 3 km on the ground regardless of latitude. Setting min = 0 gives a plain radius query; a non-zero min excludes a hole in the middle, which is how you express "between 300 m and 3 km away". Around the Kremlin (POINT(37.617499 55.752023)) over four points, the band [300, 3000] keeps only the mid-ring location:
SELECT id, nameFROM cities_idxWHERE ST_Distance_Between(loc, 'POINT(37.617499 55.752023)'::GEOMETRY('OGC:CRS84'), 300, 3000)ORDER BY id;id | name ----+------------ 3 | gorky_park
| Row | Approx. distance | In band [300, 3000]? | Why |
|---|---|---|---|
kremlin | ~0 m | ❌ | below min (it is the centre) |
red_square | ~280 m | ❌ | below min (300 m) |
gorky_park | ~2.5 km | ✅ | inside the band |
spb_palace | ~630 km | ❌ | far above max |
ST_Distance_Centroid(field, centroid)
Geodesic distance, in metres, from the indexed geometry's centroid to centroid. Used inside an index scan as the left side of a comparison — a radius query reads as ST_Distance_Centroid(...) < radius.
| Parameter | Type | Description |
|---|---|---|
field | indexed JSON / GEOMETRY | The indexed geometry column. |
centroid | GeoJSON / GEOMETRY point | The point distances are measured from. |
SELECT idFROM geo_idxWHERE ST_Distance_Centroid(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84')) < 5000ORDER BY id;id ---- 1
How it works. A bare < radius comparison is a circle around centroid. Around the Kremlin, a 3 km radius keeps the three nearby points and drops the one ~630 km away:
SELECT id, nameFROM cities_idxWHERE ST_Distance_Centroid(loc, 'POINT(37.617499 55.752023)'::GEOMETRY('OGC:CRS84')) < 3000ORDER BY id;id | name ----+------------ 1 | kremlin 2 | red_square 3 | gorky_park
ST_Distance_Centroid(field, c) < r is the open-disc form of ST_Distance_Between(field, c, 0, r, true, false).
a <-> b
As in PostGIS, <-> between two GEOMETRY values is their planar distance in coordinate units — the same value as ST_Distance(a, b). It is an ordinary scalar that works in any expression, ORDER BY included, but it takes only GEOMETRY operands (not JSON) and the index does not answer it.
SELECT id, round(geo <-> 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'), 4) AS degreesFROM geoORDER BY geo <-> 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84');id | degrees ----+--------- 1 | 0 2 | 0.1414
Coming from Elasticsearch
Elasticsearch / OpenSearch geo queries map onto SereneDB's ST_* predicates as follows (the left column links to the Elasticsearch reference):
| Elasticsearch / OpenSearch | SereneDB |
|---|---|
geo_shape INTERSECTS (default relation) | ST_Intersects |
geo_shape WITHIN | ST_Contains(query_shape, field) |
geo_shape CONTAINS | ST_Contains(field, query_shape) |
geo_distance | ST_Distance_Centroid(...) < r, ST_Distance_Between |
geo_distance ring (two clauses) | ST_Distance_Between (single call, inclusive flags) |
geo_bounding_box | ST_Intersects with a rectangular Polygon |
geo_polygon | ST_Intersects / ST_Contains with a Polygon |
Notable differences. SereneDB folds bounding-box and polygon queries into the same ST_Intersects / ST_Contains predicates rather than offering them as distinct query types, and expresses a distance ring in a single ST_Distance_Between call.
| Elasticsearch / OpenSearch | SereneDB |
|---|---|
geo_shape DISJOINT relation | no ST_Disjoint; use NOT ST_Intersects(...) |
See also
- Full-Text Search Functions — operators, constructors, parsers
- Inverted Index · Geospatial Search
GEOMETRYdata type · CREATE TEXT SEARCH DICTIONARY