Skip to main content

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.

FunctionDescription
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.
field <-> centroidOperator synonym for ST_Distance_Centroid.
Index-scan context

These functions are predicates evaluated by the index, not scalar functions you can call in a bare SELECT list. Use them in the WHERE clause of a query against an indexed table (or its _idx alias). ST_Distance_Centroid in particular returns the distance only inside an index scan; selecting it as a plain projection raises "Inverted index function called outside inverted index context."

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.

ParameterTypeDescription
fieldindexed JSON / GEOMETRYThe indexed geometry column.
shapeGeoJSON text or GEOMETRYThe query shape to test for overlap.
Query
SELECT idFROM geo_idxWHERE ST_Intersects(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'))ORDER BY id;
Result
 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]):

Query
SELECT idFROM geo_idxWHERE ST_Intersects(geo, '{"type": "Point", "coordinates": [37.6, 55.7]}')ORDER BY id;
Result
 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(...).

Behavior matrix. Against a fixed query polygon — the square [0,0]–[10,10] — over a small set of shapes:

RowShapeIntersects [0,0]–[10,10]?Why
red_squarepolygon [0,0]–[10,10]identical to the query — fully overlaps
inner_pointpoint (5,5)sits inside the square
edge_pointpoint (10,5)lies on the right edge — boundary counts
overlap_boxpolygon [5,5]–[15,15]overlaps the upper-right quadrant
far_pointpoint (50,50)well outside the square
Query
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;
Result
 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.

ParameterTypeDescription
aGeoJSON text / GEOMETRY / indexed columnThe containing ("outer") shape.
bGeoJSON text / GEOMETRY / indexed columnThe contained ("inner") shape.
Query
SELECT idFROM geo_idxWHERE ST_Contains(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'))ORDER BY id;
Result
 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:

    Query
    SELECT id, nameFROM geo_demo_idxWHERE ST_Contains(        '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}',        shape     )ORDER BY id;
    Result
     id | name----+-------------  1 | red_square  2 | inner_point  3 | edge_point
    RowShapeInside [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):

    Query
    SELECT id, nameFROM geo_demo_idxWHERE ST_Contains(shape, '{"type":"Point","coordinates":[5,5]}')ORDER BY id;
    Result
     id | name----+-------------  1 | red_square  2 | inner_point
    RowShapeContains 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 contained
    edge_point, far_pointpointsa point can only contain itself

Swapping the arguments flips the result set, so always frame the containing shape first.

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.

ParameterTypeDefaultDescription
fieldindexed JSON / GEOMETRYThe indexed geometry column.
centroidGeoJSON / GEOMETRY pointThe point distances are measured from.
minDOUBLE (metres)Lower bound of the distance band.
maxDOUBLE (metres)Upper bound of the distance band.
incl_minBOOLEANtrueWhether min itself matches.
incl_maxBOOLEANtrueWhether max itself matches.
Query
SELECT idFROM geo_idxWHERE ST_Distance_Between(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84'), 0, 5000)ORDER BY id;
Result
 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:

Query
SELECT id, nameFROM cities_idxWHERE ST_Distance_Between(loc, 'POINT(37.617499 55.752023)'::GEOMETRY('OGC:CRS84'), 300, 3000)ORDER BY id;
Result
 id | name----+------------  3 | gorky_park
RowApprox. distanceIn band [300, 3000]?Why
kremlin~0 mbelow min (it is the centre)
red_square~280 mbelow min (300 m)
gorky_park~2.5 kminside the band
spb_palace~630 kmfar 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.

ParameterTypeDescription
fieldindexed JSON / GEOMETRYThe indexed geometry column.
centroidGeoJSON / GEOMETRY pointThe point distances are measured from.
Query
SELECT idFROM geo_idxWHERE ST_Distance_Centroid(geo, 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84')) < 5000ORDER BY id;
Result
 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:

Query
SELECT id, nameFROM cities_idxWHERE ST_Distance_Centroid(loc, 'POINT(37.617499 55.752023)'::GEOMETRY('OGC:CRS84')) < 3000ORDER BY id;
Result
 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).

field <-> centroid

The <-> operator is a synonym for ST_Distance_Centroid: field <-> centroid is exactly equivalent to ST_Distance_Centroid(field, centroid), used the same way in a WHERE comparison within an index scan. It reads naturally as a distance and keeps radius queries terse:

Query
SELECT idFROM geo_idxWHERE (geo <-> 'POINT(37.6 55.7)'::GEOMETRY('OGC:CRS84')) < 5000ORDER BY id;
Result
 id----  1

Coming from Elasticsearch

Elasticsearch / OpenSearch geo queries map onto SereneDB's ST_* predicates as follows (the left column links to the Elasticsearch reference):

Elasticsearch / OpenSearchSereneDB
geo_shape INTERSECTS (default relation)ST_Intersects
geo_shape WITHINST_Contains(query_shape, field)
geo_shape CONTAINSST_Contains(field, query_shape)
geo_distanceST_Distance_Centroid(...) < r, ST_Distance_Between
geo_distance ring (two clauses)ST_Distance_Between (single call, inclusive flags)
geo_bounding_boxST_Intersects with a rectangular Polygon
geo_polygonST_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 / OpenSearchSereneDB
geo_shape DISJOINT relationno ST_Disjoint; use NOT ST_Intersects(...)

See also