Mesclar todos e quaisquer polígonos adjacentes

22

Eu gostaria de fazer testes de adjacência em uma camada de parcelas (polígonos) e mesclá-los se eles se encaixarem em determinados critérios (pode ser o tamanho). Conforme a figura abaixo, eu gostaria de mesclar polígonos 1,2,3 e 4, mas não 5.

Eu tenho dois problemas:

  1. ST_TOUCHESretorna VERDADEIRO se apenas os cantos tocarem e não um segmento de linha. Acho que preciso ST_RELATE para verificar segmentos de linha compartilhados.
  2. Idealmente, eu gostaria de mesclar TODOS os polígonos adjacentes em um, mas não sei como escalar além de dois - como em, mesclar 1,2,3 e 4 (e possivelmente mais em dados reais) em uma rodada.

A estrutura que tenho agora é baseada em uma auto-junção ST_TOUCHES.

insira a descrição da imagem aqui

Dados do brinquedo

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 ;

Seleção

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

Aqui está a saída:

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

Observe que o id do polígono = 3 compartilha um ponto com o id = 1 e, portanto, é retornado como um resultado positivo. Se eu alterar a cláusula WHERE para ST_Touches( t1.geom, t2.geom ) AND t1.geom && t2.geom AND ST_Relate(t1.geom, t2.geom ,'T*T***T**');não obter nenhum registro.

  1. Então , primeiro , como faço para especificar ST_Relate para garantir que apenas os pacotes que compartilham um segmento de linha sejam considerados.

  2. E então, como eu mesclaria polígonos 1,2,3,4 em uma rodada, recolhendo os resultados da chamada acima, reconhecendo o tempo todo que a adjacência 1 a 2 é a mesma do inverso?

Atualizar

Se eu adicionar isso à wherecláusula, obviamente obtenho apenas polígonos e não multipolígonos, eliminando assim falsos positivos para meus propósitos - os toques nos cantos serão ignorados.

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

Embora isso não seja o ideal (prefiro usar verificações de topologia ST_RELATEcomo uma solução mais geral), é um caminho a seguir. Resta, então, a questão de enganar e uni-los. Possivelmente, se eu pudesse gerar uma sequência apenas para tocar polígonos, eu poderia se unir a isso.

Atualização II

Este parece funcionar para selecionar polígonos que compartilham linhas (mas não cantos) e, portanto, é uma solução mais geral que o MULTIPOLYGONteste acima . Minha cláusula where agora se parece com isso:

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

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

Agora, o que resta ainda é como fazer a mesclagem para mais do que apenas um par de polígonos, mas para um número arbitrário que atenda aos critérios, de uma só vez.

ako
fonte
2
Estou certo de que ST_Relate é a maneira correta. Resolvi um problema semelhante, verificando se o comprimento das interseções era maior que zero para excluir interseções de ponto único. Um hack, mas funciona.
John Powell
Se houvesse uma maneira de agrupar os polígonos contíguos em matrizes que você poderia, então, modificar o ST_IntersectionArray[função] [1] para o trabalho com ST_Union [1]: gis.stackexchange.com/a/60295/36886
raphael
2
Com relação ao agrupamento de polígonos contíguos, você pode modificar o algoritmo de agrupamento de baixo para cima que escrevi aqui ( gis.stackexchange.com/a/115715/36886 ) para testar a adjacência em vez do espaço e, em seguida, usar ST_Union ao agrupar os cluster_ids resultantes
raphael
3
Há também ST_ClusterIntersectimg que pode fazer o que você precisa. Você precisa do Postgis 2.2
John Powell

Respostas:

3

Não pude deixar de pensar que seu exemplo é realmente uma varredura e, embora você tenha mencionado que gostaria de mesclar com base em "certos critérios (pode ser o tamanho)", gostaria de tentar com uma conversão de varredura.

Para o seu exemplo específico, isso funcionaria:

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)

O que acontece é que, uma vez que seus polígonos são células perfeitamente alinhadas, eles se convertem muito bem em uma varredura (tamanho de célula 10x20). Os dumpaspolygons ajudam você aqui, mesclando todas as células adjacentes em uma e comparando com os polígonos originais, você poderá recuperar o ID de polígonos não mesclados.

Tendo explicado isso, estou muito curioso sobre como isso seria dimensionado e qual o tamanho do seu conjunto de dados: D

inclinar
fonte
Ideia inteligente. Porém, este é um exemplo de brinquedo - meus dados reais são uma camada de parcelas que não é mapeada ordenadamente para rasters.
ako
3

Aqui está um exemplo de como fazer isso no estilo processual com várias passagens sob o capô.

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

Você deve poder carregar mais colunas e aplicar critérios extras para ingressar, modificando como a LIMIT 1seleção abaixo funciona:

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;

Execute a coisa:

SELECT reduce_joined_testpoly();

Uniões apropriadas, sem 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
EoghanM
fonte
2

Aqui está outra estratégia (que não está funcionando) para referência (que eu não conseguia excluir o caso de um único ponto de contato). Deve ser mais rápido do que minha outra resposta, pois leva apenas um passe.

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;

(fique à vontade para alterar e postar outra resposta se alguém puder obter a geometria id = 5 em seu próprio grupo)

Para voltar à lista de IDs, etc., você precisaria st_containsvoltar à tabela testpoly, conforme detalhado na resposta a seguir: /programming//a/37486732/6691, mas não consegui fazer isso funcionar por polígonos por algum motivo.

EoghanM
fonte
2

Aqui está uma rápida facada nele usando sua consulta original ajustada um pouco:

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;

Referências: https://postgis.net/docs/using_postgis_dbmanagement.html#DE-9IM

cm1
fonte