Wie kann ein nicht geknotetes Kreuzungsproblem in PostGIS am besten behoben werden?

38

Ich benutze eine PL/RFunktion und PostGISgeneriere Voronoi-Polygone um eine Menge von Punkten. Die Funktion, die ich benutze, wird hier definiert . Wenn ich diese Funktion für einen bestimmten Datensatz verwende, wird folgende Fehlermeldung angezeigt:

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

Nach Prüfung dieses Teils der Fehlermeldung:

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

So sieht das oben aufgeführte Problem aus:

Bildbeschreibung hier eingeben

Ich dachte anfangs, dass diese Nachricht durch die Existenz identischer Punkte verursacht werden könnte, und versuchte dies mithilfe der st_translate()Funktion zu lösen , die folgendermaßen verwendet wurde:

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

Dies behebt das Problem, aber meine Sorge ist, dass ich jetzt alle Punkte bis zu ~ 20 m in x / y-Richtung übersetze. Ich kann auch nicht sagen, welcher Übersetzungsbetrag angemessen ist. Zum Beispiel ist in diesem Datensatz durch Ausprobieren a 20m * random numberin Ordnung, aber wie kann ich feststellen, ob dies größer sein muss?

Basierend auf dem obigen Bild denke ich, dass das Problem darin besteht, dass sich der Punkt mit der Linie schneidet, während der Algorithmus versucht, den Punkt mit einem Polygon zu schneiden. Ich bin nicht sicher, was ich tun soll, um sicherzustellen, dass sich der Punkt innerhalb eines Polygons befindet, anstatt sich mit einer Linie zu schneiden. Der Fehler tritt in dieser Zeile auf:

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

Ich habe diese vorherige Frage gelesen: Was ist eine "nicht geknotete Kreuzung"? Um zu versuchen, dieses Problem besser zu verstehen, und um Ratschläge zu erhalten, wie es am besten gelöst werden kann.

djq
quelle
Wenn Ihre Eingaben zu Beginn nicht gültig sind, führen Sie ST_MakeValid () für sie aus. Wenn sie gültig sind, ist das Hinzufügen von Entropie, wie Sie es tun, der nächste verfügbare Trick und vielleicht der letzte Trick für den Moment.
Paul Ramsey
Ja, ich benutze WHERE ST_IsValid(p.geom), um die Punkte anfangs zu filtern.
8.

Antworten:

30

Nach meiner Erfahrung wird dieses Problem fast immer verursacht durch:

  1. Hohe Genauigkeit in Ihren Koordinaten (43.231499999999996), kombiniert mit
  2. Linien, die fast zusammenfallen, aber nicht identisch sind

Mit dem "Nudge" -Ansatz der ST_BufferLösungen kommen Sie mit Nummer 2 klar, aber alles, was Sie tun können, um diese zugrunde liegenden Ursachen zu beheben, z. Die gepufferten Geometrien eignen sich normalerweise gut für Zwischenberechnungen wie Überlappungsbereiche. Sie sollten jedoch darauf achten, sie beizubehalten, da sie auf lange Sicht Ihre Nah-aber-nicht-ganz-Probleme verschlimmern können.

Mit der Ausnahmebehandlungsfunktion von PostgreSQL können Sie Wrapper-Funktionen schreiben, um diese Sonderfälle zu behandeln und nur bei Bedarf zu puffern. Hier ist ein Beispiel für ST_Intersection; Ich benutze eine ähnliche Funktion für ST_Difference. Sie müssen entscheiden, ob die Pufferung und die potenzielle Rückgabe eines leeren Polygons in Ihrer Situation akzeptabel sind.

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;

Ein weiterer Vorteil dieses Ansatzes besteht darin, dass Sie die Geometrien genau bestimmen können, die Ihre Probleme verursachen. Fügen Sie einfach einige RAISE NOTICEAnweisungen in den EXCEPTIONBlock ein, um WKT oder etwas anderes auszugeben, mit dessen Hilfe Sie das Problem aufspüren können.

dbaston
quelle
Das ist eine clevere Idee. Ich habe oft festgestellt, dass Schnittpunkte von Linienfolgen herrühren, die bei Kombinationen von Vereinigungen, Unterschieden, Puffern usw. auftreten. Diese können behoben werden, indem alles gepuffert wird oder alles abgelegt wird und nur Polygone / Mutlipolygone ausgewählt werden. Dies ist ein interessanter Ansatz.
John Powell
Sie erwähnen das Einrasten der Geometrie in das 1e-6-Raster, aber ich sitze hier und überlege, ob das Einrasten mit einer Potenz von 2 besser wäre. Da PostGIS (und GEOS) Gleitkommazahlen verwenden, werden die Koordinaten beim Einrasten auf eine Zehnerpotenz möglicherweise nicht sehr stark gekürzt, da die Zahl möglicherweise keine Binärdarstellung mit endlicher Länge aufweist. Aber wenn Sie einfach 2 ^ -16 sagen, würde das garantiert jeden Bruchteil auf nur 2 Bytes verkürzen. Oder denke ich falsch?
jpmc26
12

Durch viele Versuche und Irrtümer wurde mir schließlich klar, dass das non-noded intersectionErgebnis eines Selbstüberschneidungsproblems war. Ich habe einen Thread gefunden, dessen Verwendung empfohlen ST_buffer(geom, 0)wird, um das Problem zu beheben (obwohl es insgesamt sehr viel langsamer ist). Ich habe dann versucht, ST_MakeValid()und wenn direkt auf die Geometrie angewendet, vor jeder anderen Funktion. Dies scheint das Problem robust zu beheben.

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

Ich habe dies als Antwort markiert, da dies der einzige Ansatz zu sein scheint, der mein Problem behebt.

djq
quelle
11

Ich bin auf dasselbe Problem gestoßen (Postgres 9.1.4, PostGIS 2.1.1), und das einzige, was für mich funktionierte, war, die Geometrie mit einem sehr kleinen Puffer zu umschließen.

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

ST_MakeValidhat bei mir nicht funktioniert, auch die Kombination von ST_Nodeund nicht ST_Dump. Der Puffer schien keine Verschlechterung der Leistung zu bewirken, aber wenn ich ihn verkleinerte, erhielt ich immer noch einen nicht geknoteten Kreuzungsfehler.

Hässlich, aber es funktioniert.

Aktualisieren:

Die ST_Buffer-Strategie scheint gut zu funktionieren, aber ich bin auf ein Problem gestoßen, bei dem beim Umwandeln der Geometrie in die Geografie Fehler aufgetreten sind. Wenn sich ein Punkt ursprünglich bei -90,0 befindet und von 0,0000001 gepuffert wird, befindet er sich jetzt bei -90,0000001, was eine ungültige Geografie ist.

Dies bedeutet , dass , obwohl ST_IsValid(geom)war t, ST_Area(geom::geography)kehrte NaNfür viele Funktionen.

Um das nicht-knotige Kreuzung Problem zu vermeiden, während gültige Geographie beibehalten, landete ich mit bis ST_SnapToGridwie so

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

In postgis sollte ST_Node eine Reihe von Linien an Kreuzungen brechen, wodurch das Problem der nicht geknoteten Kreuzungen gelöst werden sollte. Wenn Sie dies in ST_Dump einschließen, wird das zusammengesetzte Array der unterbrochenen Linien generiert.

Etwas verwandt gibt es eine großartige Präsentation von PostGIS: Tips for Power Users, die diese Art von Problemen und Lösungen klar umreißt.

WolfOdrade
quelle
Das ist eine großartige Präsentation (danke @PaulRamsey). Wie soll ich ST_Nodeund verwenden ST_Dump? Ich stelle mir vor, ich müsste sie in der Nähe dieses Teils der Funktion verwenden, bin mir aber nicht sicher: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')in
djq
Hmmm, ich habe nicht bemerkt, dass die beiden Linien eine identische Koordinate haben, was in Ordnung sein sollte. Wenn Sie diese Koordinaten zeichnen, ist der Schnittpunkt ca. 18 cm von der Kreuzung entfernt. Keine wirkliche Lösung, nur eine Beobachtung.
WolfOdrade
Nicht ganz klar, wie ich es st_nodehier benutze - kann ich es vorher benutzen st_intersection?
8.
1
Die Präsentation ist nicht mehr verfügbar. Ich stecke mit dem gleichen Problem fest, wenn ich versuche, ST_Clip (Raster, Polygon)
Jackie
1
@Jackie: Ich habe den Link zur Präsentation in der Antwort gefixt: PostGIS: Tipps für Poweruser .
Pete
1

Nach meiner Erfahrung habe ich meinen non-noded intersectionFehler mit der Funktion St_SnapToGrid behoben, die das Problem der hohen Genauigkeit der Koordinaten des Scheitelpunkts der Polygone behebt .

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
quelle