Source: https://gometry.monicz.dev/migrating/

# Migrating to gometry

gometry provides geometry, CRS, geodesy, grid, and spatial-index APIs for workflows
commonly split across **Shapely + pyproj + h3-py + s2sphere + rtree**.

[Migration cheatsheet](https://gometry.monicz.dev/migrating/cheatsheet/)

Use the cheatsheet to look up equivalent names, and this guide to adapt your
workflow. See [CRS, units & measurement](https://gometry.monicz.dev/guide/crs/),
[Spatial indexing & joins](https://gometry.monicz.dev/guide/indexing/), and [Grids & geocodes](https://gometry.monicz.dev/guide/grids/).

## Coming from Shapely

gometry keeps Shapely's role as the in-process geometry type system — the same
seven [Simple Features](https://www.ogc.org/standard/sfa/) families, the same
predicates and constructive operations, the same WKB/WKT/GeoJSON interop and
`__geo_interface__`. Scalar predicates become **free functions** ([`gm.contains(a, b)`](https://gometry.monicz.dev/api/toplevel/predicates/#gometry.contains "contains"), vectorized over
arrays), metrics read the CRS, and the kernels are GEOS-free. Behavior differences
include validity, CRS-aware metrics, index refinement, and point-set overlay
presentation: when `union`, `symmetric_difference`, or `difference` with a
higher-dimensional geometry on the left retains that operand, its stored
line/polygon coordinates and Z/M pass through. See [Overlay (set
operations)](https://gometry.monicz.dev/guide/constructive/#overlay-set-operations) for the exact
cases and when to call `normalize()`.

### `make_valid` → `repair`

Use [`repair`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.repair "repair") in place of `make_valid`.
`validate()`
returns a structured report with the reason and location, where Shapely splits the
job across `is_valid`, `explain_validity`, and `make_valid`.

**Shapely**

partial: source ecosystem example

```python
from shapely import make_valid
from shapely.validation import explain_validity
if not geom.is_valid:
    print(explain_validity(geom))
fixed = make_valid(geom)
```

**gometry**

```python
import gometry as gm

bowtie = gm.from_wkt("POLYGON((0 0, 1 1, 1 0, 0 1, 0 0))")
print("valid:", bowtie.is_valid)
report = bowtie.validate()
print("reason:", report.reason)
fixed = bowtie.repair()
print("repaired:", fixed.geometry_type, "| now valid:", fixed.is_valid)
```

```text
valid: False
reason: exterior ring has a self-intersection
repaired: MultiPolygon | now valid: True
```

### CRS-aware `.area`, `.distance`, and `.buffer`

On degree-valued lon/lat data Shapely's bare
`.area` returns degrees², `.distance` returns degrees, and `.buffer(100)` buffers
by 100 *degrees*. gometry has one [`geom.area`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.area "area property") (and one [`gm.distance`](https://gometry.monicz.dev/api/toplevel/measurement/#gometry.distance "distance") /
[`Geometry.buffer`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.buffer "buffer")) whose answer is decided by the geometry's CRS and is native by
default — geographic shape area and length measure straight longitude/latitude
edges on the ellipsoid, point distances follow ellipsoidal geodesics, a projected
CRS uses its **native linear units**, and CRS-free geometry uses raw coordinate
units.

**Shapely (coordinate units)**

partial: source ecosystem example

```python
poly.area
a.distance(b)
poly.buffer(100)
```

**gometry (the CRS decides)**

```python
import gometry as gm

area = gm.box(20.0, 51.0, 22.0, 53.0, crs=4326)
a = gm.Point(21.0, 52.0, crs=4326)
b = gm.Point(22.0, 52.0, crs=4326)
print("ellipsoidal area (m²):", round(area.area))
print("geodesic distance (m):", round(gm.distance(a, b)))
print("projected area (m²):", round(area.to_crs(area.estimate_local_crs()).area))
```

```text
ellipsoidal area (m²): 30564870978
geodesic distance (m): 68677
projected area (m²): 30535837688
```

### STRtree and prepare → SpatialIndex

Shapely already refines when you pass `predicate=` to `STRtree.query`.
[`SpatialIndex`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex "SpatialIndex") covers many geometries, with query, join,
and nearest, explicit `candidates` vs `query(..., predicate=...)`, frame checks,
and missing-row semantics. There is no `prepare`: a geometry
probed repeatedly indexes itself after its first few probes, so a plain loop of
predicate calls gets the prepared speed.

**Shapely**

partial: source ecosystem example

```python
from shapely import STRtree, prepare
tree = STRtree(geoms)
candidates = tree.query(area)
hits = tree.query(area, predicate="contains")

prepare(poly)
poly.contains(pt)
```

**gometry**

```python
import gometry as gm

pts = gm.points([20.25, 21.0, 30.0], [51.25, 52.0, 52.0], crs=4326)
area = gm.Polygon(
    [(20.0, 51.0), (22.0, 51.0), (22.0, 53.0), (20.0, 53.0), (20.0, 51.0)],
    holes=[[(20.5, 51.5), (21.5, 51.5), (21.5, 52.5), (20.5, 52.5), (20.5, 51.5)]],
    crs=4326,
)
idx = gm.SpatialIndex(pts)
print("candidates:", idx.candidates(area).tolist())
print("refined:", idx.query(area, predicate="contains").tolist())
print("contains first point:", gm.contains(area, pts[0]))
```

```text
candidates: [0, 1]
refined: [0]
contains first point: True
```

Build through the geometry classes
([`gm.Point`](https://gometry.monicz.dev/api/geometry/geometry-types/#gometry.Point "Point"), [`gm.Polygon`](https://gometry.monicz.dev/api/geometry/geometry-types/#gometry.Polygon "Polygon"), …) with explicit `crs=`, pack arrays with the plural
builders ([`gm.points`](https://gometry.monicz.dev/api/toplevel/constructors-bulk-factories/#gometry.points "points"), [`gm.polygons`](https://gometry.monicz.dev/api/toplevel/constructors-bulk-factories/#gometry.polygons "polygons")) or [`gm.GeometryArray([...])`](https://gometry.monicz.dev/api/geometry/geometryarray/#gometry.GeometryArray "GeometryArray"), and ingest
GeoJSON / `__geo_interface__` with [`gm.from_geojson`](https://gometry.monicz.dev/api/toplevel/io-interop/#gometry.from_geojson "from_geojson"). Readers are verb-prefixed
free functions ([`gm.from_wkb`](https://gometry.monicz.dev/api/toplevel/io-interop/#gometry.from_wkb "from_wkb"), [`gm.from_wkt`](https://gometry.monicz.dev/api/toplevel/io-interop/#gometry.from_wkt "from_wkt")), and encoders are methods on the
geometry ([`geom.to_wkb(include_srid=True)`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_wkb "to_wkb"), [`geom.to_wkt()`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_wkt "to_wkt"), [`geom.to_geojson()`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_geojson "to_geojson"),
[`geom.to_arrow()`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_arrow "to_arrow") for [GeoArrow](https://geoarrow.org/) columnar interchange).
Vertex-subset ops (`simplify`, `convex_hull`, triangulation) preserve Z/M where
Shapely drops it. Hulls copy the first input vertex at a duplicated XY.

## Coming from pyproj

pyproj owns two jobs: **CRS transformation** (the `CRS` / `Transformer` objects
over libPROJ) and **geodesy** (the `Geod` object). gometry keeps
[PROJ](https://proj.org/) as the authority backend, bundled behind the API, and
exposes transformations through `to_crs` or the stateless
[`gm.crs_transform`](https://gometry.monicz.dev/api/crs/#gometry.crs_transform "crs_transform"). Point-navigation free functions
provide geodesy on geographic CRSs. Coordinate boundaries are always X/Y.

### `Transformer` → `to_crs` and `gm.crs_transform`

For geometry, transform via the geometry. For raw coordinate values, use the
stateless `gm.crs_transform` — there is no transformer
object to construct, cache, or reuse.

**pyproj**

partial: source ecosystem example

```python
t = Transformer.from_crs(4326, 3857, always_xy=True)
x2, y2 = t.transform(21.0, 52.0)
minx2, miny2, maxx2, maxy2 = t.transform_bounds(20, 51, 22, 53)
```

**gometry**

```python
import gometry as gm

x2, y2 = gm.crs_transform(4326, 3857, 21.0, 52.0)
print("point:", round(x2, 1), round(y2, 1))
print("bounds:", [round(v) for v in gm.crs_transform_bounds(4326, 3857, (20, 51, 22, 53))])
```

```text
point: 2337709.3 6800125.5
bounds: [2226390, 6621294, 2449029, 6982998]
```

Scalar inputs return a tuple; bulk inputs return one row-major `(N, 2)` / `(N, 3)`
NumPy matrix. `transform_bounds` takes the bounds as one `(minx, miny, maxx, maxy)`
tuple and accepts a `densify=` count for curved CRS edges. To pick a working
projection without hand-choosing a UTM zone, ask
[`geom.estimate_local_crs()`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.estimate_local_crs "estimate_local_crs"). When every
coordinate has Z and the operation consumes it (geocentric, vertical, compound
CRS), `to_crs` transforms Z through the same pipeline; otherwise Z and M are
preserved unchanged — never invented, never silently dropped.

### `Geod` → point navigation

pyproj's `Geod` (Karney/[GeographicLib](https://geographiclib.sourceforge.io/)
WGS 84 ellipsoidal geodesy) maps onto [`gm.bearing`](https://gometry.monicz.dev/api/toplevel/geodesy-navigation/#gometry.bearing "bearing"),
`point.destination`, [`gm.point_between`](https://gometry.monicz.dev/api/toplevel/geodesy-navigation/#gometry.point_between "point_between"), and
point-to-point distances. Geographic shape area, length, and default LRS instead
measure the ellipsoid along straight longitude/latitude chart edges; `unit='planar'`
selects Cartesian measurement in stored coordinates. There is no `Geod` object
to construct. For a
non-WGS 84 ellipsoid, give the points that geographic CRS (for example
`gm.Point(lon, lat, crs=4230)`); every geodesic operation runs on the CRS's own
ellipsoid.

**pyproj Geod**

partial: source ecosystem example

```python
from pyproj import Geod
g = Geod(ellps="WGS84")
dist = g.inv(lon1, lat1, lon2, lat2)[2]
az = g.inv(lon1, lat1, lon2, lat2)[0]
lon2, lat2, _ = g.fwd(lon1, lat1, az, dist)
length = g.line_length(lons, lats)
area, perim = g.geometry_area_perimeter(poly)
pts = g.npts(lon1, lat1, lon2, lat2, n)
```

**gometry**

```python
import gometry as gm

a = gm.Point(21.0, 52.0, crs=4326)
b = gm.Point(22.0, 52.0, crs=4326)
area = gm.box(20.0, 51.0, 22.0, 53.0, crs=4326)

print("distance (m):", round(gm.distance(a, b)))  # geographic → geodesic
print("bearing (deg):", round(gm.bearing(a, b), 1))
print("destination:", a.destination(90.0, 1000.0))
print("midpoint:", gm.point_between(a, b, 0.5, normalized=True))
print("area (m²):", round(area.area))
print("perimeter (m):", round(area.length))
```

```text
distance (m): 68677
bearing (deg): 89.6
destination: POINT (21.014560699969515 51.999999100100496)
midpoint: POINT (21.5 52.001061140753436)
area (m²): 30564870978
perimeter (m): 719739
```

The `gm.CRS` surface includes `is_*` classification, standards export, catalog
discovery, and coordinate operations.

## Coming from h3-py & s2sphere

h3-py and s2sphere both turn geometry into compact cell IDs for aggregation,
joins, partitioning, and coarse pre-filtering. gometry unifies
[H3](https://h3geo.org/) and [S2](https://s2geometry.io/) under typed cells and
CellArray, and makes the cell-selection rule explicit. The returned cells are
candidate keys; keep the source geometry for exact predicates.

### `polyfill` → `cover` with an explicit rule

h3-py's `polygon_to_cells` (formerly `polyfill`) has an implicit containment rule.
gometry names the rule and returns a typed [`CellArray`](https://gometry.monicz.dev/api/grids/#gometry.CellArray "CellArray").

**h3-py**

partial: h3-py example

```python
import h3
cells = h3.polygon_to_cells(h3.LatLngPoly(shell), 8)  # containment rule is implicit
# Exact membership is not provided.
```

**gometry**

```python
import gometry as gm

area = gm.box(20.0, 51.0, 22.0, 53.0, crs=4326)
cov = gm.h3_cover(area, resolution=5)
print("cells:", len(cov))

# A cover is a candidate set; retain the source for exact predicates.
print("covers:", gm.covers(area, gm.Point(21.0, 52.0, crs=4326)))
```

```text
cells: 159
covers: True
```

`cell_rule` is `"within"` (cells the area fully owns), `"center"` (h3-py's
binning behavior), `"overlap"` (default — a complete-coverage superset, safe
candidate keys), or `"bbox"` (loosest). Use the free predicates
[`gm.covers(...)`](https://gometry.monicz.dev/api/toplevel/predicates/#gometry.covers "covers") / [`gm.contains(...)`](https://gometry.monicz.dev/api/toplevel/predicates/#gometry.contains "contains") / [`gm.intersects(...)`](https://gometry.monicz.dev/api/toplevel/predicates/#gometry.intersects "intersects") against the source
geometry when exact geometry answers are needed. [`gm.s2_cover(geom, level=...)`](https://gometry.monicz.dev/api/s2/#gometry.s2_cover "s2_cover")
selects a fixed-level cover. Omit `level` and pass `target_cells` or a level
range to adapt refinement through the same operation.

### Cell-set algebra

Every grid family exposes its own prefixed `union` / `intersection` /
`difference` functions (`h3_*`, `s2_*`, `geohash_*`, `tile_*`). `CellArray`s
also carry `compact()` / `uncompact(...)` for multi-resolution
rollups. Family algebra preserves hierarchy-aware behavior that plain Python
`set`s do not.

```python
import gometry as gm

a = gm.s2_cover(gm.box(0.0, 0.0, 2.0, 2.0, crs=4326), level=8)
b = gm.s2_cover(gm.box(1.0, 1.0, 3.0, 3.0, crs=4326), level=8)
a_present = [cell for cell in a if cell is not None]
b_present = [cell for cell in b if cell is not None]
shared = gm.s2_intersection(a_present, b_present)
rollup = shared.compact()
print("shared S2 cells:", len(shared), "compacted:", len(rollup))

h3_a = gm.h3_cover(gm.box(0.0, 0.0, 2.0, 2.0, crs=4326), resolution=5)
h3_b = gm.h3_cover(gm.box(1.0, 1.0, 3.0, 3.0, crs=4326), resolution=5)
h3_a_present = [cell for cell in h3_a if cell is not None]
h3_b_present = [cell for cell in h3_b if cell is not None]
combined = gm.h3_union(h3_a_present, h3_b_present)
print("combined H3 cells:", len(combined))
```

```text
shared S2 cells: 13 compacted: 13
combined H3 cells: 115
```

[`H3Cell`](https://gometry.monicz.dev/api/h3/#gometry.H3Cell "H3Cell") exposes adjacency and local indexing (`neighbors`, `is_neighbor`,
`local_ij`, `base_cell`), with resolution metadata in the H3 function family
([`gm.h3_pentagons(resolution)`](https://gometry.monicz.dev/api/h3/#gometry.h3_pentagons "h3_pentagons"), [`gm.h3_base_cells()`](https://gometry.monicz.dev/api/h3/#gometry.h3_base_cells "h3_base_cells")); `.polygon` returns one
cell's boundary polygon and `cells.polygon` a whole [`GeometryArray`](https://gometry.monicz.dev/api/geometry/geometryarray/#gometry.GeometryArray "GeometryArray").
The cell classes provide point-to-cell construction, boundaries, and
compact/uncompact operations.

## Coming from rtree & STRtree

rtree, Shapely's `STRtree`, and GeoPandas `sjoin` all accelerate spatial
predicates with a bounding-box prefilter. **Shapely 2.x and GeoPandas already
refine exactly** when you pass a predicate (`STRtree.query(..., predicate=...)`,
`sjoin(..., predicate=...)`); rtree's `intersection` path is bbox-only
unless you refine yourself. gometry exposes the corresponding stages and adds
frame and row-identity checks:

- **Explicit stage names** — `candidates` (bbox) vs `query(..., predicate=...)`
  (exact).
- **Strict frame checks** — CRS and epoch must match; mixed frames raise rather
  than silently comparing across datums.
- **Missing query rows** keep their input positions as missing result groups.
- **Prepared caches** on every geometry, built once it is probed repeatedly;
  there is no separate `prepare` step.
- **CRS-aware nearest / dwithin queries** when the index frame is geographic:
  point pairs use ellipsoidal geodesics, while shape queries use straight
  chart-edge support with ellipsoidal separation (`unit=` selects the CRS
  metric or planar coordinates).

[`gm.SpatialIndex`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex "SpatialIndex") and [`gm.join`](https://gometry.monicz.dev/api/toplevel/index-join/#gometry.join "join") are the
entry points; see [Spatial indexing & joins](https://gometry.monicz.dev/guide/indexing/) for examples.

### Nearest

rtree (`Index.nearest`), Shapely (`STRtree.nearest` / `query_nearest`), and
gometry all expose nearest-neighbour APIs.

**rtree / Shapely**

partial: source ecosystem example

```python
# rtree — bbox-based nearest by envelope
nearest = list(idx.nearest(point.bounds, num_results=5))

# Shapely 2.x — geometry nearest (+ distances via return_distance=)
from shapely import STRtree
tree = STRtree(geoms)
idxs = tree.query_nearest(query, return_distance=True)
```

**gometry**

```python
import gometry as gm

pts = gm.points([21.0, 30.0, 21.2], [52.0, 52.0, 52.1], crs=4326)
target = gm.Point(21.0, 52.0, crs=4326)
idx = gm.SpatialIndex(pts)
print("nearest 2 (planar):", idx.nearest(target, k=2, unit="planar").tolist())
```

```text
nearest 2 (planar): [0, 2]
```

[`idx.nearest(query, k=..., unit=...)`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex.nearest "nearest") returns the `k` closest indices
(`num_results` → `k`). `unit="planar"` selects projected or CRS-free coordinate
units; on a geographic CRS, point-pair ordering uses geodesics and shape-pair
ordering uses ellipsoidal separation over chart-edge support.
The index is mutable (`insert`/`remove`) while still sharing one frame; Shapely's
`STRtree` is bulk-built and immutable after construction.

### Candidate selectivity

`idx.candidates(...)` exposes the bounding-box prefilter, while
`idx.query(..., predicate=...)` performs exact refinement. Compare their lengths
to see how much work the envelope filter leaves for the predicate:

```python
import gometry as gm

pts = gm.points([20.25, 21.0, 30.0], [51.25, 52.0, 52.0], crs=4326)
area = gm.Polygon(
    [(20.0, 51.0), (22.0, 51.0), (22.0, 53.0), (20.0, 53.0), (20.0, 51.0)],
    holes=[[(20.5, 51.5), (21.5, 51.5), (21.5, 52.5), (20.5, 52.5), (20.5, 51.5)]],
    crs=4326,
)
idx = gm.SpatialIndex(pts)
candidate_count = len(idx.candidates(area))
refined_count = len(idx.query(area, predicate="contains"))
print("candidate count:", candidate_count)
print("refined count:", refined_count)
```

```text
candidate count: 2
refined count: 1
```

### Spatial joins (`sjoin`)

GeoPandas [`sjoin`](https://geopandas.org/en/latest/docs/reference/api/geopandas.sjoin.html)
**does** apply the binary `predicate=` exactly (it is not a candidates-only API).
`gm.join` is the geometry-array analogue: prefilter → exact refine,
returning matched **index pairs** (not a joined DataFrame). `gm.join` provides:

- pure geometry inputs and index-pair outputs (bring your own table join);
- CRS/epoch frame checks on both sides;
- candidate and refined counts are directly observable with `len(idx.candidates(query))` and `len(idx.query(query, predicate=...))`;
- no pandas/GeoPandas dependency for the join itself.

**GeoPandas**

partial: source ecosystem example

```python
import geopandas
result = geopandas.sjoin(points, polygons, predicate="within")
```

**gometry**

```python
import gometry as gm

pts = gm.points([21.0, 30.0], [52.0, 52.0], crs=4326)
polys = gm.GeometryArray([gm.box(20.0, 51.0, 22.0, 53.0, crs=4326)])
left, right = gm.join(pts, polys, predicate="within")
print("pairs:", list(zip(left.tolist(), right.tolist(), strict=True)))
```

```text
pairs: [(0, 0)]
```

`gm.SpatialIndex(geometries)` bulk-loads a balanced in-memory
[R-tree](https://en.wikipedia.org/wiki/R-tree) over geometry envelopes — suited to
static, query-heavy workloads — while [`idx.insert(geometry)`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex.insert "insert") / [`idx.remove(...)`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex.remove "remove")
handle dynamic sets without a full rebuild. The search structure is in memory.
[Pickle](https://gometry.monicz.dev/ecosystem/text-formats/#pickle-multiprocessing-and-copies) preserves
the live geometries and stable handles, rebuilding the search structure on load.
For portable storage, save WKB/EWKB and construct a new index.

## What gometry covers

Data-modeling layers (GeoJSON object models and dataframe integrations such as
GeoPandas) remain adjacent; gometry is the geometry engine they use.

| Package | What it does | gometry surface for the same workflow |
| --- | --- | --- |
| `shapely` | planar geometry, predicates, overlay | the whole geometry surface — [`gm.Point`](https://gometry.monicz.dev/api/geometry/geometry-types/#gometry.Point "Point"), [`geom.buffer(...)`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.buffer "buffer"), [`gm.intersection`](https://gometry.monicz.dev/api/toplevel/overlay-dissolve/#gometry.intersection "intersection"), [`gm.contains`](https://gometry.monicz.dev/api/toplevel/predicates/#gometry.contains "contains"), … |
| `pyproj` | CRS transforms, projection | [`geom.to_crs`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_crs "to_crs"), [`gm.CRS`](https://gometry.monicz.dev/api/crs/#gometry.CRS "CRS"), [`gm.crs_transform`](https://gometry.monicz.dev/api/crs/#gometry.crs_transform "crs_transform") |
| `geographiclib` | geodesic & rhumb solutions | Use `path='rhumb'` with [`gm.distance`](https://gometry.monicz.dev/api/toplevel/measurement/#gometry.distance "distance") for point distance, [`gm.bearing`](https://gometry.monicz.dev/api/toplevel/geodesy-navigation/#gometry.bearing "bearing"), `point.destination`, [`gm.point_between`](https://gometry.monicz.dev/api/toplevel/geodesy-navigation/#gometry.point_between "point_between"), or [`geom.segmentize(..., path='rhumb')`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.segmentize "segmentize") for constant-bearing routes. `path='geodesic'` on `segmentize` samples the shortest ellipsoidal route. |
| `pyclipper` | polygon buffer / offset / clip | `Geometry.buffer`, [`Geometry.offset_curve`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.offset_curve "offset_curve"), [`gm.intersection`](https://gometry.monicz.dev/api/toplevel/overlay-dissolve/#gometry.intersection "intersection") with [`gm.box`](https://gometry.monicz.dev/api/toplevel/constructors-bulk-factories/#gometry.box "box"), the overlay ops |
| `pygeohash` family | geohash encode/decode | [`gm.GeohashCell`](https://gometry.monicz.dev/api/geohash/#gometry.GeohashCell "GeohashCell"), [`cell.polygon`](https://gometry.monicz.dev/api/grids/#gometry.Cell.polygon "polygon property"), [`gm.geohash_cover(geom, ...)`](https://gometry.monicz.dev/api/geohash/#gometry.geohash_cover "geohash_cover") |
| `haversine` | point-to-point distance | [`gm.distance(a, b)`](https://gometry.monicz.dev/api/toplevel/measurement/#gometry.distance "distance") on a geographic CRS ([ellipsoidal geodesic](https://geographiclib.sourceforge.io/), not the haversine sphere) |
| `polyline` | Google encoded polyline | [`gm.from_polyline`](https://gometry.monicz.dev/api/toplevel/io-interop/#gometry.from_polyline "from_polyline"), [`Geometry.to_polyline`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.to_polyline "to_polyline") |
| `mercantile` | XYZ tiles / quadkeys | [`gm.Tile`](https://gometry.monicz.dev/api/tiles/#gometry.Tile "Tile"), [`gm.tile_cover(geom, ...)`](https://gometry.monicz.dev/api/tiles/#gometry.tile_cover "tile_cover") |
| `s2sphere` | S2 cells & coverings | `gm.s2_*`, [`gm.s2_cover(geom, ...)`](https://gometry.monicz.dev/api/s2/#gometry.s2_cover "s2_cover") |
| `h3` | H3 hexagonal cells | `gm.h3_*`, [`gm.h3_cover(geom, ...)`](https://gometry.monicz.dev/api/h3/#gometry.h3_cover "h3_cover") |
| `openlocationcode` | plus codes | [`gm.pluscode_encode`](https://gometry.monicz.dev/api/geocode/#gometry.pluscode_encode "pluscode_encode"), [`gm.pluscode_polygon`](https://gometry.monicz.dev/api/geocode/#gometry.pluscode_polygon "pluscode_polygon"), [`gm.pluscode_shorten`](https://gometry.monicz.dev/api/geocode/#gometry.pluscode_shorten "pluscode_shorten")/`recover` |
| `utm` | lat/lon ↔ UTM | [`geom.estimate_local_crs()`](https://gometry.monicz.dev/api/geometry/geometry/#gometry.Geometry.estimate_local_crs "estimate_local_crs") + `geom.to_crs(...)` |
| `rtree` / Shapely `STRtree` | bounding-box index | [`gm.SpatialIndex`](https://gometry.monicz.dev/api/spatial-index/#gometry.SpatialIndex "SpatialIndex"), [`gm.join`](https://gometry.monicz.dev/api/toplevel/index-join/#gometry.join "join") |
