Back to Claude Scientific Skills

Geometric operations, validity, and precision

skills/geopandas/references/geometric-operations.md

2.55.09.5 KB
Original Source

Geometric operations, validity, and precision

GeoPandas delegates geometry work to Shapely/GEOS. Operations are planar, two-dimensional, and expressed in CRS coordinate units. Z/M ordinates may be carried but are not part of topology.

Preflight state

Count missing, empty, invalid, and mixed geometries separately before any constructive or set operation:

python
geometry = gdf.geometry
missing = geometry.isna()
empty = geometry.is_empty
invalid = (~missing) & (~empty) & (~geometry.is_valid)

state = {
    "rows": len(gdf),
    "missing": int(missing.sum()),
    "empty": int(empty.sum()),
    "invalid": int(invalid.sum()),
    "types": geometry.geom_type.value_counts(dropna=False).to_dict(),
}

is_valid concerns polygon/ring topology; points and lines are generally valid unless malformed. is_simple, is_ring, and minimum_clearance answer different questions.

Redacted validity diagnostics

is_valid_reason() can include the coordinate of a defect, for example a self-intersection. Precise coordinates can be sensitive. Aggregate only the reason category before [ and retain detailed diagnostics in a protected local artifact:

python
reason_category = (
    geometry[invalid]
    .is_valid_reason()
    .str.split("[", n=1)
    .str[0]
    .value_counts()
)

Repair is a model change

GeoPandas 1.1 exposes Shapely 2.1 repair controls:

python
repaired = geometry.make_valid(
    method="structure",
    keep_collapsed=True,
)

Methods:

  • linework preserves every edge/vertex, nodes all rings, and reconstructs areas with even/odd parity. It can produce complex GeometryCollection output and requires keep_collapsed=True.
  • structure repairs rings, merges shells, and subtracts holes. It assumes shell/hole categorization is meaningful, requires GEOS >=3.10, and can drop collapsed parts when keep_collapsed=False.

Repair can turn a polygon into a multipolygon, line, point, collection, or empty geometry. Never replace source data in place. Create a new artifact and compare:

  1. valid/invalid/null/empty counts;
  2. geometry-type and dimensionality transitions;
  3. component counts and collapsed outputs;
  4. area/length changes in appropriate units;
  5. stable feature IDs and row count;
  6. downstream predicate/coverage behavior.

overlay(make_valid=True) also repairs invalid inputs, but that convenience can hide type changes. Audit and repair explicitly for traceable work.

Precision models

set_precision(grid_size, mode=...) rounds x/y to a grid in CRS units:

python
snapped = geometry.set_precision(
    grid_size=0.01,
    mode="valid_output",
)

Shapely 2.1 modes:

  • valid_output removes collapsed polygonal/linear elements and duplicate vertices while producing valid output;
  • pointwise rounds independently, retains duplicate vertices, and may produce invalid output;
  • keep_collapsed preserves collapsed linear elements but removes collapsed polygonal elements.

Consequences:

  • features narrower/shorter than the grid may become empty;
  • spikes and narrow sections can disappear or split polygons;
  • duplicate vertices are normally removed;
  • Z is not rounded;
  • vertex/ring/order is canonicalized and must not be used as identity;
  • inputs should be valid first;
  • later operations use the higher precision (smaller grid size) of inputs.

Choose the grid from documented source resolution and error—not decimal aesthetics. 0.001 degrees is not a universal metric tolerance.

For one union/dissolve, grid_size= can apply fixed precision without first attaching a precision model:

python
merged = geometry.union_all(method="unary", grid_size=0.01)

Record whether precision was attached to inputs or applied only to an operation.

union_all algorithms

GeoPandas 1.1.4 signature:

python
geometry.union_all(method="unary", grid_size=None)
  • unary: robust general-purpose algorithm; the only method supporting grid_size.
  • coverage: optimized for non-overlapping edge-matched polygon coverages; it can return invalid geometry if polygons overlap.
  • disjoint_subset: optimized when input can be divided into non-intersecting subsets; requires Shapely >=2.1 and may be slower when there is one subset.

Do not use coverage based on visual inspection:

python
if not geometry.is_valid_coverage(gap_width=0.0):
    edges = geometry.invalid_coverage_edges(gap_width=0.0)
    raise ValueError("Not an edge-matched non-overlapping coverage")

merged = geometry.union_all(method="coverage")

is_valid_coverage() ignores non-polygon geometry and requires Shapely >=2.1. If narrow gaps matter, select gap_width in justified projected units. simplify_coverage() preserves shared boundaries for a valid coverage; ordinary element-wise simplify() does not.

The old unary_union attribute is deprecated. Use union_all().

Constructive operations

All distance/tolerance arguments are CRS units:

python
buffered = geometry.buffer(50)
simplified = geometry.simplify(5, preserve_topology=True)
densified = geometry.segmentize(max_segment_length=10)
centroids = geometry.centroid
inside_points = geometry.representative_point()

Correctness notes:

  • Buffering geographic degrees does not create a fixed-metre buffer.
  • Negative polygon buffers can collapse to empty.
  • centroid may fall outside a concave polygon; representative_point() is guaranteed within the geometry but is not a centroid.
  • preserve_topology=True protects each geometry's validity, not shared boundaries between adjacent features.
  • segmentize() inserts vertices along planar segments; it does not create geodesic densification.
  • Affine rotate/scale/translate/skew operations are coordinate-space transforms, not CRS transformations.

Binary predicates

Predicates implement DE-9IM relationships and are directional:

PredicatePractical meaning
intersectsBoundaries or interiors share any point
disjointShare no point
withinLeft geometry lies in right interior/boundary under DE-9IM
containsInverse direction of within; boundary-only point is not contained
coversNo point of right lies outside left; includes boundary cases
covered_byInverse of covers
contains_properlyContains with no common boundary points
touchesInteriors do not meet, boundaries do
crossesInteriors meet with lower-dimensional result
overlapsSame-dimensional partial overlap, neither contains the other
dwithinPlanar distance is within the supplied CRS-unit threshold

Do not describe contains, covers, and intersects as interchangeable. Boundary-point tests are an important synthetic fixture.

Binary GeoSeries calls are one-to-one and index-aligned by default:

python
matched = left.intersects(right, align=True)

They do not answer whether each left geometry intersects any right geometry. Use sjoin or the spatial index for all-pairs matching.

Overlay robustness and slivers

Overlay and intersection can create tiny slivers from precision mismatch, near-coincident edges, or distinct source accuracy:

  1. validate inputs and CRS;
  2. quantify source precision/accuracy;
  3. select a justified grid if snapping is appropriate;
  4. run overlay with explicit keep_geom_type;
  5. validate output and count type changes;
  6. summarize area distribution and very small parts in projected units;
  7. compare area conservation appropriate to the selected overlay mode.

Do not delete polygons below an arbitrary area threshold. A small polygon can be legitimate, and thresholding can bias boundaries. Record any sliver rule and retain pre-cleaning output.

Equality and identity

  • geom_equals is topological equality; coordinate order may differ.
  • geom_equals_exact(tolerance=...) checks structural coordinate equality within tolerance.
  • geom_equals_identical exposes Shapely's identical comparison in GeoPandas 1.1 and includes coordinate/order details.
  • normalize() can canonicalize ordering for reproducible comparisons, but a normalized WKB hash is still geometry identity, not stable feature identity.

Post-operation validation

For every geometry-changing operation, record:

  • operation and all parameters;
  • source/target CRS and units;
  • package and GEOS versions;
  • null/empty/invalid/type/component counts before and after;
  • row expansion/contraction and stable-ID mapping;
  • precision model, repair method, collapsed-part policy;
  • area/length conservation checks where meaningful;
  • a new output path and source/output hashes.

The bundled scripts/geometry_validity_report.py provides bounded dry-run counts and optional new-file repair without emitting geometries or coordinates.

Sources (verified 2026-07-23)