PostGIS: ST_Equals false cuando ST_Intersection = 100% de la geometría?

9

Tengo 2 conjuntos de datos que consisten en datos de parcelas catastrales, aproximadamente 125,000 filas cada uno. La columna de geometría es polígonos WKB que representan límites de parcela; Todos los datos son geométricamente válidos (los polígonos están cerrados, etc.).

Algunos datos recientes llegaron en una proyección diferente a los datos base que se utilizan para un trabajo de comparación, así que reproyecté el más nuevo (la base era 4326; el otro era WGA94 que se introdujo en PostGIS como 900914 ... Lo reproyecté a 4326) .

La primera etapa del análisis fue encontrar y almacenar parcelas no coincidentes; parte de eso es identificar y almacenar parcelas con geometría idéntica.

Entonces ejecuté una consulta muy estándar (el bloque de código a continuación extrae los detalles del esquema, etc.):

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

CERO resultados.

"Extraño ..." pensé. "Tal vez ha habido pequeños cambios en los vértices causados ​​por la reproyección: eso sería molesto y realmente no debería suceder".

Afortunadamente, hay abundantes datos espaciales (5 columnas de identificador) que me permiten establecer parcelas que deberían ser espacialmente idénticas: aquellas con el mismo identificador, cuya fecha de cambio en la tabla de 2014 fue anterior a la fecha de cambio máxima en los datos de 2013. Eso ascendió a 120.086 filas distintas.

Almacené los identificadores y las geometrías en una tabla separada ( match_id), y ejecuté la siguiente consulta:

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

Los primeros 16 valores para intay intbfueron idénticamente cero, los siguientes 456 fueron 0.99999999-ish (mínimo 0.99999999999994, máximo 0.999999999999999), y las filas 473 en adelante fueron 1 - hasta la fila 120050, cuando el área de la intersección fue mayor que cualquiera de las geometrías (la mayor valor para intay intbfue 1.00000000000029, pero aún así).

Así que aquí está mi enigma: si dos geometrías se cruzan espacialmente entre 99.999999999994% y 100.000000000029% de sus áreas respectivas, me gustaría que "ST_Equals" diga "Sí ... te daré esa. Lo suficientemente cerca".

Después de todo, es equivalente a estar fuera por aproximadamente 1 parte en 16 billones ... es decir, como si la deuda nacional de los EE. UU. Hubiera saldado en menos de 93 centavos.

En el contexto de la circunferencia de la Tierra (a ~ 40,000 km), es como estar fuera de 0.0000000025km, arriba (ya que para dar como resultado una diferencia de área tan pequeña, cualquier cambio de vértice debe ser aún más pequeño).

De acuerdo con TFD (que tengo R'd), la tolerancia no ST_Intersects()es 0,00001 m (1 mm), por lo que los cambios implícitos en los vértices (que confieso que no he verificado: lo haré ST_Dump()y lo haré) parecerían ser más pequeños que la tolerancia (Me doy cuenta de eso ST_Intersects !== ST_Intersection(), pero es la única tolerancia mencionada).

No he podido encontrar la tolerancia correspondiente para la comparación de vértices realizada por ST_Equals()... pero parece realmente extraño que al menos 120,000 de mis filas deban pasar cualquier evaluación sensata de identidad espacial, pero no lo hacen.

(Nota: también hice el mismo ejercicio usando ::geography, con resultados que tenían más variabilidad, pero aún más de 110,000 entradas con un bonito '1' limpio).

¿Hay alguna manera de aflojar la tolerancia de ST_Equals, que no requiera profundizar en los intersticios del código? No estoy interesado en hacer eso.

Si no es así, ¿hay algún error que alguien conozca?

Nota: sería bueno si el 'kludge' no estuviera haciendo una comparación bilateral como

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

Puedo evitar esto, pero escribir las 20 páginas para documentar la solución, que solo volverá a aparecer si obtenemos datos dudosos, es un PITA que preferiría no tener que hacer dado que es probable que sea una sola vez .

(Versiones: Postgresql 9.3.5; PostGIS 2.1.3)

GT.
fuente
Solo un pensamiento aquí, pero ¿ha tratado de canonizar las nuevas parcelas en una cuadrícula que sea conforme con los datos de eliminación utilizando st_snaptogrid?
nickves
Puedo entender que no quiera mirar el código fuente, pero su pregunta me indujo a hacerlo (aunque mi C ++ apesta), así que le agradezco por eso. Si está interesado, puedo publicar las secciones relevantes, que están todas en github.com/libgeos .
John Powell
ST_Equalssolo regresa truecuando las geometrías son iguales : tipo de geometría, número de vértices, SRID y valores de vértice (en todas las dimensiones, en el mismo orden). Si hay alguna variación, la comparación se detiene y falsese devuelve.
Vince
@Vince: tal como lo entiendo (de los documentos), ST_Equals()ignora la direccionalidad. Supuse que eso significaba que para un polígono cerrado en 2-D, no hay diferencia si los puntos se enumeran en sentido horario frente a sentido antihorario. ST_OrderingEquals()Es la prueba más estricta. Dicho esto, después de inspeccionar los vértices (usando ST_Dump()y calculando deltas para cada vértice) está claro que la increíble respuesta de @John Barça está en el dinero. ST_equals()está contraindicado, incluso para datos ex ante idénticos conocidos, si se reproyecta una geometría, a menos que la comparación se realice con ST_SnapToGrid ().
GT.
De regreso a esto: una forma rápida y agradable de obtener una prueba aceptable para la igualdad [cercana] espacial es verificar qué proporción de cada geometría es parte de la intersección. Es un poco computacionalmente pesado; calcular (100*(ST_Area(ST_Intersection(a.g1, b.g1))/ST_Area(a.g1)))::int as int_pcay (100*(ST_Area(ST_Intersection(a.g1, b.g1))/ST_Area(b.g1)))::int as int_pcb(asegúrese de que JOINincluye ST_Intersects(a.g1,b.g1)). Pruebe si (int_pca, int_pcb)=(100,100)(o algún otro conjunto de puntos de corte). Kludgy, pero hará 2.6 millones de paquetes en ~ 30min (siempre que g1 esté indexado GIST).
GT.

Respuestas:

20

Supongo que las transformaciones de coordenadas han introducido pequeños errores de redondeo (vea un ejemplo a continuación). Como no hay forma de establecer la tolerancia en ST_Equals, esto provoca que ST_Equals devuelva falso para algunas geometrías que solo difieren en el enésimo lugar decimal, ya que las geometrías tienen que ser idénticas en todos los aspectos; consulte la definición de matriz de intersección en libgeos . Puedes comprobar esto con un ejemplo realmente extremo,

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

que devuelve falso .

Si usa ST_SnapToGrid , puede imponer una precisión dada, por ejemplo, a diez decimales,

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

ahora vuelve verdadero .

Si tuvieras que correr,

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

estableciendo una tolerancia adecuada, sospecho que sus problemas desaparecerían.

Aquí hay un enlace a una discusión del desarrollador de Postgis sobre la tolerancia que sugiere que es menos que trivial de implementar.

Hice un par de conversiones entre British National Grid (EPSG: 27700) y lat / lon para ilustrar el punto sobre la precisión del redondeo, tomando un punto en algún lugar de Londres,

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

devoluciones POINT(-0.19680497282746 51.5949871603888)

y revertir esto,

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

devoluciones POINT(525000.000880007 189999.999516211)

que está desactivado en menos de un milímetro, pero más que suficiente para hacer que ST_Equals devuelva falso.

John Powell
fuente
La respuesta de John Barca es correcta: que pequeños errores de redondeo pueden arrojar ST_Equals. Mi experiencia (desagradable) fue cuando trabajaba con dos conjuntos de datos, ambos proyectados desde EPSG 4326 a EPSG 3857, uno a través de ArcCatalog (ArcToolbox -> Herramientas de administración de datos -> Proyecciones y transformaciones) , mientras que el otro a través de GDAL ogr2ogr.
Ralph Tee
Esta respuesta me ha ayudado mucho. Pero noté que los índices geográficos ya no se usaban y las consultas demoraban demasiado. Mi solución era crear tablas temporales con las geometrías ajustadas y agregarles índices antes de ejecutar la consulta. ¿Hay una mejor manera de acelerar las cosas?
hfs
1
@hfs. Creo que puedes hacer un índice funcional usando ST_SnapToGrid. Tiene razón en que el uso de una llamada de función dentro de una operación espacial igual / intersecta / contiene etc. hará que el índice no se use y hacer un índice funcional resolvería esto. O puede actualizar permanentemente sus datos si cree que la precisión es falsa y luego no tiene que usar ST_SnapToGrid en la consulta. Depende de sus datos y caso de uso, por supuesto.
John Powell
2

¿Ejecutó la verificación ST_IsValid en sus geometrías? Si no son válidos, todas las apuestas están canceladas. ST_Intersects y la otra familia de funciones de relación espacial GEOS a menudo devolverán falso porque el área no está bien definida desde el punto de vista de la matriz de intersección. La razón por la que probablemente funcione ST_Buffer es porque está convirtiendo sus geometrías inválidas en válidas. ST_Buffer (..., tinybit) es lo que se conoce como la herramienta del "pobre intento de hacer que mi geometría sea válida".

LR1234567
fuente
El primer paso con cualquier conjunto de datos nuevo es seleccionar solo geometrías válidas usando ST_isValid(g1)- eso se mencionó (oblicuamente) "[l] columna de geometría son polígonos WKB que representan límites de parcela; todos los datos son geométricamente válidos (los polígonos están cerrados, etc.) ".
GT.
0

Mi respuesta llega un poco tarde, pero quizás ayude a alguien que tiene el mismo problema. Según mi experiencia, cuando dos geometrías son iguales pero ST_Equals devuelve Falso, dos cosas pueden ayudar:

  1. asegúrese de que las geometrías de comparación sean geometrías simples (sin MultiLinesting, MultiPoin, etc.)
  2. intente en ST_Equals(st_astext(a.geom), st_astext(b.geom)) lugar deST_Equals(a.geom , b.geom)

El primero ya se menciona en la documentación . El segundo parece irracional pero funciona. No lo sé, pero supongo que tiene que ver con el formato binario de la geometría postGIS predeterminada.

ioanna tsak
fuente