Skip to content

Spatial index

Bulk spatial indexing for repeated relationship queries.

Related: Spatial indexing & joins

SpatialIndex

Accepted call forms
SpatialIndex(values: Geometry | GeometryArray[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface | None] | None = None) -> SpatialIndex

A packed STR-tree over geometries sharing one CRS/epoch frame.

Built by SpatialIndex(geoms): ask set questions against the indexed geometries — exact predicate matches (idx.query(geom)), bounding-box candidates (idx.candidates(geom)), proximity (idx.nearest(geom)), self-joins (idx.self_join()) — and mutate it incrementally with insert/remove. Distances follow the indexed CRS: meters on a geographic frame, native linear units on a projected frame, raw coordinate units when CRS-free.

Build a spatial index (STR-tree) over present geometries.

Parameters:

Name Type Description Default
values Geometry | GeometryArray[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface | None] | None

Geometries to index. Iterable rows may be native geometries, WKB buffers, GeoJSON mappings, or objects exposing __geo_interface__. None builds an empty mutable index for later insert calls. Every indexed geometry must share one CRS and coordinate epoch. Missing rows are skipped but retain their original positions as stable, non-live handles, so query and join results always refer to the input row ids. Packed arrays retain their immutable column storage; empty rows likewise have no live tree entry.

None

Returns:

Type Description
SpatialIndex

A mutable STR-tree whose handles refer to the original input rows.

Raises:

Type Description
CRSMismatchError

If items carry conflicting CRS or coordinate-epoch metadata.

See Also

join : High-level spatial join built on the index.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(5, 5, 6, 6)])
>>> [int(i) for i in idx.query(gm.Point(0.5, 0.5), predicate='intersects')]
[0]

crs property

crs: CRS | None

CRS shared by the indexed geometries, or None for an unframed index.

Returns:

Type Description
CRS or None

epoch property

epoch: float | None

Coordinate epoch shared by the indexed geometries, if set.

Returns:

Type Description
float or None

__contains__

__contains__(handle: object, /) -> bool

Whether handle is a live geometry handle.

Non-integer probes return False instead of raising, matching Python's container protocol.

Returns:

Type Description
bool

__getitem__

__getitem__(handle: SupportsIndex, /) -> Geometry

Return the geometry at a live handle.

The handle must be a non-negative integer accepted by __index__; bool is not accepted. Raises KeyError when a valid handle is unknown or has been removed.

Returns:

Type Description
Geometry

__iter__

__iter__() -> SpatialIndexIterator

Iterate live handles lazily in ascending handle order.

Returns:

Type Description
iterator of int

__len__

__len__() -> int

Number of live (non-removed) geometries in the index.

Returns:

Type Description
int

candidates

Accepted call forms
candidates(geom: Geometry, *, distance: float | None = None, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]]
candidates(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, distance: float | None = None, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]]]
candidates(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, distance: float | None = None, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]] | None]
candidates(geom: Geometry | (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, distance: float | None = None, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]] | Groups[ndarray[tuple[int], dtype[int64]] | None]

Bounding-box candidate matches for a query geometry, array, or iterable (not exact). If you refine these candidates with a directional predicate, evaluate it as predicate(query_geometry, indexed_geometry).

A single geometry returns an int64 ndarray; arrays and iterables return Groups — one candidate row per input row.

Parameters:

Name Type Description Default
geom Geometry, GeometryArray, or iterable of Geometry or None

One query geometry or one query per sequence row. Missing rows produce None groups; present rows with no candidates produce empty groups.

required
distance float

Expand the query envelope by this much, in unit. Finite non-negative expansions and positive infinity are accepted; infinity admits every live non-empty row for a query with bounds.

None
unit ('planar', 'meters')

None (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). 'planar' uses Cartesian coordinate space, with geographic coordinates in their native angular units. 'meters' uses the CRS-aware metric and requires a CRS.

'planar'

Returns:

Type Description
int64 numpy.ndarray or Groups

Source-row handles in ascending order.

Raises:

Type Description
CRSMismatchError

If the query does not share the index's CRS/epoch frame.

GeometryError

If a query parameter is invalid, or unit='meters' is requested for a CRS-free geometry.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)])
>>> idx.candidates(gm.box(0, 0, 1, 1)).tolist()
[0]

get

Accepted call forms
get(handle: SupportsIndex, /) -> Geometry | None
get(handle: SupportsIndex, /, default: Geometry | DefaultT) -> Geometry | DefaultT

Return the geometry at handle, or default when it is not live.

Parameters:

Name Type Description Default
handle SupportsIndex

A non-negative integer/index handle (positional-only; bool is rejected). Invalid or negative handles raise instead of returning default.

required
default object

Value returned when the handle is not live.

required

Returns:

Type Description
Geometry or object

The live geometry, else default.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1)])
>>> g = idx.get(0)
>>> g is not None and g.to_wkt().startswith('POLYGON')
True

insert

Accepted call forms
insert(values: Geometry) -> int
insert(values: GeometryArray[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface]) -> ndarray[tuple[int], dtype[int64]]
insert(values: Geometry | GeometryArray[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface]) -> int | ndarray[tuple[int], dtype[int64]]

Insert one geometry or many geometries and return their stable handles.

A single Geometry returns one int handle. A GeometryArray or generic iterable of geometries returns a read-only int64 ndarray of handles in input order. Batch inserts follow the same frame rules as scalar insert: the first inserted row fixes an empty index's CRS/epoch frame, and later inserts must match it. A GeometryArray containing a missing row is rejected. All input-validation failures leave the index unchanged; this is not a transaction guarantee for allocation or publication failures.

Parameters:

Name Type Description Default
values Geometry or GeometryArray or iterable of Geometry

Values to append to the index; all must share the index's CRS/epoch frame. Empty geometries and missing GeometryArray rows cannot be inserted.

required

Returns:

Type Description
int or ndarray

Stable handle for a scalar insert, or stable handles assigned to a batch insert in input order as a read-only int64 ndarray.

Raises:

Type Description
CRSMismatchError

If the geometry or geometries do not share the index's CRS/epoch frame.

GeometryError

If any inserted geometry is empty, or a GeometryArray contains a missing row.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([])
>>> idx.insert(gm.Point(1, 1))
0

items

items() -> ItemsView[int, Geometry]

Return a dynamic view of (handle, geometry) pairs.

Returns:

Type Description
ItemsView

(handle, geometry) pairs for the live handles.

Examples:

>>> import gometry as gm
>>> list(gm.SpatialIndex([gm.box(0, 0, 1, 1)]).items())[0][0]
0

join

Accepted call forms
join(queries: Geometry | (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface | None], *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> tuple[ndarray[tuple[int], dtype[int64]], ndarray[tuple[int], dtype[int64]]]
join(queries: Geometry | (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None] | Iterable[Geometry | Buffer | Mapping[str, Any] | SupportsGeoInterface | None], *, predicate: Literal['intersects', 'contains', 'contains_properly', 'covers', 'within', 'covered_by', 'equals', 'touches', 'crosses', 'overlaps'] = 'intersects', distance: None = None, unit: Literal['planar', 'meters'] | None = None) -> tuple[ndarray[tuple[int], dtype[int64]], ndarray[tuple[int], dtype[int64]]]

Join query rows against this prebuilt index.

Reuses the index instead of rebuilding the right side on every call. Predicate orientation is predicate(query, indexed_geometry), exactly matching free-function join(queries, indexed_values, ...). Missing query rows produce no pairs; missing rows skipped while building the index retain their original handles.

Parameters:

Name Type Description Default
queries Geometry, GeometryArray, or iterable of Geometry or None

Left-side geometries to join against the indexed rows.

required
predicate str

Spatial predicate each returned pair must satisfy. Topological forms omit predicate to use 'intersects'; 'dwithin' forms require an explicit predicate and distance.

varies by call form
distance float

Required when predicate='dwithin': the maximum separation in CRS-natural units. Finite non-negative values and positive infinity are accepted; missing query rows produce no pairs.

None
unit ('planar', 'meters')

None (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). 'planar' uses Cartesian coordinate space, with geographic coordinates in their native angular units. 'meters' uses the CRS-aware metric and requires a CRS.

'planar'

Returns:

Type Description
tuple of numpy.ndarray

(query_ids, handles) parallel read-only int64 columns.

Raises:

Type Description
CRSMismatchError

If the query and index CRS/coordinate-epoch frames differ.

GeometryError

If a predicate or distance option is invalid.

CRSError

If geographic dwithin refinement cannot prove a result within the point-edge evaluation budget.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)])
>>> left, right = idx.join(gm.GeometryArray([gm.Point(0.5, 0.5)]))
>>> (left.tolist(), right.tolist())
([0], [0])

keys

keys() -> KeysView[int]

Return a dynamic view of the live handles.

Returns:

Type Description
KeysView

The live handles, in ascending order.

Examples:

>>> import gometry as gm
>>> list(gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)]).keys())
[0, 1]

nearest

Accepted call forms
nearest(geom: Geometry, *, k: int = 1, max_distance: float | None = None, return_distance: Literal[False] = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> ndarray[tuple[int], dtype[int64]]
nearest(geom: Geometry, *, k: int = 1, max_distance: float | None = None, return_distance: Literal[True], unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> tuple[ndarray[tuple[int], dtype[int64]], NDArray[float64]]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, k: int = 1, max_distance: float | None = None, return_distance: Literal[False] = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> Groups[ndarray[tuple[int], dtype[int64]]]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, k: int = 1, max_distance: float | None = None, return_distance: Literal[False] = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> Groups[ndarray[tuple[int], dtype[int64]] | None]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, k: int = 1, max_distance: float | None = None, return_distance: Literal[True], unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> tuple[Groups[ndarray[tuple[int], dtype[int64]]], NDArray[float64]]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, k: int = 1, max_distance: float | None = None, return_distance: Literal[True], unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> tuple[Groups[ndarray[tuple[int], dtype[int64]] | None], NDArray[float64]]
nearest(geom: Geometry, *, k: int = 1, max_distance: float | None = None, return_distance: bool = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> NDArray[int64] | tuple[NDArray[int64], NDArray[float64]]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, k: int = 1, max_distance: float | None = None, return_distance: bool = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> Groups[ndarray[tuple[int], dtype[int64]]] | tuple[Groups[ndarray[tuple[int], dtype[int64]]], NDArray[float64]]
nearest(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, k: int = 1, max_distance: float | None = None, return_distance: bool = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> Groups[NDArray[int64] | None] | tuple[Groups[NDArray[int64] | None], NDArray[float64]]
nearest(geom: Geometry | (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, k: int = 1, max_distance: float | None = None, return_distance: bool = False, unit: Literal['planar', 'meters'] | None = None, exclusive: bool = False, ties: bool = False) -> ndarray[tuple[int], dtype[int64]] | Groups[ndarray[tuple[int], dtype[int64]] | None] | tuple[ndarray[tuple[int], dtype[int64]], NDArray[float64]] | tuple[Groups[ndarray[tuple[int], dtype[int64]] | None], NDArray[float64]]

Nearest indexed geometries to the query.

Parameters:

Name Type Description Default
geom Geometry, GeometryArray, or iterable of Geometry or None

One query geometry or one query per sequence row. Missing rows produce None groups and retain their logical positions.

required
k int

How many nearest neighbors to return; 0 returns no neighbors.

1
max_distance float

Ignore matches farther than this, in unit. Must be finite and non-negative.

None
return_distance bool

Return distances alongside handles — (indices, distances) for a scalar query, (matches, distances) for an array query. The distance-returning form requires return_distance=True.

False
unit ('planar', 'meters')

None (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). 'planar' uses Cartesian coordinate space, with geographic coordinates in their native angular units. 'meters' uses the CRS-aware metric and requires a CRS.

'planar'
exclusive bool

Skip an indexed geometry equal to the query (self-matches in joins over the indexed set itself).

False
ties bool

Also return every geometry TYING the k-th nearest distance (exact comparison) — results can then exceed k.

False

Returns:

Type Description
int64 numpy.ndarray, Groups, or tuple

The nearest handles — an int64 ndarray for a scalar query, CSR Groups for an array query. With return_distance=True, plain tuple field order is (indices, distances) for a scalar query or (matches, distances) for an array query (distances parallel to matches.values).

Raises:

Type Description
CRSMismatchError

If the query does not share the index's CRS/epoch frame.

GeometryError

If k or max_distance is invalid, or unit='meters' is requested for a CRS-free geometry.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)])
>>> idx.nearest(gm.Point(4, 4)).tolist()
[1]

query

Accepted call forms
query(geom: Geometry, *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]]
query(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]]]
query(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]] | None]
query(geom: Geometry, *, predicate: Literal['intersects', 'contains', 'contains_properly', 'covers', 'within', 'covered_by', 'equals', 'touches', 'crosses', 'overlaps'] = 'intersects', distance: None = None, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]]
query(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, predicate: Literal['intersects', 'contains', 'contains_properly', 'covers', 'within', 'covered_by', 'equals', 'touches', 'crosses', 'overlaps'] = 'intersects', distance: None = None, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]]]
query(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry | None], *, predicate: Literal['intersects', 'contains', 'contains_properly', 'covers', 'within', 'covered_by', 'equals', 'touches', 'crosses', 'overlaps'] = 'intersects', distance: None = None, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]] | None]

Return exact predicate-refined matches for a query geometry, array, or iterable of geometry rows.

Topological predicates use 'intersects' when predicate is omitted; 'dwithin' requires an explicit predicate and distance. Use candidates for a bounding-box-only prefilter. A single geometry returns an int64 ndarray; arrays and iterables return Groups, with one id row per input row.

Parameters:

Name Type Description Default
geom Geometry, GeometryArray, or iterable of Geometry or None

One query geometry or one query per sequence row. Missing rows produce missing result groups (None); present rows with no matches produce empty groups.

required
predicate str

Spatial relation each match must satisfy, evaluated as predicate(query_geometry, indexed_geometry). Topological forms omit predicate to use 'intersects'; 'dwithin' forms require an explicit predicate and distance.

varies by call form
distance float

'dwithin' distance threshold, in unit. A positive infinite threshold is accepted; other values must be finite and non-negative. Missing query rows remain missing result groups; present empty query rows produce empty result groups.

None
unit ('planar', 'meters')

None (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). 'planar' uses Cartesian coordinate space, with geographic coordinates in their native angular units. 'meters' uses the CRS-aware metric and requires a CRS.

'planar'

Returns:

Type Description
int64 numpy.ndarray or Groups

Matching index handles (row ids). A scalar query returns a read-only int64 ndarray of handles; an array query returns Groups of handles, one row per query row. Missing queries yield None rows, while present queries with no matches yield empty groups. Each result is in ascending source-row order.

Raises:

Type Description
CRSMismatchError

If the query does not share the index's CRS/epoch frame.

GeometryError

If a query parameter is invalid, or unit='meters' is requested for a CRS-free geometry.

CRSError

If geographic dwithin refinement cannot prove a result within the point-edge evaluation budget.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)])
>>> idx.query(gm.Point(0.5, 0.5)).tolist()
[0]

remove

remove(handle: SupportsIndex) -> bool

Remove a geometry by its handle. Returns True if a live geometry was removed, False if the handle is unknown or was already removed. Removed handles are not reused, so surviving handles stay stable.

Parameters:

Name Type Description Default
handle SupportsIndex

The handle returned by insert (or a position from building).

required

Returns:

Type Description
bool

Whether a live geometry was removed.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([])
>>> handle = idx.insert(gm.Point(1, 1))
>>> idx.remove(handle)
True

self_join

Accepted call forms
self_join(*, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> tuple[ndarray[tuple[int], dtype[int64]], ndarray[tuple[int], dtype[int64]]]
self_join(*, predicate: Literal['intersects', 'equals', 'touches', 'crosses', 'overlaps'] = 'intersects', distance: None = None, unit: Literal['planar', 'meters'] | None = None) -> tuple[ndarray[tuple[int], dtype[int64]], ndarray[tuple[int], dtype[int64]]]

All index pairs (i, j) with i < j whose geometries satisfy a symmetric predicate — a self-join over the index.

Parameters:

Name Type Description Default
predicate str

A symmetric relation: 'intersects', 'equals', 'dwithin', 'touches', 'crosses', or 'overlaps'. Directional predicates ('contains', 'within', ...) are rejected — unordered pairs would drop the reverse direction; use join(...) for directed relations.

'intersects'
distance float

'dwithin' distance threshold, in unit. Positive infinity is accepted; other values must be finite and non-negative. Empty geometries never produce a pair.

None
unit ('planar', 'meters')

None (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). 'planar' uses Cartesian coordinate space, with geographic coordinates in their native angular units. 'meters' uses the CRS-aware metric and requires a CRS.

'planar'

Returns:

Type Description
tuple of numpy.ndarray

(left, right) parallel int64 row-id columns.

Raises:

Type Description
CRSMismatchError

If indexed items carry conflicting CRS/epoch frames.

GeometryError

If predicate is unknown or directional, distance is missing or invalid for predicate='dwithin', or unit='meters' is requested for a CRS-free geometry.

CRSError

If geographic dwithin refinement cannot prove a result within the point-edge evaluation budget.

Examples:

>>> import gometry as gm
>>> idx = gm.SpatialIndex([gm.box(0, 0, 1, 1), gm.box(2, 2, 3, 3)])
>>> left, right = idx.self_join()
>>> (left.tolist(), right.tolist())
([], [])

values

values() -> ValuesView[Geometry]

Return a dynamic view of the geometries at live handles.

Returns:

Type Description
ValuesView

One geometry per live handle, in handle order.

Examples:

>>> import gometry as gm
>>> list(gm.SpatialIndex([gm.box(0, 0, 1, 1)]).values())[0].to_wkt()
'POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))'