Il modo migliore per risolvere un problema di intersezione senza nodi in PostGIS?


38

Sto usando una PL/Rfunzione e PostGISper generare poligoni voronoi attorno a una serie di punti. La funzione che sto usando è definita qui . Quando utilizzo questa funzione su un determinato set di dati, viene visualizzato il seguente messaggio di errore:

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

Dall'esame di questa parte del messaggio di errore:

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

Ecco come appare il problema sopra elencato:

inserisci qui la descrizione dell'immagine

Inizialmente pensavo che questo messaggio potesse essere causato dall'esistenza di punti identici e ho provato a risolverlo utilizzando la st_translate()funzione, utilizzata nel modo seguente:

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

Questo risolve il problema, ma la mia preoccupazione è che ora sto traducendo tutti i punti fino a ~ 20m nella direzione x / y. Inoltre, non posso dire quale sia la necessità di una traduzione adeguata. Ad esempio, in questo set di dati tramite tentativi ed errori a 20m * random numberè ok, ma come posso sapere se questo deve essere più grande?

In base all'immagine sopra, penso che il problema sia che il punto si interseca con la linea mentre l'algoritmo sta cercando di intersecare il punto con un poligono. Non sono sicuro di cosa dovrei fare per assicurarmi che il punto sia all'interno di un poligono, piuttosto che intersecarsi con una linea. L'errore si sta verificando su questa riga:

"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'');"

Ho letto questa domanda precedente, che cos'è un "incrocio non annuito"? per cercare di capire meglio questo problema e apprezzerebbe qualsiasi consiglio su come risolverlo al meglio.


Se i tuoi input non sono validi per iniziare, esegui ST_MakeValid () su di essi. Se sono validi, l'aggiunta di entropia, come stai facendo, è il prossimo trucco disponibile, e forse l'ultimo trucco per ora.
Paul Ramsey,

Sì, WHERE ST_IsValid(p.geom)inizialmente sto usando per filtrare i punti.
djq

Risposte:


30

Nella mia esperienza questo problema è quasi sempre causato da:

  1. Alta precisione nelle coordinate (43.231499999999996), combinata con
  2. Linee quasi coincidenti ma non identiche

L'approccio "nudge" delle ST_Buffersoluzioni ti consente di cavartela con il n. 2, ma qualsiasi cosa tu possa fare per risolvere queste cause sottostanti, come agganciare la tua geometria a una griglia 1e-6, ti semplificherà la vita. Le geometrie bufferizzate di solito vanno bene per calcoli intermedi come l'area di sovrapposizione, ma ti consigliamo di fare attenzione a conservarli perché possono peggiorare i tuoi problemi di chiusura ma non del tutto nel lungo raggio.

La capacità di gestione delle eccezioni di PostgreSQL consente di scrivere funzioni wrapper per gestire questi casi speciali, eseguendo il buffering solo quando necessario. Ecco un esempio per ST_Intersection; Uso una funzione simile per ST_Difference. Dovrai decidere se il buffering e il potenziale ritorno di un poligono vuoto sono accettabili nella tua situazione.

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;

Un altro vantaggio di questo approccio è che puoi individuare le geometrie che stanno effettivamente causando i tuoi problemi; aggiungi solo alcune RAISE NOTICEistruzioni nel EXCEPTIONblocco per generare WKT o qualcos'altro che ti aiuterà a rintracciare il problema.


Questa è un'idea intelligente. Ho spesso scoperto che i problemi di intersezione derivano dalle stringhe di linea che compaiono durante le combinazioni di unioni, differenze, buffer, ecc., Che possono essere risolte bufferando tutto o scaricando tutto e selezionando solo Poligoni / Mutlipoligoni. Questo è un approccio interessante.
John Powell,

Hai detto di agganciare la geometria alla griglia 1e-6, ma sono seduto qui a chiedermi se scattare ad una potenza di 2 sarebbe meglio. PostGIS (e GEOS) utilizzano numeri in virgola mobile, quindi lo snap ad una potenza di 10 potrebbe effettivamente non troncare molto le coordinate poiché il numero potrebbe non avere una rappresentazione binaria a lunghezza finita. Ma se fai uno scatto per dire 2 ^ -16, credo che sarebbe garantito per troncare qualsiasi parte frazionaria a soli 2 byte. O sto pensando male?
jpmc26,

12

Attraverso un sacco di tentativi ed errori alla fine mi sono reso conto che il non-noded intersectionrisultato di un problema di autointersezione. Ho trovato un thread che ha suggerito di usare ST_buffer(geom, 0)per risolvere il problema (anche se nel complesso lo rende molto più lento). Ho quindi provato a utilizzare ST_MakeValid()e quando applicato direttamente sulla geometria prima di qualsiasi altra funzione. Questo sembra risolvere saldamente il problema.

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]
        )
    )

Ho contrassegnato questo come la risposta in quanto sembra essere l'unico approccio che risolve il mio problema.


11

Mi sono imbattuto in questo stesso problema (Postgres 9.1.4, PostGIS 2.1.1) e l'unica cosa che ha funzionato per me è stata avvolgere la geometria con un buffer molto piccolo.

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

ST_MakeValidnon ha funzionato per me, né la combinazione di ST_Nodee ST_Dump. Il buffer non sembra comportare un peggioramento delle prestazioni, ma se lo rendessi più piccolo, avrei comunque ricevuto un errore di intersezione senza nodi.

Brutto, ma funziona.

Aggiornare:

La strategia ST_Buffer sembra funzionare bene, ma ho riscontrato un problema in cui produceva errori quando si proiettava la geometria in geografia. Ad esempio, se un punto è originariamente a -90,0 ed è bufferizzato da 0,0000001, ora è a -90,0000001, ovvero una geografia non valida.

Ciò significava che, sebbene ST_IsValid(geom)fosse t, ST_Area(geom::geography)restituito NaNper molte funzionalità.

Per evitare il problema di intersezione senza nodi, pur mantenendo una geografia valida, ho finito per usarlo in questo ST_SnapToGridmodo

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

6

In postgis ST_Node dovrebbe interrompere una serie di linee agli incroci, che dovrebbe risolvere il problema di intersezione senza nodi. Il wrapping di questo in ST_Dump genera l'array composito delle linee spezzate.

Leggermente correlato, c'è una fantastica presentazione PostGIS: Suggerimenti per utenti esperti che delinea chiaramente questo tipo di problemi e soluzioni.


Questa è un'ottima presentazione (grazie @PaulRamsey). Come dovrei usare ST_Nodee ST_Dump? Immagino che dovrei usarli vicino a questa parte della funzione, ma non sono sicuro: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')in
djq

Hmmm Non ho notato che le due linee avevano una coordinata identica, che dovrebbe andare bene. Se si tracciano queste coordinate, il punto di intersezione si trova a circa 18 cm dall'intersezione. Non proprio una soluzione, solo un'osservazione.
WolfOdrade,

Non del tutto chiaro su come uso st_nodequi - posso usarlo prima st_intersection?
DJ

1
La presentazione non è più disponibile. Sono bloccato con lo stesso problema, quando provo a ST_Clip (rast, poligono)
Jackie,

1
@ Jack: ho risolto il link alla presentazione nella risposta: PostGIS: Suggerimenti per utenti esperti .
Pete,

1

Nella mia esperienza, ho risolto il mio non-noded intersectionerrore utilizzando la funzione St_SnapToGrid che ha risolto il problema di avere un'alta precisione nelle coordinate del vertice dei poligoni.

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';
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.