Как рекурсивно перебрать пересечения родительских полигонов, чтобы получить наименьшие (дочерние) полигоны без перекрытий?

11

Я боролся с проблемой в течение нескольких дней и понял, что многие люди также застревают, когда речь идет о пересечениях в PostGIS (v2.5). Вот почему я решил задать более подробный и общий вопрос.

У меня есть следующая таблица:

DROP TABLE IF EXISTS tbl_foo;
CREATE TABLE tbl_foo (
    id bigint NOT NULL,
    geom public.geometry(MultiPolygon, 4326),
    att_category character varying(15),
    att_value integer
);
INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (1, ST_SetSRID('MULTIPOLYGON (((0 6, 0 12, 8 9, 0 6)))'::geometry,4326) , 'cat1', 2 );
INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (2, ST_SetSRID('MULTIPOLYGON (((5 0, 5 12, 9 12, 9 0, 5 0)))'::geometry,4326), 'cat1', 1 );
INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (3, ST_SetSRID('MULTIPOLYGON (((4 4, 3 8, 4 12, 7 14,10 12, 11 8, 10 4, 4 4)))'::geometry,4326) , 'cat2', 5 );

Это выглядит так:

Начните

Я хочу получить все дочерние полигоны на основе пересечения родительских полигонов. Для результата можно было бы ожидать:

  • Дочерние полигоны без перекрытия между ними.
  • Столбец, содержащий сумму значений их родительских полигонов,
  • Столбец, содержащий количество родительских полигонов одной категории
  • Столбец, содержащий количество другой категории
  • Столбец, содержащий категорию дочернего полигона, на основе следующего правила: -Если ВСЕ родительские полигоны принадлежат одному классу, дочерний полигон также имеет этот класс. Иначе, категория дочернего многоугольника является третьей категорией.

Так это будет выглядеть так:

вывод

Так, в конце концов, выходная таблица генерируется (для данного примера) будет иметь 7 строк (все 7, неперекрывающиеся, детские многоугольников), содержащие столбцы category, sum_value, ct_overlap_cat1,ct_overlap_cat2

Следующий код, который я начал, дает мне отдельные пересечения, сравнивая одного родителя с другим.

SELECT
(ST_Dump(
    ST_SymDifference(a.geom, b.geom) 
)).geom
FROM tbl_foo a, tbl_foo b
WHERE a.ID < b.ID AND ST_INTERSECTS(a.geom, b.geom)
UNION ALL
SELECT
ST_Intersection(a.geom, b.geom) as geom
FROM tbl_foo a, tbl_foo b
WHERE a.ID < b.ID AND ST_INTERSECTS(a.geom, b.geom);

Как мне рекурсивно перебрать результат этого упомянутого кода, чтобы, независимо от числа перекрывающихся многоугольников, я всегда получал его «наименьшие» (дочерние) многоугольники (рис. 2)?

Matt_Geo
источник

Ответы:

8

Попробуй это:

Загрузите дополнения PostGIS по этой ссылке: https://github.com/pedrogit/postgisaddons

Установите, запустив файл postgis_addons.sql, чтобы получить функцию ST_SplitAgg ().

Протестируйте, запустив файл postgis_addons_test.sql.

Вот ваш запрос:

WITH  result_table AS (
    WITH  parts AS (
      SELECT a.att_value val,
             CASE WHEN a.att_category = 'cat1' THEN 1 ELSE 0 END cat1,
             CASE WHEN a.att_category = 'cat2' THEN 1 ELSE 0 END cat2,
             unnest(ST_SplitAgg(a.geom, b.geom, 0.00001)) geom
      FROM tbl_foo a,
           tbl_foo b
      WHERE ST_Equals(a.geom, b.geom) OR
            ST_Contains(a.geom, b.geom) OR
            ST_Contains(b.geom, a.geom) OR
            ST_Overlaps(a.geom, b.geom)
      GROUP BY a.id, a.att_category , ST_AsEWKB(a.geom), val
    )
    SELECT CASE WHEN sum(cat2) = 0 THEN 'cat1'
                WHEN sum(cat1) = 0 THEN 'cat2'
                ELSE 'cat3'
           END category, 
           sum(val*1.0) sum_value, 
           sum(cat1) ct_overlap_cat1, 
           sum(cat2) ct_overlap_cat2, 
           ST_Union(geom) geom
    FROM parts
    GROUP BY ST_Area(geom)
)
SELECT category, sum_value, ct_overlap_cat1, ct_overlap_cat2,
(ST_Dump(result_table.geom)).geom as geom
FROM result_table
Пьер Расин
источник
Я смотрел на ваши аддоны GIT-репо раньше. Вдохновляющие вещи.
Джон Пауэлл
Вау, отличное решение. Вы также проделали потрясающую работу по созданию этих аддонов. Прежде чем щелкнуть, чтобы присудить этот ответ, я только один, чтобы убедиться в одной вещи, которая меня беспокоит. При выполнении предоставленного вами кода «полигон 5» (второй фигуры вопроса), похоже, не распознает перекрытие с другим полигоном («ct_overlap_cat2 = 1»; «ct_overlap_cat2 = 0»). Следовательно, этот многоугольник в конечном итоге классифицируется как «cat1» и «sum = 2» вместо «cat3» и «sum = 7». Я сталкиваюсь с некоторыми трудностями при отладке этой маленькой проблемы. Не могли бы вы помочь мне?
Matt_Geo
1
Единственная проблема с этим решением состоит в том, что операторы case жестко закодированы. В принципе, он, вероятно, должен иметь возможность обрабатывать произвольное количество категорий.
Джон Пауэлл
@ Matt_Geo Я получаю 6 полигонов в итоговой таблице. Треугольник многоугольника делится на три. Один с суммой = 2, один с суммой = 7 и один с суммой = 8, как показано на рисунке.
Пьер Расин
1
Что если вы замените ST_Centroid (geom) на ST_Area (geom)?
Пьер Расин
1

Я полагаю, если вы используете тип геометрии полигона вместо MultiPolygon, все станет на свои места:

DROP TABLE IF EXISTS tbl_foo;
CREATE TABLE tbl_foo (
    id bigint NOT NULL,
    geom public.geometry(Polygon, 4326),
    att_category character varying(15),
    att_value integer
);

INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (1, ST_SetSRID('POLYGON ((0 6, 0 12, 8 9, 0 6))'::geometry,4326) , 'cat1', 2 );
INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (2, ST_SetSRID('POLYGON ((5 0, 5 12, 9 12, 9 0, 5 0))'::geometry,4326), 'cat1', 1 );
INSERT INTO tbl_foo (id, geom, att_category, att_value) VALUES 
    (3, ST_SetSRID('POLYGON ((4 4, 3 8, 4 12, 7 14,10 12, 11 8, 10 4, 4 4))'::geometry,4326) , 'cat2', 5 );

В результате получается 9 записей, которые соответствуют различным вариантам пересечения в приведенном вами примере.

Кирилл Михальченко
источник