Skip to main content

Geometry Validity

VectorX ships a set of geometry-processing helpers powered by shapely (GEOS under the hood), running as distributed Python UDFs across your Spark cluster. These functions fill gaps in the product's native ST_* surface — operations that are standard in shapely but have no direct built-in counterpart in Databricks Runtime.

Lightweight tier only

The functions on this page are available in the lightweight (pyvx) tier. Register pyvx before using them in SQL or Python:

from databricks.labs.gbx.pyvx import functions as vx
vx.register(spark)

The OGC Simple Features Specification (SFS) defines what makes a geometry valid: every ring must be closed, holes must lie inside their outer boundary, no ring may properly self-intersect, every ring must have at least four points, and coordinates must be finite. Databricks Runtime's ST_IsValid checks these rules and returns a boolean. What it does not provide is the why, the where, or the fix — and at scale, a single malformed geometry can fail a worker or a shuffle.

GeoBrix fills those gaps with two light-tier functions:

  • gbx_st_explainvaliditydiagnose: returns a JSON string with the SFS violation class, a stable code, and the coordinate where the violation occurs.
  • gbx_st_makevalidrepair: returns a valid geometry, preserving the original CRS.

ST_IsValid vs Shapely vs GeoBrix

Understanding which tool catches which invalidity class matters for the conditional-repair pattern.

Invalidity classST_IsValid
(product)
Shapely is_validgbx_st_explainvalidity / gbx_st_makevalid
Self-touching ring (bowtie at vertex)Flags invalidFlags invalidDiagnoses + repairs
Self-intersecting shell (edge crossing)Flags invalidFlags invalidDiagnoses + repairs
Hole outside shellFlags invalidFlags invalidDiagnoses + repairs
Nested holes (hole-in-hole)Flags invalidFlags invalidDiagnoses + repairs
Overlapping holesFlags invalidFlags invalidDiagnoses + repairs
Multipolygon overlapping componentsFlags invalidFlags invalidDiagnoses + repairs
Disconnected interior (hole tangent to shell)Flags invalidFlags invalidDiagnoses + repairs
Ring-orientation mismatch (same-winding outer + hole)Flags invalidDoes not flagDiagnoses + repairs (default mode also normalizes orientation)
Duplicate consecutive ring pointBoth agree validBoth agree validValid Geometry (code 0)
Near-zero-area sliverBoth agree validBoth agree validValid Geometry (code 0)

Key takeaway: ST_IsValid is the stricter check. It enforces OGC ring orientation (outer ring counter-clockwise, holes clockwise) in addition to all topological rules. Shapely's is_valid ignores orientation — a geometry can pass Shapely's validity test and fail ST_IsValid. The default gbx_st_makevalid repair (level='linework' or 'structure') applies orient_polygons after topology repair, so output passes ST_IsValid's stricter gate.

Conditional repair workflow

At scale, you want to gate repairs and diagnostics: run ST_IsValid as a cheap per-row boolean first, then pass only the flagged rows to gbx_st_makevalid or gbx_st_explainvalidity. Calling either function on every row (including already-valid ones) wastes compute.

Three-part workflow:

  1. Gate with ST_IsValid — cheap per-row boolean; skips valid rows entirely.
  2. Repair flagged rowsgbx_st_makevalid rewrites only the invalid rows; valid rows pass through unchanged.
  3. Diagnose systematicallygbx_st_explainvalidity on the flagged rows, then GROUP BY the reason code to find systemic invalidity classes. Using gbx_st_explainvalidity as a per-row validity test is wasteful; ST_IsValid is the right boolean gate.

The following patterns run on Databricks Runtime where ST_IsValid is a built-in. ST_IsValid operates on the built-in GEOMETRY type, while GeoBrix functions consume and return BINARY (WKB/EWKB) — so the geom column is coerced with ST_GeomFromEWKB(geom) before the gate:

-- Repair only the rows that need it.
-- ST_GeomFromEWKB coerces GeoBrix BINARY (EWKB) to the built-in GEOMETRY type ST_IsValid requires.
SELECT CASE
WHEN NOT ST_IsValid(ST_GeomFromEWKB(geom)) THEN gbx_st_makevalid(geom)
ELSE geom
END AS clean
FROM t;
-- Diagnose flagged rows: which SFS rules are being violated?
SELECT geom, gbx_st_explainvalidity(geom) AS detail
FROM t
WHERE NOT ST_IsValid(ST_GeomFromEWKB(geom));
-- Systemic analysis: count violations by class
SELECT
json_tuple(gbx_st_explainvalidity(geom), 'code', 'reason') AS (code, reason),
COUNT(*) AS n
FROM t
WHERE NOT ST_IsValid(ST_GeomFromEWKB(geom))
GROUP BY code, reason
ORDER BY n DESC;
-- Explain and fix in one pass
SELECT
geom,
gbx_st_explainvalidity(geom) AS detail,
gbx_st_makevalid(geom) AS clean
FROM t
WHERE NOT ST_IsValid(ST_GeomFromEWKB(geom));

gbx_st_makevalid

Lightweight

Repairs a geometry to OGC-SFS validity. Uses GEOS make_valid under the hood (via shapely). The default linework level splits self-intersections by noding edges into a valid multi-polygon; structure uses the more conservative GEOS structure/overlay method. Both default modes also apply orient_polygons so output passes the product's stricter ST_IsValid (which enforces ring orientation in addition to OGC topology). The ogc level skips orientation normalization for callers who need OGC-only semantics.

Signature: gbx_st_makevalid(geom [, level])

Parameters:

  • geom — Geometry to repair. Accepts WKB/EWKB/WKT/EWKT.
  • level (optional) — Repair strategy. One of:
    • 'linework' (default) — GEOS linework noding + orient_polygons. Splits self-intersections; self-touching rings become MULTIPOLYGON pieces. Output passes ST_IsValid (topology + orientation).
    • 'structure' — Conservative GEOS structure/overlay method + orient_polygons. Preserves more of the original topology. Output passes ST_IsValid.
    • 'ogc' — Linework noding only, no orientation normalization. OGC-valid, orientation-agnostic.

Returns: BINARY — WKB (or EWKB if input carried a SRID). The original CRS is preserved without reprojection. Returns NULL for null or unparseable input.

SELECT gbx_st_makevalid('POLYGON((0 0,1 1,1 0,0 1,0 0))') AS clean
Example output
+--------+
|clean |
+--------+
|[binary]|
+--------+
... (WKB binary — repaired geometry; bowtie becomes a valid multi-polygon)
from databricks.labs.gbx.pyvx import functions as vx
from shapely import from_wkt, to_wkb

bowtie = from_wkt("POLYGON((0 0,1 1,1 0,0 1,0 0))")
df = spark.createDataFrame([(to_wkb(bowtie),)], ["geom"])
Example output
+--------+
|clean |
+--------+
|[binary]|
+--------+
... (WKB binary — repaired geometry; bowtie becomes a valid multi-polygon)

gbx_st_explainvalidity

Lightweight

Diagnoses geometry validity and returns a JSON string with four fields. The valid and reason fields are always present. The code and location fields are best-effort: GEOS embeds the violation coordinate in the reason string as Reason[x y], and gbx_st_explainvalidity extracts it to a POINT(x y) WKT string; code maps the GEOS reason prefix to a stable integer from the table below. Both are null when GEOS does not provide them. Nothing throws.

Signature: gbx_st_explainvalidity(geom)

Parameters:

  • geom — Geometry to diagnose. Accepts WKB/EWKB/WKT/EWKT.

Returns: STRING — JSON with shape:

{
"valid": true | false,
"reason": "<GEOS reason string>",
"code": <integer> | null,
"location": "POINT(x y)" | null
}

Returns NULL for null or unparseable input.

SELECT gbx_st_explainvalidity('POLYGON((0 0,1 1,1 0,0 1,0 0))') AS detail
Example output
+--------------------------------------------------------------------+
|detail |
+--------------------------------------------------------------------+
|{"valid": false, "reason": "Self-intersection[0.5 0.5]", "code": 10,|
+--------------------------------------------------------------------+
... (JSON string — {valid, reason, code, location} for SFS validity diagnosis)
from databricks.labs.gbx.pyvx import functions as vx
from shapely import from_wkt, to_wkb

bowtie = from_wkt("POLYGON((0 0,1 1,1 0,0 1,0 0))")
df = spark.createDataFrame([(to_wkb(bowtie),)], ["geom"])
Example output
+----------------------------------------------------------------------+
|detail |
+----------------------------------------------------------------------+
|{"valid": false, "reason": "Self-intersection[0.5 0.5]", "code": 10,|
+----------------------------------------------------------------------+
... (JSON string — {valid, reason, code, location} for SFS validity diagnosis)

Tier availability

FunctionLightweight (pyvx)Heavyweight (vectorx)
gbx_st_makevalidSupported
gbx_st_explainvaliditySupported

Invalidity reason codes

The code field in gbx_st_explainvalidity output maps the GEOS reason prefix to a stable integer. Use these codes in GROUP BY queries to count violations by class.

CodeSFS violation classNotes
0Valid GeometryThe geometry passed all validity checks
1Too few points in geometry componentRing has fewer than 4 points
2Invalid coordinateNon-finite coordinate (NaN / Inf)
3Ring not closedFirst and last ring vertices differ
4Repeated pointConsecutive duplicate vertices in a ring. Current GEOS (3.13.1) does not flag this as invalid — the code is mapped for completeness but no current GEOS version emits this reason
10Self-intersectionShell or hole edges cross each other
11Ring Self-intersectionA ring touches itself at a vertex (bowtie)
20Hole lies outside shellA hole is not contained within its outer ring
21Holes are nestedA hole is contained within another hole
22Interior is disconnectedA hole is tangent to the shell from inside
23Nested shellsTwo outer rings are nested (multipolygon)
24Duplicate ringsTwo rings are identical
nullUnmapped reasonA GEOS reason string not yet in the lookup table