23. Validez¶
En el 90% de los casos, la respuesta a la pregunta «¿por qué mi consulta da un error “TopologyException”?» es «una o varias de las entradas no son válidas». Lo que nos lleva a preguntarnos: ¿qué significa que no sea válida y por qué debería importarnos?
23.1. ¿Qué es la Validez?¶
La validez es más importante para los polígonos, que definen áreas delimitadas y requieren mucha estructura. Las líneas son muy sencillas y no pueden ser inválidas, ni tampoco los puntos.
Algunas de las reglas de validez de los polígonos parecen obvias y otras arbitrarias (y, de hecho, lo son).
Los anillos del polígono deben cerrarse.
Los anillos que definen agujeros deben estar dentro de los anillos que definen límites exteriores.
Los anillos no pueden autointersectarse (no pueden tocarse ni cruzarse).
Los anillos no pueden tocar a otros anillos, excepto en un punto.
Los elementos de los multipolígonos no pueden tocarse entre sí.
Las tres últimas reglas podrían catalogarse como arbitrarias. Hay otras formas de definir polígonos que son igualmente autoconsistentes, pero las reglas anteriores son las utilizadas por el estándar OGC SFSQL que PostGIS cumple.
La razón por la que las reglas son importantes es porque los algoritmos para cálculos geométricos dependen de una estructura consistente en las entradas. Es posible construir algoritmos que no tengan suposiciones estructurales, pero esas rutinas tienden a ser muy lentas, porque el primer paso en cualquier rutina sin estructura es analizar las entradas y construir estructura en ellas.
He aquí un ejemplo del por qué la estructura importa. Este polígono no es válido:
POLYGON((0 0, 0 1, 2 1, 2 2, 1 2, 1 0, 0 0));
Puede ver la invalidez un poco más claramente en este diagrama:

El anillo exterior es en realidad una figura de ocho, con una auto-intersección en el centro. Obsérvese que las rutinas gráficas representan correctamente el relleno del polígono, de modo que visualmente parece un «área»: dos cuadrados de una unidad, es decir, un área total de dos unidades de área.
Veamos cuál cree la base de datos que es el área de nuestro polígono:
SELECT ST_Area(ST_GeometryFromText(
'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
));
st_area
---------
0
¿Qué ocurre aquí? El algoritmo que calcula el área asume que los anillos no se auto-intersecan. Un anillo que se comporte bien siempre tendrá el área delimitada (el interior) a un lado de la línea de delimitación (no importa a qué lado, sólo que sea a un lado). Sin embargo, en nuestro (mal comportamiento) ocho, el área delimitada está a la derecha de la línea para un lóbulo y a la izquierda para el otro. Esto hace que las áreas calculadas para cada lóbulo se anulen (una sale como 1, la otra como -1), de ahí el resultado de «área cero».
23.2. Detectando Validez¶
In the previous example we had one polygon that we knew was invalid. How do we detect invalidity in a table with millions of geometries? With the ST_IsValid(geometry) function. Used against our figure-eight, we get a quick answer:
SELECT ST_IsValid(ST_GeometryFromText(
'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
));
f
Now we know that the feature is invalid, but we don’t know why. We can use the ST_IsValidReason(geometry) function to find out the source of the invalidity:
SELECT ST_IsValidReason(ST_GeometryFromText(
'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
));
Self-intersection[1 1]
Note that in addition to the reason (self-intersection) the location of the invalidity (coordinate (1 1)) is also returned.
We can use the ST_IsValid(geometry) function to test our tables too:
-- Find all the invalid polygons and what their problem is
SELECT name, boroname, ST_IsValidReason(geom)
FROM nyc_neighborhoods
WHERE NOT ST_IsValid(geom);
name | boroname | st_isvalidreason
-------------------------+---------------+-----------------------------------------
Howard Beach | Queens | Self-intersection[597264.08 4499924.54]
Corona | Queens | Self-intersection[595483.05 4513817.95]
Steinway | Queens | Self-intersection[593545.57 4514735.20]
Red Hook | Brooklyn | Self-intersection[584306.82 4502360.51]
23.3. Repairing Invalidity¶
Repairing invalidity involves stripping a polygon down to its simplest structures (rings), ensuring the rings follow the rules of validity, then building up new polygons that follow the rules of ring enclosure. Frequently the results are intuitive, but in the case of extremely ill-behaved inputs, the valid outputs may not conform to your intuition of how they should look. Recent versions of PostGIS include different algorithms for geometry repair: read the manual page carefully and choose the one you like best.
For example, here’s a classic invalidity – the «banana polygon» – a single ring that encloses an area but bends around to touch itself, leaving a «hole» which is not actually a hole.
POLYGON((0 0, 2 0, 1 1, 2 2, 3 1, 2 0, 4 0, 4 4, 0 4, 0 0))

Running ST_MakeValid on the polygon returns a valid OGC polygon, consisting of an outer and inner ring that touch at one point.
SELECT ST_AsText(
ST_MakeValid(
ST_GeometryFromText('POLYGON((0 0, 2 0, 1 1, 2 2, 3 1, 2 0, 4 0, 4 4, 0 4, 0 0))')
)
);
POLYGON((0 0,0 4,4 4,4 0,2 0,0 0),(2 0,3 1,2 2,1 1,2 0))
Nota
The «banana polygon» (or «inverted shell») is a case where the OGC topology model for valid geometry and the model used internally by ESRI differ. The ESRI model considers rings that touch to be invalid, and prefers the banana form for this kind of shape. The OGC model is the reverse. Neither is «correct», they are just different ways to model the same situation.
23.4. Bulk Validity Repair¶
Here’s an example of SQL to flag invalid geometries for review while adding a repaired version to the table.
-- Column for old invalid form
ALTER TABLE nyc_neighborhoods
ADD COLUMN geom_invalid geometry
DEFAULT NULL;
-- Fix invalid and save the original
UPDATE nyc_neighborhoods
SET geom = ST_MakeValid(geom),
geom_invalid = geom
WHERE NOT ST_IsValid(geom);
-- Review the invalid cases
SELECT geom, ST_IsValidReason(geom_invalid)
FROM nyc_neighborhoods
WHERE geom_invalid IS NOT NULL;
A good tool for visually repairing invalid geometry is OpenJump (http://openjump.org) which includes a validation routine under Tools->QA->Validate Selected Layers.
23.5. Lista de funciones¶
ST_IsValid(geometry A): Returns a boolean indiciting whether the geometery is valid.
ST_IsValidReason(geometry A): Returns a text string with the reason for the invalidity and a coordinate of invalidity.
ST_MakeValid(geometry A): Returns a geometry re-constructed to obey the validity rules.