geometric-operations.md
267 lines · skills/geopandas/references/geometric-operations.md at 5f2bc03
Most marks come from matching words in the file, so treat them as pointers.
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.
Raw lines 1 to 6Preflight 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()
)Raw lines 31 to 47Repair is a model change
GeoPandas 1.1 exposes Shapely 2.1 repair controls:
repaired = geometry.make_valid(
method="structure",
keep_collapsed=True,
)Methods:
lineworkpreserves every edge/vertex, nodes all rings, and reconstructs areas with even/odd parity. It can produce complexGeometryCollectionoutput and requireskeep_collapsed=True.structurerepairs rings, merges shells, and subtracts holes. It assumes shell/hole categorization is meaningful, requires GEOS >=3.10, and can drop collapsed parts whenkeep_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:
- valid/invalid/null/empty counts;
- geometry-type and dimensionality transitions;
- component counts and collapsed outputs;
- area/length changes in appropriate units;
- stable feature IDs and row count;
- 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:
valid_outputremoves collapsed polygonal/linear elements and duplicate vertices while producing valid output;pointwiserounds independently, retains duplicate vertices, and may produce invalid output;keep_collapsedpreserves 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:
merged = geometry.union_all(method="unary", grid_size=0.01)GeoPandas 1.2 also accepts grid_size= on row-wise intersection, union, difference, and symmetric_difference. This does not add a grid_size argument to overlay; snap valid inputs explicitly for that workflow. buffer(resolution=...) is deprecated in 1.2; use quad_segs=.
Record whether precision was attached to inputs or applied only to an operation.
Raw lines 81 to 127union_all algorithms
GeoPandas 1.2.0 signature:
geometry.union_all(method="unary", grid_size=None)unary: robust general-purpose algorithm; the only method supportinggrid_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:
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, quad_segs=16)
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.
centroidmay fall outside a concave polygon;representative_point()is guaranteed within the geometry but is not a centroid.preserve_topology=Trueprotects 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:
| 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:
- validate inputs and CRS;
- quantify source precision/accuracy;
- select a justified grid if snapping is appropriate;
- run overlay with explicit
keep_geom_type; - validate output and count type changes;
- summarize area distribution and very small parts in projected units;
- 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.
Raw lines 215 to 231Equality and identity
geom_equalsis topological equality; coordinate order may differ.geom_equals_exact(tolerance=...)checks structural coordinate equality within tolerance.geom_equals_identicalexposes 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-10-01)
- GeoPandas geometric manipulations.
- GeoSeries.make_valid.
- GeoSeries.union_all.
- GeoSeries.is_valid_coverage.
- Shapely 2.1.2 make_valid.
- Shapely 2.1.2 set_precision.
- Shapely 2.1.2 union_all.
- GeoPandas 1.1.0 release — released 2025-06-01.