Combinar cualquiera y todos los polígonos adyacentes


22

Me gustaría hacer pruebas de adyacencia en una capa de parcela (polígonos) y fusionarlas si se ajustan a ciertos criterios (podría ser el tamaño). Según la imagen a continuación, me gustaría fusionar los polígonos 1,2,3 y 4, pero no 5.

Tengo dos problemas:

  1. ST_TOUCHESdevuelve VERDADERO si solo se tocan las esquinas y no un segmento de línea. Creo que necesito ST_RELATE para buscar segmentos de línea compartidos.
  2. Idealmente, me gustaría fusionar TODOS los polígonos adyacentes en uno, pero no estoy seguro de cómo escalar más allá de dos, como en, fusionar 1,2,3 y 4 (y posiblemente más en datos reales) en una ronda.

La estructura que tengo ahora se basa en una autounión ST_TOUCHES.

ingrese la descripción de la imagen aquí

Datos del juguete

CREATE TABLE testpoly AS 
SELECT 
1 AS id, ST_PolyFromText('POLYGON ((0 0, 10 0, 10 20, 00 20, 0 0 ))') AS geom UNION SELECT
2 AS id, ST_PolyFromText('POLYGON ((10 0, 20 0, 20 20, 10 20, 10 0 ))') AS geom UNION SELECT
3 AS id, ST_PolyFromText('POLYGON ((10 -20, 20 -20, 20 0, 10 0, 10 -20 ))') AS geom UNION SELECT
4 AS id, ST_PolyFromText('POLYGON ((20 -20, 30 -20, 30 0, 20 0, 20 -20 ))') AS geom  UNION SELECT 
5 AS id, ST_PolyFromText('POLYGON ((30 0, 40 0, 40 20, 30 20, 30 0 ))') AS geom ;

Selección

SELECT 
    gid, adj_gid,
    st_AStext(st_union(l2.g1,l2.g2)) AS geo_combo
from (
    --level 2
    SELECT
      t1.id AS gid,
      t1.geom AS g1,
      t2.id AS adj_gid,
      t2.geom AS g2
     from
      testpoly  t1,
      testpoly  t2
     where
      ST_Touches( t1.geom, t2.geom ) 
      AND t1.geom && t2.geom 
) 
l2

Aquí está la salida:

+-----+---------+-------------------------------------------------------------------------------+
| gid | adj_gid | geo_combo                                                                     |
+-----+---------+-------------------------------------------------------------------------------+
| 1   | 2       | POLYGON((10 0,0 0,0 20,10 20,20 20,20 0,10 0))                                |
+-----+---------+-------------------------------------------------------------------------------+
| 1   | 3       | MULTIPOLYGON(((10 0,0 0,0 20,10 20,10 0)),((10 0,20 0,20 -20,10 -20,10 0)))   |
+-----+---------+-------------------------------------------------------------------------------+
| 2   | 1       | POLYGON((10 20,20 20,20 0,10 0,0 0,0 20,10 20))                               |
+-----+---------+-------------------------------------------------------------------------------+
| 2   | 3       | POLYGON((10 0,10 20,20 20,20 0,20 -20,10 -20,10 0))                           |
+-----+---------+-------------------------------------------------------------------------------+
| 2   | 4       | MULTIPOLYGON(((20 0,10 0,10 20,20 20,20 0)),((20 0,30 0,30 -20,20 -20,20 0))) |
+-----+---------+-------------------------------------------------------------------------------+
| 3   | 1       | MULTIPOLYGON(((10 0,20 0,20 -20,10 -20,10 0)),((10 0,0 0,0 20,10 20,10 0)))   |
+-----+---------+-------------------------------------------------------------------------------+
| 3   | 2       | POLYGON((20 0,20 -20,10 -20,10 0,10 20,20 20,20 0))                           |
+-----+---------+-------------------------------------------------------------------------------+
| 3   | 4       | POLYGON((20 -20,10 -20,10 0,20 0,30 0,30 -20,20 -20))                         |
+-----+---------+-------------------------------------------------------------------------------+
| 4   | 2       | MULTIPOLYGON(((20 0,30 0,30 -20,20 -20,20 0)),((20 0,10 0,10 20,20 20,20 0))) |
+-----+---------+-------------------------------------------------------------------------------+
| 4   | 3       | POLYGON((20 0,30 0,30 -20,20 -20,10 -20,10 0,20 0))                           |
+-----+---------+-------------------------------------------------------------------------------+
| 4   | 5       | MULTIPOLYGON(((30 0,30 -20,20 -20,20 0,30 0)),((30 0,30 20,40 20,40 0,30 0))) |
+-----+---------+-------------------------------------------------------------------------------+
| 5   | 4       | MULTIPOLYGON(((30 0,30 20,40 20,40 0,30 0)),((30 0,30 -20,20 -20,20 0,30 0))) |
+-----+---------+-------------------------------------------------------------------------------+

Tenga en cuenta que el polígono id = 3 comparte un punto con id = 1 y, por lo tanto, se devuelve como un resultado positivo. Si cambio la cláusula WHERE a ST_Touches( t1.geom, t2.geom ) AND t1.geom && t2.geom AND ST_Relate(t1.geom, t2.geom ,'T*T***T**');no obtengo ningún registro.

  1. Entonces , primero , ¿cómo especifico ST_Relate para asegurarme de que solo se consideren las parcelas que comparten un segmento de línea?

  2. Y luego, ¿cómo fusionaría los polígonos 1,2,3,4 en una ronda, colapsando los resultados de la llamada anterior, reconociendo al mismo tiempo que la adyacencia 1 a 2 es lo mismo que el reverso?

Actualizar

Si agrego esto a la wherecláusula, obviamente solo obtengo polígonos y no multipolígonos, eliminando así los falsos positivos para mis propósitos: se ignorarán los toques de esquina.

GeometryType(st_union(t1.geom,t2.geom)) != 'MULTIPOLYGON'

Si bien esto no es ideal (prefiero usar las comprobaciones de topología ST_RELATEcomo una solución más general), es un camino a seguir. Luego queda la cuestión de eliminarlas y unirlas. Posiblemente, si pudiera generar una secuencia para solo tocar polígonos, podría unirme a eso.

Actualización II

Este parece funcionar para seleccionar polígonos que comparten líneas (pero no esquinas) y, por lo tanto, es una solución más general que la MULTIPOLYGONprueba anterior . Mi cláusula where ahora se ve así:

WHERE
              ST_Touches( t1.geom, t2.geom ) 
              AND t1.geom && t2.geom 

              -- 'overlap' relation
              AND ST_Relate(t1.geom, t2.geom)='FF2F11212') t2 

Ahora lo que queda es cómo fusionar más de un par de polígonos, pero para un número arbitrario que se ajuste a los criterios, de una sola vez.


2
Estoy seguro de que ST_Relate es la forma correcta. Resolví un problema similar comprobando que la longitud de las intersecciones era mayor que cero para excluir las intersecciones de un solo punto. Un truco, pero funciona.
John Powell

Si hubiera una forma de agrupar polígonos contiguos en matrices, podría modificar la ST_IntersectionArray[función] [1] para que funcione con ST_Union [1]: gis.stackexchange.com/a/60295/36886
raphael

2
Con respecto a la agrupación de polígonos contiguos, puede modificar el algoritmo de agrupación de abajo hacia arriba que escribí aquí ( gis.stackexchange.com/a/115715/36886 ) para probar la adyacencia en lugar del espacio y luego usar ST_Union mientras agrupa en los cluster_ids resultantes
raphael

3
También hay ST_ClusterIntersectimg que puede hacer lo que necesita. Necesitas Postgis 2.2
John Powell

Respuestas:


3

No pude evitar pensar que su ejemplo es en realidad una trama y, aunque mencionó que le gustaría fusionarse en base a "ciertos criterios (podría ser el tamaño)", me gustaría darle una oportunidad con una conversión de trama.

Para su ejemplo específico, esto funcionaría:

WITH rast AS (
  SELECT 
  ST_UNION(ST_AsRaster(geom,10, 20, '2BUI')) r
  FROM testpoly 
)
,p AS (
    SELECT (ST_DumpAsPolygons(r)).geom FROM rast
)
SELECT t.id,p.* 
FROM p
LEFT JOIN testpoly  t ON ST_Equals(p.geom, t.geom)

Lo que sucede es que, dado que sus polígonos son celdas perfectamente alineadas, se convertirán muy bien en un ráster (tamaño de celda 10x20). Los dumpaspolygons lo ayudan aquí al fusionar todas las celdas adyacentes en una y al comparar con los polígonos originales, incluso podrá recuperar la identificación de los polígonos no fusionados.

Habiendo explicado esto, tengo mucha curiosidad sobre cómo se escalará y qué tan grande es su conjunto de datos: D


Idea inteligente. Sin embargo, este es un ejemplo de juguete: mis datos reales son una capa de parcela que no se asignará perfectamente a los rásteres.
ako

3

Aquí hay un ejemplo de cómo hacer esto en estilo de procedimiento con múltiples pases debajo del capó.

CREATE TABLE joined_testpoly AS SELECT array[id] ids, geom FROM testpoly; 

Debería poder llevar más columnas y aplicar criterios adicionales para unirse modificando cómo funciona la LIMIT 1selección a continuación:

CREATE OR REPLACE FUNCTION reduce_joined_testpoly()
RETURNS void
AS $$
DECLARE
  joined_row joined_testpoly%ROWTYPE;
BEGIN
  LOOP
     SELECT array_cat(a.ids, b.ids), st_union(a.geom, b.geom)
         INTO joined_row 
     FROM joined_testpoly a INNER JOIN joined_testpoly b
           on a.ids != b.ids
              and ST_Touches(a.geom, b.geom) and a.geom && b.geom 
              and ST_Relate(a.geom, b.geom)='FF2F11212'
         LIMIT 1;
     IF NOT FOUND THEN
           EXIT;
     END IF;
     INSERT INTO joined_testpoly VALUES (joined_row.ids, joined_row.geom);
     DELETE FROM joined_testpoly
         WHERE joined_testpoly.ids <@ joined_row.ids 
           AND joined_testpoly.ids != joined_row.ids;
  END LOOP;
  RETURN;
END;
$$ LANGUAGE plpgsql;

Ejecuta la cosa:

SELECT reduce_joined_testpoly();

Uniones adecuadas, sin multipolígonos:

SELECT ids, st_geometrytype(geom), st_area(geom), st_numgeometries(geom) 
FROM joined_testpoly;
    ids    | st_geometrytype | st_area | st_numgeometries 
-----------+-----------------+---------+------------------
 {5}       | ST_Polygon      |     200 |                1
 {1,2,3,4} | ST_Polygon      |     800 |                1

2

Aquí hay otra estrategia de referencia (que no funciona) (que no pude excluir el caso de punto de contacto único). Debería ser más rápido que mi otra respuesta, ya que solo toma 'una pasada'.

SELECT st_numgeometries(g), (SELECT st_union(x.geom) FROM st_dump(g) x GROUP BY g)
FROM (
    SELECT unnest(st_clusterintersecting(geom)) g, id < 100 as other_arbitrary_grouping 
    FROM testpoly
    GROUP BY other_arbitrary_grouping) c;

(siéntase libre de enmendar y publicar otra respuesta si alguien puede obtener la geometría id = 5 en su propio grupo)

Para volver a la lista de identificadores, etc., tendría que usar st_containspara volver a unirse a la tabla testpoly como se detalla en la siguiente respuesta: /programming//a/37486732/6691 pero no pude hacer que eso funcione para polígonos por alguna razón.


2

Aquí hay una puñalada rápida usando su consulta original ajustada un poco:

with gr as (SELECT 
    gid, adj_gid,
    st_AStext(st_union(l2.g1,l2.g2)) AS geo_combo
from (
    --level 2
    SELECT
      t1.id AS gid,
      t1.geom AS g1,
      t2.id AS adj_gid,
      t2.geom AS g2
     from
      testpoly  t1,
      testpoly  t2
     where
      ST_Touches( t1.geom, t2.geom ) 
      AND ST_Relate(t1.geom,t2.geom, '****1****')
      AND t1.geom && t2.geom 
) 
l2) select ST_AsText(st_union(gr.geo_combo)) from gr;

Referencias: https://postgis.net/docs/using_postgis_dbmanagement.html#DE-9IM

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.