Quelle est la meilleure solution pour résoudre un problème d’intersection sans nœud dans PostGIS?

38

J'utilise une PL/Rfonction et PostGISgénère des polygones de voronoï autour d'un ensemble de points. La fonction que j'utilise est définie ici . Lorsque j'utilise cette fonction sur un ensemble de données particulier, le message d'erreur suivant s'affiche:

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

En examinant cette partie du message d'erreur:

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

Voici à quoi ressemble le problème mentionné ci-dessus:

entrez la description de l'image ici

J'ai d'abord pensé que ce message pouvait être causé par l'existence de points identiques et j'ai essayé de résoudre ce problème en utilisant la st_translate()fonction utilisée de la manière suivante:

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

Cela résout le problème, mais ce qui me préoccupe, c’est que je traduis maintenant tous les points jusqu’à environ 20 m dans la direction x / y. Je ne peux pas non plus savoir quel montant de traduction est nécessaire. Par exemple, dans cet ensemble de données par essais et erreurs, un20m * random number est ok, mais comment puis-je savoir si cela doit être plus grand?

Basé sur l'image ci-dessus, je pense que le problème est que le point intersecte la ligne alors que l'algorithme tente de l'intersecter du polygone. Je ne suis pas sûr de ce que je devrais faire pour m'assurer que le point se trouve dans un polygone, plutôt que de croiser une ligne. L'erreur se produit sur cette ligne:

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

J'ai lu cette question précédente, qu'est-ce qu'une "intersection sans nœud"? pour essayer de mieux comprendre ce problème et apprécierait tout conseil sur la meilleure façon de le résoudre.

djq
la source
Si vos entrées ne sont pas valides pour commencer, exécutez ST_MakeValid () sur elles. S'ils sont valides, l'ajout d'entropie, comme vous le faites, est le prochain tour disponible et peut-être le dernier tour pour le moment.
Paul Ramsey
Oui, j'utilise WHERE ST_IsValid(p.geom)pour filtrer les points au départ.
Djq

Réponses:

30

D'après mon expérience, ce problème est presque toujours causé par:

  1. Haute précision dans vos coordonnées (43.23149999999999996), associé à
  2. Lignes presque coïncidentes mais non identiques

Le "coup de pouce" approche de la ST_Buffer solutions vous permet d’éviter 2, mais tout ce que vous pouvez faire pour résoudre ces causes sous-jacentes, telles que l’accrochage de votre géométrie sur une grille 1e-6, vous facilitera la vie. Les géométries mises en mémoire tampon conviennent généralement bien pour les calculs intermédiaires, tels que la zone de chevauchement, mais vous devrez faire attention à les conserver, car elles peuvent aggraver vos problèmes proches mais pas tout à fait graves à long terme.

La capacité de gestion des exceptions de PostgreSQL vous permet d'écrire des fonctions de wrapper pour gérer ces cas particuliers, en mettant en mémoire tampon uniquement lorsque cela est nécessaire. Voici un exemple pour ST_Intersection; J'utilise une fonction similaire pour ST_Difference. Vous devrez décider si la mise en mémoire tampon et le retour potentiel d'un polygone vide sont acceptables dans votre situation.

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 autre avantage de cette approche est que vous pouvez identifier les géométries qui causent réellement vos problèmes. ajoutez simplement quelques RAISE NOTICEinstructions dans le EXCEPTIONbloc pour générer WKT ou autre chose qui vous aidera à localiser le problème.

Dbaston
la source
C'est une idée intelligente. J'ai souvent constaté que les problèmes d'intersection provenaient de chaînes de lignes apparaissant lors de combinaisons d'unions, de différences, de tampons, etc., qui peuvent être corrigées en tamponnant tout, ou en vidant tout et en sélectionnant uniquement des polygones / mutlipolygones. C'est une approche intéressante.
John Powell
Vous avez parlé de caler la géométrie sur la grille 1e-6, mais je me demande si une prise de 2 serait mieux. PostGIS (et GEOS) utilisant des nombres en virgule flottante, il est donc possible que l’accrochage à une puissance de 10 ne tronque pas beaucoup les coordonnées car le nombre peut ne pas avoir de représentation binaire de longueur finie. Mais si vous vous adaptez à dire 2 ^ -16, je pense que nous serions assurés de tronquer toute partie décimale à seulement 2 octets. Ou est-ce que je me trompe?
jpmc26
12

Après beaucoup d'essais et d'erreurs, j'ai finalement réalisé que cela non-noded intersectionrésultait d'un problème d'auto-intersection. J'ai trouvé un fil qui suggère d'utiliser ST_buffer(geom, 0)peut être utilisé pour résoudre le problème (même si cela le rend beaucoup plus lent dans l'ensemble). J'ai ensuite essayé d'utiliser ST_MakeValid()et lorsqu'il est appliqué directement à la géométrie avant toute autre fonction. Cela semble résoudre le problème de manière robuste.

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

J'ai marqué ceci comme étant la réponse car cela semble être la seule approche qui résout mon problème.

djq
la source
11

J'ai rencontré ce même problème (Postgres 9.1.4, PostGIS 2.1.1), et la seule chose qui a fonctionné pour moi a été d'envelopper la géométrie avec un très petit tampon.

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

ST_MakeValidn'a pas fonctionné pour moi, ni la combinaison de ST_Nodeet ST_Dump. La mémoire tampon ne semblait pas entraîner de dégradation des performances, mais si je la réduisais plus petite, je recevais quand même une erreur d'intersection sans nœud.

Moche, mais ça marche.

Mise à jour:

La stratégie ST_Buffer semble bien fonctionner, mais j'ai rencontré un problème qui entraînait des erreurs lors de la conversion de la géométrie en géographie. Par exemple, si un point est à l'origine à -90.0 et qu'il est mis en mémoire tampon par 0.0000001, il est maintenant à -90.0000001, ce qui est une géographie non valide.

Cela signifie que même si ST_IsValid(geom)était t, de ST_Area(geom::geography)retour NaNpour de nombreuses fonctionnalités.

Pour éviter le problème d'intersection non noded, tout en maintenant la géographie valide, je fini par utiliser ST_SnapToGridcomme si

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

Dans le postgis, ST_Node devrait interrompre une série de lignes aux intersections, ce qui devrait résoudre le problème des intersections sans nœud. Envelopper cela dans ST_Dump génère le tableau composite des lignes pointillées.

Légèrement lié, il y a une présentation géniale PostGIS: Conseils pour utilisateurs chevronnés qui décrit clairement ce type de problèmes et de solutions.

WolfOdrade
la source
C'est une excellente présentation (merci @PaulRamsey). Comment devrais-je utiliser ST_Nodeet ST_Dump? J'imagine que je devrais les utiliser près de cette partie de la fonction, mais je ne suis pas certain: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')in
djq
Hmmm, je n'ai pas remarqué que les deux lignes avaient une coordonnée identique, ce qui devrait bien se passer. Si vous tracez ces coordonnées, le point d'intersection est à environ 18 cm de l'intersection. Pas vraiment une solution, juste une observation.
WolfOdrade
Pas tout à fait clair sur la façon dont j'utilise st_nodeici - puis-je l'utiliser avant st_intersection?
djq
1
La présentation n'est plus disponible. Je suis coincé avec le même problème, en essayant de ST_Clip (rast, polygone)
Jackie
1
@ Jackie: J'ai corrigé le lien vers la présentation dans la réponse: PostGIS: astuces pour les utilisateurs privilégiés .
Pete
1

D'après mon expérience, j'ai résolu mon non-noded intersectionerreur en utilisant la fonction St_SnapToGrid qui résolvait le problème de la précision élevée des coordonnées du sommet des polygones.

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
la source