SQL Server: seleccione todos los polígonos anidados dentro de un polígono más grande

9

Esta es una pregunta de geometría de SQL Server aparentemente simple que pensé que tendría una solución lista para usar, pero no tengo suerte para encontrarla.

Mi intención es seleccionar todos los registros dentro de una tabla que tengan polígonos anidados (contenidos) dentro de un polígono más grande de otra tabla. Esperaba funciones STWithiny STContainscomo las soluciones que necesitaba, pero desafortunadamente, ambas identifican solo los polígonos internos dentro de los anidados en el polígono más grande, no aquellos polígonos anidados que están tocando el límite del polígono más grande. Ver imagen por ejemplo.Resultado de las funciones STWithin y STContains

Una opción alternativa que funcionó para mis necesidades fue STIntersection. Sin embargo, el problema con esta función es que solo devuelve la columna de geometría. Me gustaría obtener el ID de registro en su lugar. ¿Alguien tiene una sugerencia sobre cómo se puede hacer esto?

STWithin:

select a.bg10 from
gis.usa_10_block_group a
join gis.usa_10_mkt_definition b
on a.shape.STWithin(b.shape) = 1
where b.mktname = 'Loop'

STContains:

select a.bg10 from
gis.usa_10_block_group a
join gis.usa_10_mkt_definition b
on b.shape.STContains(a.shape) = 1
where b.mktname = 'Loop'

STIntersection:

select a.shape.STIntersection(b.shape)
from gis.usa_10_block_group a
join gis.usa_10_mkt_definition b
on a.shape.STIntersects(b.shape) = 1
where b.mktname = 'Loop'

Editar:

Una sugerencia fue omitir STIntersectiony usar únicamente de la STIntersectssiguiente manera:

STIntersects:

select a.bg10
from gis.usa_10_block_group a
join gis.usa_10_mkt_definition b
on a.shape.STIntersects(b.shape) = 1
where b.mktname = 'Loop'

El problema con este enfoque es que STIntersectsparece seleccionar todos los polígonos dentro o fuera y tocar el polígono más grande, no solo los estrictamente dentro. Ver imagen por ejemplo.Resultado de la función STIntersects

usuario1185790
fuente
Puede intentar hacer un búfer mínimo en su polígono que contiene y luego usar STContainso STWithin. Realmente no es un buen truco, pero obtendrá los resultados que desea. La otra opción sería hacer las intersecciones STI con una comparación del área de intersección y el área de polígonos.
MickyT
Empecé a trabajar en un área comparar, pero se metió en un agujero de conejo con la comparación de la geometría convertido a la zona a un número, etc., etc ...
DPSSpatial

Respuestas:

8

En teoría, las consultas que ha realizado deberían devolver los polígonos que dijo que no se han devuelto. Eso me hace sospechar que puede estar encontrando problemas de error de punto flotante que SQL Server tiene con los tipos de datos espaciales. De ahí mi comentario sobre amortiguar el polígono delimitador con una cantidad mínima.
Entonces, algo como lo siguiente debería obtener los resultados que desea.

SELECT a.bg10 
FROM gis.usa_10_block_group a
    JOIN gis.usa_10_mkt_definition b
        ON a.shape.STWithin(b.shape.STBuffer(0.0001)) = 1
WHERE b.mktname = 'Loop'

Aquí hay un ejemplo rápido del comportamiento esperado de algunos de los métodos espaciales.

SELECT Geometry::STGeomFromText(WKT,0), Description
    , Geometry::STGeomFromText('POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',0).STIntersects(Geometry::STGeomFromText(WKT,0)) Intersects
    , Geometry::STGeomFromText('POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',0).STContains(Geometry::STGeomFromText(WKT,0)) Contained
    , Geometry::STGeomFromText('POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',0).STOverlaps(Geometry::STGeomFromText(WKT,0)) Overlaps
    , Geometry::STGeomFromText('POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',0).STTouches(Geometry::STGeomFromText(WKT,0)) Touches
FROM (VALUES
    ('POLYGON((0 0, 20 0, 20 20, 0 20, 0 0))'            ,'Interior corner')
    ,('POLYGON((90 90, 100 90, 100 100, 90 100, 90 90))' ,'Interior corner')
    ,('POLYGON((20 20, 40 20, 40 40, 20 40, 20 20))'     ,'Interior')
    ,('POLYGON((50 0, 70 0, 70 20, 50 20, 50 0))'        ,'Interior edge')
    ,('POLYGON((50 80, 70 80, 70 100, 50 100, 50 80))'   ,'Interior edge')
    ,('POLYGON((80 50, 100 50, 100 70, 80 70, 80 50))'   ,'Interior edge')
    ,('POLYGON((90 0, 110 0, 110 20, 90 20, 90 0))'      ,'Overlap')
    ,('POLYGON((100 50, 120 50, 120 70, 100 70, 100 50))','Exterior edge')
    )P(WKT,Description)
UNION ALL 
SELECT Geometry::STGeomFromText('POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))',0),'Bounding Area',null,null,null,null

Resultados

Description     Intersects Contained Overlaps Touches
--------------- ---------- --------- -------- -------
Interior corner 1          1         0        0
Interior corner 1          1         0        0
Interior        1          1         0        0
Interior edge   1          1         0        0
Interior edge   1          1         0        0
Interior edge   1          1         0        0
Overlap         1          0         1        0
Exterior edge   1          0         0        1
Bounding Area   NULL       NULL      NULL     NULL
MickyT
fuente
¡Esto funciona muy bien! Tuve que reducir el tamaño del búfer a 0.001, pero el concepto funcionó. Sospecho que el problema es que las geometrías de la tabla gis.usa_10_mkt_definition no se derivan de la misma topología que gis.usa_10_block_group, lo que explica la razón por la cual se desvía del resultado esperado que mencionó. Probé el uso de STWithin usando dos tablas que comparten la misma topología, y no se necesitaba un búfer.
user1185790
2

La consulta de intersección debería tener este aspecto (suponiendo que desea que todos los registros vuelvan de 'a'):

select a.* --get all columns from table 'a'
from gis.usa_10_block_group a
join gis.usa_10_mkt_definition b
on a.shape.STIntersects(b.shape) = 1
where b.mktname = 'Loop'

Si desea solo las áreas de a que se cruzan b (es decir, recortar aa b), entonces agregue la intersección STI

select a.bg10
, a.STIntersection(b.geom) --clipped geometry from a against b
    from gis.usa_10_block_group a
    join gis.usa_10_mkt_definition b
    on a.shape.STIntersects(b.shape) = 1
    where b.mktname = 'Loop'

Pero esto no te da los polígonos que están dentro de b todavía ...

Este tipo de polígono en polígono es muy irritante con los límites y su coincidencia: para estar 'Dentro', los límites de a no pueden coincidir con los límites de b, lo mismo ocurre con 'Contiene'.

Según estas definiciones, ¿cuántos de sus polígonos en a están realmente dentro de b ...?

Entonces, ¿desea almacenar en búfer b antes de seleccionar polígonos en a que están dentro? ¿O hacer un buffer negativo en un?

No estoy seguro de cuál es la respuesta exacta aquí ...

DPSSpacial
fuente
Ver edición para una explicación completa de por qué esto no es exactamente lo que busco
user1185790
Veo lo que estás tratando de hacer ... trabajando en algo ahora ...
DPSSpatial