The short version
Geometry validity is whether a feature obeys the rules of the OGC Simple Features model — the formal definition of what a well-formed point, line, or polygon is. A polygon can look perfect on screen and still be invalid: a ring that crosses itself, an interior hole that pokes outside its shell, or two vertices in the same spot. Invalid geometry is the silent cause of overlay operations that error out, buffers that return garbage, areas that come back wrong, and exports that fail. This article explains what the validity rules actually are, how to detect violations with ST_IsValid and the QGIS tools, and how to repair them reliably.
What "valid" means under Simple Features
The OGC Simple Features specification defines validity per geometry type. The interesting cases are polygons:
- Rings must be simple — an exterior or interior ring may not cross itself. A ring that loops back through its own path is the classic "bowtie" or figure-eight.
- Rings must be closed — the first and last vertex are identical. An unclosed ring is malformed.
- Interior rings (holes) must lie inside the exterior ring and may touch it only at a single point, never cross it or share an edge.
- Holes may not nest or overlap each other; they may touch only at single points.
- No spikes or repeated points — consecutive duplicate vertices and zero-area spurs violate the model.
- Ring orientation — by convention the exterior ring is counter-clockwise and interior rings clockwise (RFC 7946 GeoJSON mandates this; shapefiles use the opposite convention, which is a frequent source of confusion at conversion time).
Lines have lighter rules (a LineString must have at least two distinct points), and simplicity is a related but separate property: a LineString is "simple" if it does not self-intersect, which matters for some operations even when the geometry is technically valid.
Why it breaks operations
Most spatial predicates and overlay functions — intersection, union, difference, buffer — are built on algorithms that assume valid input. Feed them a self-intersecting polygon and you get one of three outcomes: a hard error (PostGIS: "TopologyException: side location conflict"; GEOS errors in QGIS), a silently wrong result (the bowtie's two lobes counted with opposite sign, so the reported area is too small or even negative), or a downstream failure when the data is exported to a format with stricter checking. The danger is that the map renders fine, so the defect is invisible until an analysis depends on it.
Detecting invalid geometry
In PostGIS, the canonical checks are:
SELECT id FROM parcels WHERE NOT ST_IsValid(geom);
SELECT id, ST_IsValidReason(geom) FROM parcels WHERE NOT ST_IsValid(geom);
ST_IsValidReason returns a human-readable cause and often the offending coordinate, e.g. Self-intersection[412300 5012880], which lets you zoom straight to the problem vertex. ST_IsValidDetail returns the reason and location as structured columns for programmatic handling.
In QGIS, run Vector › Geometry Tools › Check Validity (it produces valid-output, invalid-output, and error layers) or use the Processing algorithm Check validity. Note that QGIS offers two validity engines — the older QGIS method and the GEOS method; the GEOS method matches what PostGIS reports, so prefer it for consistency across your stack.
From the command line, ogrinfo and the GDAL stack rely on GEOS as well, and many ogr2ogr failures on conversion trace back to a geometry that was invalid at the source.
Repairing invalid geometry
The workhorse in PostGIS is ST_MakeValid:
UPDATE parcels SET geom = ST_MakeValid(geom) WHERE NOT ST_IsValid(geom);
ST_MakeValid repairs without discarding vertices — but understand what it does: a bowtie polygon may be split into a MultiPolygon, and a degenerate sliver may become a line or be dropped, so the output geometry type can change. After running it, re-check with ST_IsValid and confirm the geometry type is still what your column expects (you may need ST_CollectionExtract(geom, 3) to keep only polygons). In QGIS, the Fix geometries algorithm wraps the same GEOS makeValid logic. As a cruder fallback, a zero-width buffer (ST_Buffer(geom, 0)) sometimes cleans simple self-intersections, but it can mangle complex features and is not a substitute for ST_MakeValid.
For ring-orientation problems on export (GeoJSON wanting CCW exterior rings), ST_ForcePolygonCCW / ST_ForcePolygonCW set the winding explicitly; ogr2ogr to GeoJSON generally writes RFC 7946-compliant orientation, but data passed straight from a shapefile can carry the opposite winding.
A short worked repair
For a delivered GeoPackage that errors on union:
- Load into PostGIS:
ogr2ogr -f PostgreSQL PG:"dbname=gis" parcels.gpkg. - Find the offenders:
SELECT id, ST_IsValidReason(geom) FROM parcels WHERE NOT ST_IsValid(geom);— read the reasons and the coordinates. - Inspect a few in QGIS at the reported coordinate to understand whether it is a digitizing bowtie or a hole-outside-shell.
- Repair:
UPDATE parcels SET geom = ST_CollectionExtract(ST_MakeValid(geom), 3) WHERE NOT ST_IsValid(geom);. - Re-verify: the validity query should return zero rows.
- Re-run the failing union/overlay; confirm areas are sane against a known control value.
Common pitfalls and why they happen
- "It draws, so it's fine." Rendering tolerates invalid geometry; topology algorithms do not. The cause is conflating visual correctness with model correctness.
- Bowties from over-eager digitizing, where a polygon was traced back through itself or a vertex was dragged across an edge. Enable snapping and avoid loops; check validity right after editing.
ST_MakeValidchanging the geometry type and then failing a constraint or a strict format. Always pair it with a type-extraction step and a re-check.- Shapefile/GeoJSON ring-orientation mismatch producing "valid but inside-out" holes after conversion. Force the winding explicitly when targeting GeoJSON.
- Mixed validity engines (QGIS native vs GEOS) reporting different results; standardize on GEOS to match PostGIS and GDAL.
Validation summary
A dataset is validity-clean when ST_IsValid returns true for every feature under the GEOS engine, the geometry type matches the column/schema, ring orientation matches the target format, and the operations that previously failed (overlay, buffer, union) now succeed with areas that match a trusted control. Record that you ran the check, because invalid geometry tends to creep back in at the next edit.
Bathyl perspective
We run a validity pass on every dataset before it leaves the studio, because an invalid geometry is a defect that travels — it passes silently through delivery and detonates in the recipient's analysis. Catching it with ST_IsValid early is the difference between a clean handoff and a support ticket weeks later.
Related reading
- GeoPackage for QGIS Projects
- Spatial Indexes in GIS Files
- Why Your GIS Layers Do Not Line Up
- Spatial data products