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
|
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 |
required |
distance
|
float
|
Expand the query envelope by this much, in |
None
|
unit
|
('planar', 'meters')
|
|
'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 |
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
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; |
required |
default
|
object
|
Value returned when the handle is not live. |
required |
Returns:
| Type | Description |
|---|---|
Geometry or object
|
The live geometry, else |
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
|
|
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 |
varies by call form
|
distance
|
float
|
Required when |
None
|
unit
|
('planar', 'meters')
|
|
'planar'
|
Returns:
| Type | Description |
|---|---|
tuple of numpy.ndarray
|
|
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 |
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 |
required |
k
|
int
|
How many nearest neighbors to return; |
1
|
max_distance
|
float
|
Ignore matches farther than this, in |
None
|
return_distance
|
bool
|
Return distances alongside handles — |
False
|
unit
|
('planar', 'meters')
|
|
'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 |
False
|
Returns:
| Type | Description |
|---|---|
int64 numpy.ndarray, Groups, or tuple
|
The nearest handles — an |
Raises:
| Type | Description |
|---|---|
CRSMismatchError
|
If the query does not share the index's CRS/epoch frame. |
GeometryError
|
If |
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 ( |
required |
predicate
|
str
|
Spatial relation each match must satisfy, evaluated as
|
varies by call form
|
distance
|
float
|
|
None
|
unit
|
('planar', 'meters')
|
|
'planar'
|
Returns:
| Type | Description |
|---|---|
int64 numpy.ndarray or Groups
|
Matching index handles (row ids). A scalar query returns a read-only
|
Raises:
| Type | Description |
|---|---|
CRSMismatchError
|
If the query does not share the index's CRS/epoch frame. |
GeometryError
|
If a query parameter is invalid, or |
CRSError
|
If geographic |
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 |
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'
|
distance
|
float
|
|
None
|
unit
|
('planar', 'meters')
|
|
'planar'
|
Returns:
| Type | Description |
|---|---|
tuple of numpy.ndarray
|
|
Raises:
| Type | Description |
|---|---|
CRSMismatchError
|
If indexed items carry conflicting CRS/epoch frames. |
GeometryError
|
If |
CRSError
|
If geographic |
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))'