Source: https://gometry.monicz.dev/api/spatial-index/

# Spatial index

Bulk spatial indexing for repeated relationship queries.

**Related:** [Spatial indexing & joins](https://gometry.monicz.dev/guide/indexing/)

## SpatialIndex

Accepted call forms

```python
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](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex.join "join") : High-level spatial join built on the index.

Examples:

```pycon
>>> 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`

```python
crs: CRS | None
```

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

Returns:

| Type | Description |
| --- | --- |
| `CRS or None` |  |

### epoch `property`

```python
epoch: float | None
```

Coordinate epoch shared by the indexed geometries, if set.

Returns:

| Type | Description |
| --- | --- |
| `float or None` |  |

### \_\_contains\_\_

```python
__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\_\_

```python
__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\_\_

```python
__iter__() -> SpatialIndexIterator
```

Iterate live handles lazily in ascending handle order.

Returns:

| Type | Description |
| --- | --- |
| `iterator of int` |  |

### \_\_len\_\_

```python
__len__() -> int
```

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

Returns:

| Type | Description |
| --- | --- |
| `int` |  |

### candidates

Accepted call forms

```python
candidates(geom: Geometry, *, distance: float | None = None, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]]
```

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

```python
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]
```

```python
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:

```pycon
>>> 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

```python
get(handle: SupportsIndex, /) -> Geometry | None
```

```python
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:

```pycon
>>> 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

```python
insert(values: Geometry) -> int
```

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

```python
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:

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

### items

```python
items() -> ItemsView[int, Geometry]
```

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

Returns:

| Type | Description |
| --- | --- |
| `ItemsView` | `(handle, geometry)` pairs for the live handles. |

Examples:

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

### join

Accepted call forms

```python
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]]]
```

```python
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:

```pycon
>>> 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

```python
keys() -> KeysView[int]
```

Return a dynamic view of the live handles.

Returns:

| Type | Description |
| --- | --- |
| `KeysView` | The live handles, in ascending order. |

Examples:

```pycon
>>> 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

```python
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]]
```

```python
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]]
```

```python
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]]]
```

```python
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]
```

```python
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]]
```

```python
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]]
```

```python
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]]
```

```python
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]]
```

```python
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]]
```

```python
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:

```pycon
>>> 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

```python
query(geom: Geometry, *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> ndarray[tuple[int], dtype[int64]]
```

```python
query(geom: (GeometryArray[GeometryRowT] | Iterable[GeometryRowT])[Geometry], *, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> Groups[ndarray[tuple[int], dtype[int64]]]
```

```python
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]
```

```python
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]]
```

```python
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]]]
```

```python
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:

```pycon
>>> 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

```python
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:

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

### self\_join

Accepted call forms

```python
self_join(*, predicate: Literal['dwithin'], distance: float, unit: Literal['planar', 'meters'] | None = None) -> tuple[ndarray[tuple[int], dtype[int64]], ndarray[tuple[int], dtype[int64]]]
```

```python
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:

```pycon
>>> 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

```python
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:

```pycon
>>> 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))'
```
