← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / scientific-agent-skills / skills / geopandas / references / geometric-operations.md

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:

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:

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:

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

Methods:

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:

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

Shapely 2.1 modes:

Consequences:

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:

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:

geometry.union_all(method="unary", grid_size=None)

Do not use coverage based on visual inspection:

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:

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:

Binary predicates

Predicates implement DE-9IM relationships and are directional:

Predicate Practical meaning
intersects Boundaries or interiors share any point
disjoint Share no point
within Left geometry lies in right interior/boundary under DE-9IM
contains Inverse direction of within; boundary-only point is not contained
covers No point of right lies outside left; includes boundary cases
covered_by Inverse of covers
contains_properly Contains with no common boundary points
touches Interiors do not meet, boundaries do
crosses Interiors meet with lower-dimensional result
overlaps Same-dimensional partial overlap, neither contains the other
dwithin Planar 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:

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

Post-operation validation

For every geometry-changing operation, record:

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)