PostGIS: ST_Equals false lorsque ST_Intersection = 100% de la géométrie?

9

J'ai 2 jeux de données comprenant des données de parcelles cadastrales - environ 125 000 lignes chacune. La colonne de géométrie est constituée de polygones WKB représentant les limites des parcelles; toutes les données sont géométriquement valides (les polygones sont fermés, etc.).

Certaines données récentes sont arrivées dans une projection différente des données de base utilisées pour un travail de comparaison - j'ai donc reprojeté la plus récente (la base était 4326; l'autre était WGA94 qui a été importé dans PostGIS sous le numéro 900914 ... Je l'ai reprojeté en 4326) .

La première étape de l'analyse a consisté à rechercher et à stocker des colis non correspondants; cela consiste en partie à identifier et à stocker des colis de géométrie identique.

J'ai donc exécuté une requête très standard (le bloc de code ci-dessous résume les détails du schéma, etc.):

create table matchdata as
  select  a.*
  from gg2014 a, gg2013 b
  where ST_Equals(a.g1,b.g1)

Résultats ZÉRO.

"Bizarre ..." pensai-je. "Peut-être qu'il y a eu de minuscules changements de vertex provoqués par la reprojection: ce serait ennuyeux et ne devrait vraiment pas se produire."

Heureusement, il existe d'abondantes données aspatiales (5 colonnes identifiantes) qui me permettent d'établir des parcelles qui devraient être spatialement identiques: celles avec le même identifiant, dont la date de changement dans le tableau 2014 était antérieure à la date de changement max dans les données 2013. Cela représentait 120 086 lignes distinctes.

J'ai stocké les identifiants et les géométries dans une table distincte ( match_id) et j'ai exécuté la requête suivante:

select apid, 
       bpid, 
       ST_Area(ag::geometry) as aa, 
       ST_Area(bg::geometry) as ab,
       ST_Area(ST_Intersection(ag,bg)::geometry)/ST_Area(ag::geometry) as inta,
       ST_Area(ST_Intersection(ag,bg)::geometry)/ST_Area(ag::geometry) as intb
from match_id
order by inta

Les 16 premières valeurs pour intaet intbétaient identiques à zéro, les 456 suivantes étaient de 0,99999999-ish (min 0,99999999999994, max 0,99999999999999999) et les lignes 473 à partir de 1 - jusqu'à la ligne 120050, lorsque la zone de l'intersection était supérieure à l'une ou l'autre géométrie (la plus grande valeur intaet intbétait 1,00000000000029, mais quand même).

Voici donc mon énigme: si deux géométries se croisent spatialement entre 99,999999999994% et 100,000000000029% de leurs zones respectives, je voudrais que "ST_Equals" dise "Oui ... je vais vous donner celle-là. Assez près".

Après tout, cela équivaut à une perte d'environ 1 partie sur 16 billions ... c'est-à-dire, comme si la dette nationale américaine avait diminué de moins de 93 cents.

Dans le contexte de la circonférence de la Terre (à environ 40000 km), c'est comme être décalé de 0,000000000025 km, au sommet (car pour aboutir à une différence de surface aussi petite, tout changement de sommet doit être encore plus petit).

Selon TFD (que j'ai R'd), la tolérance ST_Intersects()est théoriquement de 0,00001 m (1 mm), donc les changements implicites dans les sommets (que j'avoue que je n'ai pas vérifiés: je les ferai ST_Dump()et le ferai) sembleraient plus petits que la tolérance. (Je m'en rends compte ST_Intersects !== ST_Intersection(), mais c'est la seule tolérance mentionnée).

Je n'ai pas pu trouver la tolérance correspondante pour la comparaison de sommets entreprise par ST_Equals()... mais il semble vraiment étrange qu'au moins 120 000 de mes rangées devraient passer une évaluation raisonnable de l'identité spatiale, mais ne le font pas.

(Remarque: j'ai également fait le même exercice en utilisant ::geography- avec des résultats qui avaient plus de variabilité, mais toujours plus de 110 000 entrées avec un joli «1» propre).

Existe-t-il un moyen de desserrer la tolérance de ST_Equals, qui ne nécessite pas de creuser dans les interstices du code? Je ne veux pas faire ça.

Sinon, y a-t-il un soupçon dont quelqu'un est au courant?

Remarque: ce serait bien si le «kludge» ne faisait pas une comparaison bilatérale comme

where ST_within(g1, ST_Buffer(g2, 0.0000001))
  and ST_within(g2, ST_Buffer(g1, 0.0000001))


   - I've done that: sure, it works... but it's a gigantic documentation PITA).

Je peux contourner cela, mais écrire les 20 pages pour documenter la solution de contournement - qui ne reviendra jamais que si nous obtenons des données douteuses - est un PITA que je préférerais ne pas avoir à faire étant donné qu'il est probable qu'il s'agisse d'une opération ponctuelle .

(Versions: Postgresql 9.3.5; PostGIS 2.1.3)

GT.
la source
Juste une pensée ici, mais avez-vous essayé de canoniser les nouvelles parcelles en une grille qui est compatible avec les données existantes en utilisant st_snaptogrid?
nickves
Je peux comprendre ne pas vouloir regarder le code source, mais votre question m'a incité à le faire (même si mon C ++ est nul), donc je vous en remercie. Si vous êtes intéressé, je peux poster les sections pertinentes, qui sont toutes sur github.com/libgeos .
John Powell
ST_Equalsrenvoie uniquement truelorsque les géométries sont égales - type de géométrie, nombre de sommets, SRID et valeurs des sommets (dans toutes les dimensions, dans le même ordre). S'il existe un écart, la comparaison s'arrête et falseest renvoyée.
Vince
@Vince: si je comprends bien (d'après les documents), ST_Equals()ignore la directionnalité. J'ai pris cela pour signifier que pour un polygone 2D fermé, cela ne fait aucune différence si les points sont énumérés dans le sens des aiguilles d'une montre contre dans le sens inverse des aiguilles d'une montre. ST_OrderingEquals()est le test le plus serré. Cela dit, après avoir inspecté les sommets (en utilisant ST_Dump()et en calculant les deltas pour chaque sommet), il est clair que la réponse impressionnante de @John Barça est sur l'argent. ST_equals()est contre-indiqué, même pour des données ex ante identiques connues, si une géométrie est reprojetée - sauf si la comparaison est effectuée avec ST_SnapToGrid ().
GT.
Tard dans le passé: un moyen rapide et agréable d'obtenir un test acceptable d'égalité [proche] spatiale est de vérifier quelle proportion de chaque géométrie fait partie de l'intersection. C'est un peu lourd en termes de calcul; calculer (100*(ST_Area(ST_Intersection(a.g1, b.g1))/ST_Area(a.g1)))::int as int_pcaet (100*(ST_Area(ST_Intersection(a.g1, b.g1))/ST_Area(b.g1)))::int as int_pcb(assurez-vous que votre JOINcomprend ST_Intersects(a.g1,b.g1)). Testez si (int_pca, int_pcb)=(100,100)(ou un autre ensemble de seuils). Kludgy, mais cela fera 2,6 millions de colis en ~ 30 minutes (tant que g1 est indexé GIST).
GT.

Réponses:

20

Je suppose que vous coordonnez des transformations qui ont introduit de minuscules erreurs d'arrondi (voir un exemple ci-dessous). Comme il n'y a aucun moyen de définir la tolérance dans ST_Equals, cela provoque ST_Equals à retourner faux pour certaines géométries qui ne diffèrent que par la nième décimale, car les géométries doivent être identiques à tous égards - voir la définition de la matrice d'intersection dans les libges. . Vous pouvez vérifier cela avec un exemple vraiment extrême,

SELECT ST_Equals(
      ST_MakePoint(0,0),
      ST_MakePoint(0,0.000000000000000000000000000000000000000000000000000000000001));

qui retourne faux .

Si vous utilisez ST_SnapToGrid, vous pouvez imposer une précision donnée, par exemple, à dix décimales,

SELECT ST_Equals(
      ST_MakePoint(0,0),
      ST_SnapToGrid(
             ST_MakePoint(0,0.00000000000000000000000000000000000000000000001),
      10));

renvoie maintenant vrai .

Si vous deviez courir,

CREATE table matchdata AS
SELECT  a.*
FROM gg2014 a, gg2013 b
WHERE ST_Equals(ST_SnapToGrid(a.g1, 5), ST_SnapToGrid(b.g1, 5));

fixer une tolérance appropriée, je soupçonne que vos problèmes disparaîtraient.

Voici un lien vers une discussion des développeurs de Postgis sur la tolérance qui suggère qu'elle est moins banale à mettre en œuvre.

J'ai fait quelques conversions entre British National Grid (EPSG: 27700) et lat / lon pour illustrer le point sur la précision de l'arrondi, en prenant un point quelque part à Londres,

SELECT ST_AsText(ST_Transform(ST_SetSrid(ST_MakePoint(525000, 190000),27700),4326));

Retour POINT(-0.19680497282746 51.5949871603888)

et inverser cela,

SELECT ST_AsText(ST_Transform(ST_SetSrid(ST_MakePoint(-0.19680497282746, 51.5949871603888),4326),27700));

Retour POINT(525000.000880007 189999.999516211)

qui est décalé de moins d'un millimètre, mais plus que suffisant pour que ST_Equals retourne faux.

John Powell
la source
La réponse de John Barca est correcte - que de minuscules erreurs d'arrondi peuvent rejeter ST_Equals. Mon expérience (désagréable) a été de travailler avec deux ensembles de données - tous deux projetés de EPSG 4326 à EPSG 3857 - l'un via ArcCatalog (ArcToolbox -> Outils de gestion des données -> Projections et transformations) , tandis que l'autre via GDAL ogr2ogr.
Ralph Tee
Cette réponse m'a beaucoup aidé. Mais j'ai remarqué que les index géographiques n'étaient plus utilisés et que les requêtes prenaient beaucoup trop de temps. Ma solution de contournement consistait à créer des tables temporaires avec les géométries cassées et à leur ajouter des index avant d'exécuter la requête. Existe-t-il une meilleure façon d'accélérer les choses?
hfs
1
@hfs. Je pense que vous pouvez créer un index fonctionnel en utilisant ST_SnapToGrid. Vous avez raison de dire que l'utilisation d'un appel de fonction à l'intérieur d'une opération spatiale égale / intersecte / contient etc. entraînera la non-utilisation de l'index et la création d'un index fonctionnel résoudrait ce problème. Ou vous pouvez mettre à jour en permanence vos données si vous pensez que la précision est fausse et ne pas avoir à utiliser ST_SnapToGrid dans la requête. Cela dépend bien sûr de vos données et de votre cas d'utilisation.
John Powell
2

Avez-vous exécuté la vérification ST_IsValid sur vos géométries? S'ils ne sont pas valides, tous les paris sont désactivés. ST_Intersects et l'autre famille de fonctions de relations spatiales GEOS retournent souvent juste faux car la zone n'est pas bien définie du point de vue de la matrice d'intersection. La raison pour laquelle ST_Buffer fonctionne probablement est qu'il convertit vos géométries invalides en géométries valides. ST_Buffer (..., tinybit) est ce que l'on appelle un outil "pauvre essayant de rendre ma géométrie valide".

LR1234567
la source
La toute première étape avec tout nouvel ensemble de données consiste à sélectionner uniquement des géométries valides à l'aide ST_isValid(g1)- comme cela a été mentionné (obliquement) "[la] colonne de géométrie est des polygones WKB représentant les limites des parcelles; toutes les données sont géométriquement valides (les polygones sont fermés, etc.) ."
GT.
0

Ma réponse arrive un peu tard, mais cela aidera peut-être quelqu'un qui a le même problème. D'après mon expérience, lorsque deux géométries sont effectivement égales mais que ST_Equals renvoie Faux, deux choses peuvent aider:

  1. assurez-vous que la comparaison des géométries est une géométrie unique (pas de MultiLinesting, MultiPoin, etc.)
  2. essayez ST_Equals(st_astext(a.geom), st_astext(b.geom)) au lieu deST_Equals(a.geom , b.geom)

Le premier est déjà mentionné dans la documentation . Le second semble irrationnel mais fonctionne. Je ne sais pas, mais je suppose que cela a à voir avec le format binaire de la géométrie postGIS par défaut.

ioanna tsak
la source