PostGIS - jak skutecznie ST_Union wszystkie nakładające się wielokąty w jednej tabeli

13

Moim celem jest zebranie jednego stołu i połączenie wszystkich wielokątów, które się dotykają lub zbliżają do siebie w pojedyncze wielokąty

Jestem programistą C #, który zaczyna się uczyć o PostGIS. Korzystając z poniższego kodu, udało mi się to osiągnąć, ale wydaje się to nieefektywne, a PostGIS ma wiele dla mnie nowych cech.

Od mojej pierwszej próby (wciąż w komentarzach) byłem w stanie zredukować iteracje, używając array_agg z ST_UNION zamiast łączenia tylko polis na raz.

Skończyłem z 133 polami z mojego oryginału 173.

sql = "DROP TABLE IF Exists tmpTable; create table tmpTable ( ID varchar(50), Geom  geometry(Geometry,4326), Touchin varchar(50) ); create index idx_tmp on tmpTable using GIST(Geom); ";
                CommandText = sql;
                ExecuteNonQuery();

                sql = "";
                for (int i = 0; i < infos.Count(); i++)
                {
                    sql += "INSERT INTO tmpTable SELECT '" + infos[i].ID + "', ST_GeomFromText('" + infos[i].wkt + "', 4326), '0';";
                }
                CommandText = sql;
                ExecuteNonQuery();

                CommandText = "update tmpTable set touchin = (select id from tmpTable as t where st_intersects(st_buffer(geom, 0.0001), (select geom from tmpTable as t2 where t2.ID = tmpTable.ID ) ) and t.ID <> tmpTable.ID limit 1)";
                ExecuteNonQuery();

                CommandText = "select count(*) from tmpTable where touchin is not null";
                long touching = (long)ExecuteScalar();
                string thisId = "";
                // string otherId = "";
                while (touching > 0)
                {
                    CommandText = "select touchin, count(*)  from tmpTable where touchin is not null group by touchin order by 2 desc limit 1";
                    //CommandText = "select id, touchin from tmpTable where touchin is not null";
                    using (var prdr = ExecuteReader())
                    {
                        CommandText = "";
                        if (prdr.Read())
                        {
                            thisId = prdr.GetString(0);
                             // otherID = prdr.GetString(1);
                            CommandText = @"update tmpTable set geom = st_union(unioned) 
                                from (select array_agg(geom) as unioned from tmpTable where touchin = '" + thisId + "' or id = '" + thisId + @"') as data
                                where id = '" + thisId + "'";
                             // CommandText = "update tmpTable set geom = st_union(geom, (select geom from tmpTable where ID = '" + otherId + "')) where id = '" + thisId + "'";
                        }
                    }

                    if (!string.IsNullOrEmpty(CommandText))
                    {
                        ExecuteNonQuery();
                        //CommandText = "update tmpTable set geom = null, touchin = null where ID = '" + otherId + "'";
                        CommandText = "update tmpTable set geom = null, touchin = null where touchin = '" + thisId + "'";
                        ExecuteNonQuery();                            
                    }

                    CommandText = "update tmpTable set touchin = (select id from tmpTable as t where st_intersects(st_buffer(geom, 0.0001), (select geom from tmpTable as t2 where t2.ID = tmpTable.ID ) ) and t.ID <> tmpTable.ID limit 1)";
                    ExecuteNonQuery();

                    CommandText = "select count(*) from tmpTable where touchin is not null";
                    touching = (long)ExecuteScalar();
                }

Oto przykład zestawu danych, którego używam:

INSERT INTO tmpTable SELECT '872538', ST_GeomFromText('POLYGON((-101.455035985 26.8835084441,-101.455035985 26.8924915559,-101.444964015 26.8924915559,-101.444964015 26.8835084441,-101.455035985 26.8835084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872550', ST_GeomFromText('POLYGON((-93.9484752173 46.0755084441,-93.9484752173 46.0844915559,-93.9355247827 46.0844915559,-93.9355247827 46.0755084441,-93.9484752173 46.0755084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872552', ST_GeomFromText('POLYGON((-116.060688575 47.8105084441,-116.060688575 47.8194915559,-116.047311425 47.8194915559,-116.047311425 47.8105084441,-116.060688575 47.8105084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872553', ST_GeomFromText('POLYGON((-116.043688832 47.8125084441,-116.043688832 47.8214915559,-116.030311168 47.8214915559,-116.030311168 47.8125084441,-116.043688832 47.8125084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872557', ST_GeomFromText('POLYGON((-80.6380222359 26.5725084441,-80.6380222359 26.5814915559,-80.6279777641 26.5814915559,-80.6279777641 26.5725084441,-80.6380222359 26.5725084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872558', ST_GeomFromText('POLYGON((-80.6520223675 26.5755084441,-80.6520223675 26.5844915559,-80.6419776325 26.5844915559,-80.6419776325 26.5755084441,-80.6520223675 26.5755084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872559', ST_GeomFromText('POLYGON((-80.6400224991 26.5785084441,-80.6400224991 26.5874915559,-80.6299775009 26.5874915559,-80.6299775009 26.5785084441,-80.6400224991 26.5785084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872560', ST_GeomFromText('POLYGON((-80.6530226307 26.5815084441,-80.6530226307 26.5904915559,-80.6429773693 26.5904915559,-80.6429773693 26.5815084441,-80.6530226307 26.5815084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872568', ST_GeomFromText('POLYGON((-90.7892258584 30.7365084441,-90.7892258584 30.7454915559,-90.7787741416 30.7454915559,-90.7787741416 30.7365084441,-90.7892258584 30.7365084441))', 4326), '0';
INSERT INTO tmpTable SELECT '872569', ST_GeomFromText('POLYGON((-90.7832259127 30.7375084441,-90.7832259127 30.7464915559,-90.7727740873 30.7464915559,-90.7727740873 30.7375084441,-90.7832259127 30.7375084441))', 4326), '0';
Carol AndorMarten Liebster
źródło
Czy same dane są potrzebne w twoim pytaniu?
Paul
@Paul - nie byłem pewien, czy byłoby to pomocne, czy nie.
Carol AndorMarten Liebster

Odpowiedzi:

20

Najprostszym sposobem byłoby przejście do ST_Unioncałego stołu:

SELECT ST_Union(geom) FROM tmpTable;

To da ci jedną ogromną MultiPolygon, która prawdopodobnie nie jest tym, czego chcesz. Możesz uzyskać poszczególne rozpuszczone składniki za pomocą ST_Dump. Więc:

SELECT (ST_Dump(geom)).geom FROM (SELECT ST_Union(geom) AS geom FROM tmpTable) sq;

Daje to oddzielny wielokąt dla każdego zestawu dotykających danych wejściowych, ale grupy danych wejściowych, które zostały oddzielone na niewielką odległość, pozostaną jako osobne geometrie. Jeśli masz dostęp do PostGIS 2.2.0rc1 , możesz łączyć geometrie, które są blisko siebie, w jeden GeometryCollectionza pomocą ST_ClusterWithin :

SELECT unnest(ST_ClusterWithin(geom, 0.0001)) AS grp FROM tmpTable;

Jeśli chcesz Polygonsobrębie GeometryCollectionzostać rozwiązane, można uruchomić ST_UnaryUnionna każdym GeometryCollectionw rezultacie, jak:

SELECT ST_UnaryUnion(grp) FROM
(SELECT unnest(ST_ClusterWithin(geom, 0.0001)) AS grp FROM tmpTable) sq;
dbaston
źródło
To zdecydowanie znacznie szybciej, ale 2 rzeczy: (1) Czy mogę zachować pole ID w wynikach? Które nie jest ważne, ale muszę wziąć wynik i uzyskać z niego inne dane. (2) Czy istnieje sposób, aby ponownie dodać ST_Buffer?
Carol AndorMarten Liebster
1
(1) Niełatwo, ale prostym sposobem na odzyskanie atrybutów jest przestrzenne połączenie wielokątów wynikowych z wewnętrznym punktem wielokątów wejściowych. (2) Dodano wyjaśnienie dotyczące obsługi geometrii, które są blisko, ale nie dotykają.
dbaston
Dzięki za pomoc - obecnie nie mam wersji 2.2, więc będę musiał ponownie to sprawdzić, kiedy uaktualnię do tego. Na razie wyłączenie bufora nie stanowi przełomu.
Carol AndorMarten Liebster
Zgadzam się, że to jest najprostsze. Zastanawiam się, czy byłby sposób na wykonanie rekurencyjnego zapytania, które znajdzie dotykające geomy, ale zrezygnowałem z
niego-
1
@chrismarx, sprawdź gis.stackexchange.com/a/94228/18189, aby uzyskać kilka pomysłów na rozwiązanie rekurencyjne.
dbaston,