Unisci tutti i poligoni adiacenti


22

Vorrei fare test di adiacenza su uno strato di pacchi (poligoni) e unirli se si adattano a determinati criteri (potrebbero essere le dimensioni). Per l'immagine qui sotto, vorrei unire i poligoni 1,2,3 e 4, ma non 5.

Ho due problemi:

  1. ST_TOUCHESrestituisce VERO se solo gli angoli toccano e non un segmento di linea. Penso di aver bisogno di ST_RELATE per verificare la presenza di segmenti di linea condivisi.
  2. Idealmente, vorrei unire TUTTI i poligoni adiacenti in uno, ma non sono sicuro di come scalare oltre due - come in, unire 1,2,3 e 4 (e possibilmente più su dati reali) in un round.

La struttura che ho ora si basa su un self join ST_TOUCHES.

inserisci qui la descrizione dell'immagine

Dati del giocattolo

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 ;

Selezione

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

Ecco l'output:

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

Si noti che il poligono id = 3 condivide un punto con id = 1 e quindi viene restituito come risultato positivo. Se cambio la clausola WHERE in ST_Touches( t1.geom, t2.geom ) AND t1.geom && t2.geom AND ST_Relate(t1.geom, t2.geom ,'T*T***T**');non ottengo alcun record.

  1. Quindi , in primo luogo , come specificare ST_Relate per assicurarsi che vengano considerati solo i pacchi che condividono un segmento di linea.

  2. E poi, come unirei i poligoni 1,2,3,4 in un round, comprimendo i risultati della chiamata precedente, riconoscendo nel contempo che l'adiacenza da 1 a 2 è la stessa del contrario?

Aggiornare

Se aggiungo questo alla whereclausola ovviamente ottengo solo poligoni e non multipoligoni, eliminando così i falsi positivi per i miei scopi: i tocchi d'angolo verranno ignorati.

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

Sebbene ciò non sia ideale (preferirei utilizzare i controlli di topologia ST_RELATEcome soluzione più generale), è una via da seguire. Resta quindi la questione del de-duping e dell'unione di questi. Forse, se potessi generare una sequenza solo per i poligoni toccanti, potrei unirmi a questo.

Aggiornamento II

Questo sembra funzionare per selezionare i poligoni che condividono le linee (ma non gli angoli) ed è quindi una soluzione più generale rispetto al MULTIPOLYGONtest sopra . La mia clausola Where ora appare così:

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

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

Ora ciò che rimane è ancora come fare l'unione per più di una coppia di poligoni, ma per un numero arbitrario che si adatta ai criteri, in una volta sola.


2
Sono sicuro che ST_Relate sia il modo corretto. Ho risolto un problema simile verificando che la lunghezza delle intersezioni fosse maggiore di zero per escludere intersezioni a punto singolo. Un trucco, ma funziona.
John Powell,

Se esistesse un modo per raggruppare poligoni contigui in array, è possibile modificare la ST_IntersectionArray[funzione] [1] per lavorare con ST_Union [1]: gis.stackexchange.com/a/60295/36886
raphael,

2
Per quanto riguarda il raggruppamento di poligoni contigui, è possibile modificare l'algoritmo di clustering bottom-up che ho scritto qui ( gis.stackexchange.com/a/115715/36886 ) per verificare l'adiacenza anziché lo spazio e quindi utilizzare ST_Union mentre si raggruppano i cluster_ids risultanti
Raffaello,

3
C'è anche ST_ClusterIntersectimg che potrebbe fare ciò di cui hai bisogno. Hai bisogno di Postgis 2.2
John Powell,

Risposte:


3

Non ho potuto fare a meno di pensare che il tuo esempio sia in realtà un raster e anche se hai detto che vorresti unirti in base a "determinati criteri (potrebbe essere la dimensione)", vorrei fare un tentativo con una conversione raster.

Per il tuo esempio specifico questo funzionerebbe:

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)

Quello che succede è che poiché i tuoi poligoni sono cellule perfettamente allineate, si convertiranno bene in un raster (formato 10x20). I dumpaspolygons ti aiutano qui unendo tutte le celle adiacenti in una e confrontando con i poligoni originali sarai anche in grado di recuperare l'id per i poligoni non uniti.

Dopo aver spiegato questo, sono molto curioso di sapere come si ridimensionerebbe e quanto sia grande il tuo set di dati: D


Idea intelligente. Questo è un esempio di giocattolo, tuttavia: i miei dati effettivi sono uno strato di pacchi che non si associa perfettamente ai raster.
ako,

3

Ecco un esempio di come eseguire questa operazione in stile procedurale con più passaggi sotto il cofano.

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

Dovresti essere in grado di portare con te più colonne e applicare criteri extra per l'unione modificando il funzionamento della LIMIT 1selezione seguente:

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;

Esegui la cosa:

SELECT reduce_joined_testpoly();

Unioni proprie, nessun multipoligono:

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

Ecco un'altra strategia (non funzionante) di riferimento (che non riuscivo a escludere il caso del singolo punto di contatto). Dovrebbe essere più veloce della mia altra risposta in quanto richiede solo un "passaggio".

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;

(sentiti libero di modificare e pubblicare un'altra risposta se qualcuno può ottenere la geometria id = 5 nel proprio gruppo)

Per recuperare l'elenco di ID ecc. st_containsDovresti utilizzare per riconnetterti alla tabella testpoly come dettagliato nella seguente risposta: /programming//a/37486732/6691 ma non sono riuscito a farlo funzionare per i poligoni per qualche motivo.


2

Ecco un rapido colpo usando la tua query originale regolata un po ':

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;

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.