¿Cuál es la mejor manera de solucionar un problema de intersección sin nudos en PostGIS?

38

Estoy usando una PL/Rfunción y PostGISpara generar polígonos voronoi alrededor de un conjunto de puntos. La función que estoy usando se define aquí . Cuando uso esta función en un conjunto de datos en particular, aparece el siguiente mensaje de error:

Error : ERROR:  R interpreter expression evaluation error
DETAIL:  Error in pg.spi.exec(sprintf("SELECT %3$s AS id,   
st_intersection('SRID='||st_srid(%2$s)||';%4$s'::text,'%5$s') 
AS polygon FROM %1$s WHERE st_intersects(%2$s::text,'SRID='||st_srid(%2$s)||';%4$s');",  
:error in SQL statement : Error performing intersection: TopologyException: found non-noded 
intersection between LINESTRING (571304 310990, 568465 264611) and LINESTRING (568465 
264611, 594406 286813) at 568465.05533706467 264610.82749605528
CONTEXT:  In R support function pg.spi.exec In PL/R function r_voronoi

Al examinar esta parte del mensaje de error:

Error performing intersection: TopologyException: found non-noded intersection between
LINESTRING (571304 310990, 568465 264611) and LINESTRING (568465 264611, 594406 286813) 
at 568465.05533706467 264610.82749605528

Así es como se ve el problema mencionado anteriormente:

ingrese la descripción de la imagen aquí

Inicialmente pensé que este mensaje podría ser causado por la existencia de puntos idénticos, e intenté resolverlo usando la st_translate()función, que se usa de la siguiente manera:

ST_Translate(geom, random()*20, random()*20) as geom 

Esto soluciona el problema, pero mi preocupación es que ahora estoy traduciendo todos los puntos hasta ~ 20m en la dirección x / y. Tampoco puedo decir qué cantidad de traducción se necesita. Por ejemplo, en este conjunto de datos a través de prueba y error a 20m * random numberestá bien, pero ¿cómo puedo saber si esto necesita ser más grande?

Basado en la imagen de arriba, creo que el problema es que el punto se cruza con la línea mientras el algoritmo intenta intersecar el punto con un polígono. No estoy seguro de lo que debería hacer para asegurarme de que el punto esté dentro de un polígono, en lugar de cruzarse con una línea. El error está ocurriendo en esta línea:

"SELECT 
  %3$s AS id, 
  st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'') AS polygon 
FROM 
  %1$s 
WHERE 
  st_intersects(%2$s::text,''SRID=''||st_srid(%2$s)||'';%4$s'');"

He leído esta pregunta anterior, ¿Qué es una "intersección sin nudos"? para tratar de comprender mejor este problema, y ​​agradecería cualquier consejo sobre la mejor manera de resolverlo.

djq
fuente
Si sus entradas no son válidas para comenzar, ejecute ST_MakeValid () en ellas. Si son válidos, agregar entropía, como lo está haciendo, es el próximo truco disponible, y quizás el último truco por ahora.
Paul Ramsey
Sí, estoy usando WHERE ST_IsValid(p.geom)para filtrar los puntos inicialmente.
djq

Respuestas:

30

En mi experiencia, este problema casi siempre es causado por:

  1. Alta precisión en sus coordenadas (43.231499999999996), combinado con
  2. Líneas casi coincidentes pero no idénticas.

El enfoque de "empujar" de las ST_Buffersoluciones le permite salirse con la # 2, pero cualquier cosa que pueda hacer para resolver estas causas subyacentes, como ajustar su geometría a una cuadrícula 1e-6, le facilitará la vida. Las geometrías amortiguadas generalmente son adecuadas para cálculos intermedios como el área de superposición, pero querrá tener cuidado al retenerlas porque pueden empeorar sus problemas cercanos pero no del todo a largo plazo.

La capacidad de manejo de excepciones de PostgreSQL le permite escribir funciones de contenedor para manejar estos casos especiales, almacenando en búfer solo cuando sea necesario. Aquí hay un ejemplo para ST_Intersection; Yo uso una función similar para ST_Difference. Tendrá que decidir si el almacenamiento en búfer y el posible retorno de un polígono vacío son aceptables en su situación.

CREATE OR REPLACE FUNCTION safe_isect(geom_a geometry, geom_b geometry)
RETURNS geometry AS
$$
BEGIN
    RETURN ST_Intersection(geom_a, geom_b);
    EXCEPTION
        WHEN OTHERS THEN
            BEGIN
                RETURN ST_Intersection(ST_Buffer(geom_a, 0.0000001), ST_Buffer(geom_b, 0.0000001));
                EXCEPTION
                    WHEN OTHERS THEN
                        RETURN ST_GeomFromText('POLYGON EMPTY');
    END;
END
$$
LANGUAGE 'plpgsql' STABLE STRICT;

Otro beneficio con este enfoque es que puede identificar las geometrías que realmente están causando sus problemas; simplemente agregue algunas RAISE NOTICEdeclaraciones en el EXCEPTIONbloque para generar WKT o algo más que lo ayudará a rastrear el problema.

dbaston
fuente
Esa es una idea inteligente. A menudo he descubierto que los problemas de intersección provienen de cadenas lineales que aparecen durante combinaciones de uniones, diferencias, amortiguadores, etc., que se pueden solucionar almacenando todo en un búfer, o volcando todo y solo seleccionando Polígonos / Mutlipolígonos. Este es un enfoque interesante.
John Powell
Mencionas ajustar la geometría a la cuadrícula 1e-6, pero estoy sentado aquí preguntándome si sería mejor ajustar a una potencia de 2. PostGIS (y GEOS) usando números de punto flotante, por lo que ajustar a una potencia de 10 podría no truncar realmente las coordenadas, ya que el número puede no tener una representación binaria de longitud finita. Pero si dice 2 ^ -16, creo que se garantizaría que truncaría cualquier parte fraccional a solo 2 bytes. ¿O estoy pensando mal?
jpmc26
12

A través de muchas pruebas y errores, finalmente me di cuenta de que era el non-noded intersectionresultado de un problema de auto-intersección. Encontré un hilo que sugirió que ST_buffer(geom, 0)se puede usar para solucionar el problema (aunque en general lo hace mucho más lento). Luego intenté usar ST_MakeValid()y cuando se aplica directamente a la geometría antes que cualquier otra función. Esto parece solucionar el problema de manera robusta.

ipoint <- pg.spi.exec(
        sprintf(
            "SELECT 
                    %3$s AS id, 
                    st_intersection(ST_MakeValid(''SRID=''||st_srid(%2$s)||'';%4$s''::text), ST_MakeValid(''%5$s'', 0)) AS polygon 
            FROM %1$s 
            WHERE 
                ST_Intersects(ST_MakeValid(%2$s::text),ST_MakeValid(''SRID=''||st_srid(%2$s)||'';%4$s''));",
            arg1,
            arg2,
            arg3,
            curpoly,
            buffer_set$ewkb[1]
        )
    )

He marcado esto como la respuesta, ya que parece ser el único enfoque que soluciona mi problema.

djq
fuente
11

Me encontré con este mismo problema (Postgres 9.1.4, PostGIS 2.1.1), y lo único que funcionó para mí fue envolver la geometría con un búfer muy pequeño.

SELECT ST_Intersection(
    (SELECT geom FROM table1), ST_Union(ST_Buffer(geom, 0.0000001))
) FROM table2

ST_MakeValidno funcionó para mí, ni la combinación de ST_Nodey ST_Dump. El búfer no pareció provocar ninguna degradación en el rendimiento, pero si lo reduje aún recibo un error de intersección sin nudos.

Feo, pero funciona.

Actualizar:

La estrategia ST_Buffer parece funcionar bien, pero me encontré con un problema en el que producía errores al transmitir la geometría a la geografía. Por ejemplo, si un punto está originalmente en -90.0, y está protegido por 0.0000001, ahora está en -90.0000001, que es una geografía no válida.

Esto significaba que, a pesar de que ST_IsValid(geom)era t, ST_Area(geom::geography)regresó NaNpor muchas características.

Para evitar el problema de intersección sin nudos, mientras mantenía una geografía válida, terminé usando ST_SnapToGridasí

SELECT ST_Union(ST_MakeValid(ST_SnapToGrid(geom, 0.0001))) AS geom, common_id
    FROM table
    GROUP BY common_id;
jczaplew
fuente
6

En postgis, ST_Node debería romper una serie de líneas en las intersecciones, lo que debería resolver el problema de intersección sin nudos. Al envolver esto en ST_Dump, se genera la matriz compuesta de las líneas discontinuas .

Ligeramente relacionado, hay una presentación impresionante PostGIS: Consejos para usuarios avanzados que describe claramente este tipo de problemas y soluciones.

WolfOdrade
fuente
Esa es una gran presentación (gracias @PaulRamsey). ¿Cómo debo usar ST_Nodey ST_Dump? Me imagino que necesitaría usarlos cerca de esta parte de la función, pero no estoy seguro: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')en
djq
Hmmm, no me di cuenta de que las dos líneas tenían una coordenada idéntica, lo que debería estar bien. Si traza esas coordenadas, el punto de intersección está a unos 18 cm de la intersección. No es realmente una solución, solo una observación.
WolfOdrade
No está del todo claro cómo lo uso st_nodeaquí, ¿puedo usarlo antes st_intersection?
djq
1
La presentación ya no está disponible. Estoy atascado con el mismo problema, cuando trato de ST_Clip (rast, polígono)
Jackie
1
@Jackie: arreglé el enlace a la presentación en la respuesta: PostGIS: Consejos para usuarios avanzados .
Pete
1

En mi experiencia, resolví mi non-noded intersectionerror usando la función St_SnapToGrid que resolvió el problema de tener una alta precisión en las coordenadas del vértice de los polígonos.

SELECT dissolve.machine, dissolve.geom FROM (
        SELECT machine, (ST_Dump(ST_Union(ST_MakeValid(ST_SnapToGrid(geom,0.000001))))).geom 
        FROM cutover_automatique
        GROUP BY machine) as dissolve
WHERE ST_isvalid(dissolve.geom)='t' AND ST_GeometryType(dissolve.geom) = 'ST_Polygon';
CptGasse
fuente