Realización de desglose / superposición de polígonos en PostGIS?

8

Me enfrento a un desafío con PostGIS que parece que no puedo entender. Sé que puedo resolver esto usando un lenguaje de programación (y ese es mi plan de respaldo), pero realmente me gusta resolver esto en PostGIS. Intenté buscar, pero no pude encontrar ninguna respuesta que coincida con mi problema, esto podría deberse a que no estoy seguro de mis términos de búsqueda, así que discúlpeme y apúnteme en la dirección correcta, de hecho, hay una respuesta.

Mi problema es este:

  • Tengo una mesa con polígonos mixtos / polígonos múltiples
  • Cada polígono (múltiple) tiene un atributo que lo clasifica (prioridad)
  • Cada polígono también tiene un valor que me gustaría saber
  • Tengo un área de búsqueda (polígono)
  • Para mi área de consulta, quiero encontrar el área cubierta por cada valor de polígono

Ejemplo:

Digamos que tengo los tres polígonos representados en rojo, verde e índigo aquí: Ejemplo

Y que el rectángulo azul más pequeño es mi polígono de consulta

Además, los atributos son

geom   | rank | value
-------|------|----  
red    |  3   | 0.1
green  |  1   | 0.2
indigo |  2   | 0.2

Lo que quiero es seleccionar estas geometrías, de modo que el rango más alto (verde) llene todo el área que pueda (es decir, la intersección entre mi consulta geom y esa geom), luego el siguiente más alto (índigo) llena la intersección entre la consulta geom y el geom MENOS el ya cubierto) etc.

Algo como esto: ingrese la descripción de la imagen aquí

He encontrado esta pregunta: ¿ Usando ST_Difference para eliminar características superpuestas? pero no parece hacer lo que quiero.

¡Yo mismo puedo descubrir cómo calcular áreas y cosas así, así que una consulta que me da las tres geometrías como se muestra en la segunda imagen está bien!

Información adicional: - Esta no es una tabla grande (~ 2000 filas) - puede haber cero o múltiples superposiciones (no solo tres) - puede que no haya ningún polígono en mi área de consulta (o solo en partes de ella) - i ' m ejecutando postgis 2.3 en postgres 9.6.6

Mi solución alternativa es hacer una consulta como esta:

SELECT 
ST_Intersection(geom, querygeom) as intersection, rank, value
FROM mytable
WHERE ST_Intersects(geom, querygeom)
ORDER by rank asc

Y luego iterativamente "corta" partes de las geometrías en el código. Pero, como dije, realmente me gustaría hacer esto en PostGIS

atlefren
fuente
2
no puedo darte una respuesta en este momento, pero si estás dispuesto a WITH RECURSIVE ...
intentarlo
1
ah y
mira
¡Gracias! Intentaré esto mañana si nadie más se siente obligado a proporcionar una solución completa.
atlefren

Respuestas:

7

Creo que esto funciona.

Es una función de ventana, obteniendo la diferencia entre la intersección de cada intersección de geometrías con el cuadro de consulta y la unión de las geometrías anteriores.

La fusión es necesaria ya que la unión de las geometrías anteriores para la primera geometría es nula, lo que da un resultado nulo, en lugar de lo que se desea.

WITH a(geom, rank, val) AS
(
    VALUES
    ('POLYGON((1 1, 1 5, 5 5, 5 1, 1 1))'::geometry,3,0.1),
    ('POLYGON((2 3, 2 8, 4 8, 5 3,2 3))'::geometry,1,0.2),
    ('POLYGON((4 4, 4 6, 6 6, 6 4,4 4))'::geometry,2,0.2)
)
,q AS
(
    SELECT 'POLYGON((3 3, 3 4.5, 12 4.5,12 3, 3 3))'::geometry geom
) 
SELECT 
  ST_Difference(
    ST_Intersection(a.geom, q.geom), 
    COALESCE(ST_Union(a.geom) 
           OVER (ORDER BY rank ROWS BETWEEN UNBOUNDED PRECEDING and 1 PRECEDING),
       'POLYGON EMPTY'::geometry)
  ) geom 
FROM a,q
WHERE ST_Intersects(a.geom, q.geom);

Sin embargo, no estoy seguro de cómo funciona. Pero dado que tanto ST_Union como ST_Intersection están marcados como inmutables, puede que no sea tan malo.

Nicklas Avén
fuente
¡Esto funcionó a las mil maravillas! Solo tiene que ajustar su consulta en otra consulta para eliminar las
colecciones de
5

Un enfoque un poco diferente a esto. Hay una advertencia de que no sé cómo escalará el rendimiento, pero en una tabla indexada debería estar bien. Realiza casi lo mismo que la consulta de Nicklas (¿un poco más lenta?), Pero la medición en una muestra tan pequeña está cargada.

Se ve mucho más feo que la consulta de Nicklas, pero evita la recurrencia en la consulta.

WITH 
    a(geom, rank, val) AS
    (
        VALUES
        ('POLYGON((1 1, 1 5, 5 5, 5 1, 1 1))'::geometry,3,0.1),
        ('POLYGON((2 3, 2 8, 4 8, 5 3, 2 3))'::geometry,1,0.2),
        ('POLYGON((4 4, 4 6, 6 6, 6 4, 4 4))'::geometry,2,0.2)
    ),
    polygonized AS (
        -- get the basic building block polygons
        SELECT (ST_Dump(         -- 5. Dump out the polygons
            ST_Polygonize(line)) -- 4. Polygonise the linework
            ).geom AS mypoly
        FROM (
            SELECT 
                ST_Node(                  -- 3. Node lines on intersection
                    ST_Union(             -- 2. Union them for noding
                        ST_Boundary(geom) -- 1. Change to linestrings
                    ) 
                ) 
                AS line
            FROM a
        ) line
    ),
    overlayed AS ( 
        -- Overlay with original polygons and get minimum rank.
        -- Union and dissolve interior boundaries for like ranks.
        SELECT (ST_Dump(ST_UnaryUnion(geom))).geom, rank 
        FROM ( 
            -- union up the polygons by rank, unary union doesn't count as an aggregate function?
            SELECT ST_Union(mypoly) geom, rank 
            FROM ( 
                -- get the minimum rank for each of the polygons
                SELECT p.mypoly, min(rank) rank
                FROM polygonized p 
                    INNER JOIN a ON ST_Contains(a.geom,ST_PointOnSurface(p.mypoly))
                GROUP BY p.mypoly
                ) g
            GROUP BY rank
            ) r
    )
-- get the intersection of the query area with the overlayed polygons
SELECT ST_Intersection(o.geom,'POLYGON((3 3, 3 4.5, 12 4.5,12 3, 3 3))'::Geometry), rank
FROM overlayed o
WHERE ST_Intersects(o.geom,'POLYGON((3 3, 3 4.5, 12 4.5,12 3, 3 3))'::Geometry) and
    -- Intersection can do funky things
    GeometryType(ST_Intersection(o.geom,'POLYGON((3 3, 3 4.5, 12 4.5,12 3, 3 3))'::Geometry)) like '%POLYGON';
MickyT
fuente
1

Como balbuceé, WITH RECURSIVEagregaré una respuesta rápida y sucia al usarla.

Esto funciona tan bien como la solución de @ NicklasAvén en tres polígonos, no se pudo probar cuando se amplió.
Tal como están las dos soluciones, esta tiene un pequeño beneficio sobre la otra: si, por ejemplo, el Polígono con rango = 2 está contenido por el de rango = 1 , los ...WHERE GeometryType = 'POLYGON'filtros se eliminan mientras que de lo contrario habrá un GEOMETRYCOLLECTION EMPTY(Cambié la geometría del Polígono respectivo en mi solución en consecuencia para dar un ejemplo; esto también es cierto para otros casos cuando no se encuentra una intersección con la diferencia). Sin embargo, esto se incluye fácilmente en las otras soluciones y puede que ni siquiera sea motivo de preocupación.

WITH RECURSIVE
    a(geom, rank, val) AS (
        VALUES
           ('POLYGON((1 1, 1 5, 5 5, 5 1, 1 1))'::geometry,3,0.1),
           ('POLYGON((2 3, 2 8, 4 8, 5 3,2 3))'::geometry,1,0.2),
           ('POLYGON((2.1 3.1, 2.1 7.9, 3.9 7.9, 4.9 3.1,2.1 3.1))'::geometry,2,0.2)
    ),
    q AS (
        SELECT 'POLYGON((3 3, 3 4.5, 12 4.5,12 3, 3 3))'::geometry geom
    ),
    src AS (
        SELECT ROW_NUMBER() OVER(ORDER BY rank) AS rn,
               ST_Intersection(q.geom, a.geom) AS geom,
               rank,
               val
        FROM a
        JOIN q
           ON ST_Intersects(a.geom, q.geom)
    ),
    res AS (
        SELECT s.geom AS its,
               ST_Difference(q.geom, s.geom) AS dif,
               s.rank,
               s.val,
               2 AS icr
        FROM src AS s,
             q
        WHERE s.rn = 1
        UNION ALL
        SELECT ST_Intersection(s.geom, r.dif) AS its,
               ST_Difference(r.dif, s.geom) AS dif,
               s.rank,
               s.val,
               icr + 1 AS icr
        FROM src AS s,
             res AS r
        WHERE s.rank = r.icr
    )

SELECT its AS geom,
       rank,
       val
FROM res
WHERE GeometryType(its) = 'POLYGON'
geozelot
fuente