Учитывая дыры / ограничения в создании многоугольника Вороного в QGIS?

12

Я пытаюсь создать в QGIS полигоны вороной, которые бы рассматривали «дыры» в общей области. Примером может быть:

введите описание изображения здесь

На самом деле я создал Вороного на этом изображении, используя QGIS через команду GRASS, а затем с помощью инструмента «Разница» для создания отверстий. В качестве слоя «Разница» использовался отдельный шейп-файл многоугольника, который содержит размеры отверстий. Примером приложения будет создание полигонов вокруг точек отбора проб, которые были собраны между структурами, которые должны быть исключены из анализа.

Здесь возникают две проблемы:

  1. Функция «разности» не работает должным образом на 100%, при этом некоторые границы многоугольника простираются в «дыры». Это можно исправить, найдя строку в таблице атрибутов, у которой нет номера ID полигона (или ID «0»).

  2. Этот тип "пробивания отверстий" после факта может привести к прерывистым многоугольникам, как показано красной стрелкой на изображении.

У меня вопрос: есть ли инструмент или плагин Вороного, который может рассматривать наличие «дырок» в центре области как одношаговый процесс, а также устранять образование разрывных многоугольников? Я предполагаю, что такой инструмент будет расширять границу многоугольника до ближайшего пересечения с другой границей, если только эта другая граница не ударяет сначала о границу «дыры».

LeaningCactus
источник
Это было бы аналогично (я думаю) использованию маски окружения в ArcGIS . Это позволит вам ограничить созданные полигоны в пределах определенной границы. Однако я не знаю ни одного инструмента, который использовал бы сложные границы / дыры (хотя, возможно, в ArcGIS маска могла быть такой сложной - я не проверял ее, и я мог бы попробовать позже, если у меня будет время).
Крис W
Я проверил теорию ArcGIS, и она не будет работать. Согласно связанному вопросу, вы можете ограничить результаты внешней формой. Тем не менее, резание отверстий в форме игнорируется полученными полями. Кроме того, если в этом отверстии также есть некоторые точки, инструмент выдает ошибку и не запускается. Я не могу объяснить вашу первую проблему с разницей, но вторая, приводящая к осколкам, не совсем неожиданна - в конце концов, эта область все равно будет выделена той же точке, даже если дыра присутствует. Вы можете использовать этот метод, а затем включить ленты в их соседей с помощью метода очистки.
Крис W
2
Вы можете решить эту проблему, перейдя в растр. С растровой маской евклидово расстояние будет выходить из ваших точек, пока оно не достигнет ни ячеек, выходящих из другой точки, ни вашего растрового маски (описание вашего граничного удара). Затем вы делаете зональную очистку и векторизуете результат, чтобы получить полигоны.
Крис W
1
Я бы удостоверился, что voronoi Geometry верна, запустив v.clean, а затем проверил геометрию. Наконец, выполните Разница, чтобы создать дыры.
Klewis
Что такое Вороной об этих дырах? Разве вы не хотите пробивать отверстия? Почему бы не сделать слой многоугольника?
mdsumner

Ответы:

3

Это может быть возможно с использованием растров. Сначала преобразуйте точки и полигоны в растр высокого разрешения. Установите маску для ваших границ, используя r.mask. Затем бегите r.grow.distanceв GRASS и используйте Value= output. Это даст вам за каждый пиксель, который является ближайшей точкой. Преобразуйте это обратно в векторные многоугольники. Там могут быть дополнительные шаги, необходимые для избавления от осколков полигонов.

Гийом
источник
2

Это, конечно, возможно с растрами.

Надеемся, что этот скриншот показывает проблему более четко. Часть В вороноя «ближе к лету» ближе к первоначальному центру вороноя, но это не принимает во внимание тот факт, что для обхода здания потребуется больше времени. Мое понимание вопроса ОП заключается в том, что вороной должен учитывать это дополнительное расстояние, чтобы пройтись вокруг здания.

введите описание изображения здесь

Мне нравится предложение от @Guillaume. Однако, когда я попробовал это, у меня были проблемы с получением r.grow.distanceмаски (см. Ниже. Рябь не должна проходить через здания).

Мои знания о траве не так сильны, как могли бы, поэтому, возможно, я делаю что-то глупое. Определенно, сначала проверьте это предложение, так как оно будет намного меньше работы, чем мое ;-)

введите описание изображения здесь

Шаг 1 - Создайте поверхность затрат

Первый шаг - создать поверхность затрат. Это нужно сделать только один раз.

  • создать редактируемый слой, отверстия и все.
  • добавьте поле с именем 'unit', установите его в 1.
  • используя полигон-растр на «вырубленном» векторном слое (который имеет отверстия), используя поле «unit». Теперь у вас есть слой «маска», где 1 - свободное пространство, а 0 - здание.
  • используйте растровый калькулятор, чтобы превратить это в поверхность затрат. Я установлю «на улице» на 1 и «в помещении» на 9999. Это затруднит передвижение по зданиям.

    (( "Маска @ 1" = 1) * 1) + (( "маска @ 1" = 0) * 9999)

Вы можете получить более «естественные» результаты, добавив немного шума на поверхность затрат (например, используйте случайное число от 1 до 3, а не просто 1 для наружных пикселей).

Шаг 2. Создайте растры совокупной стоимости для каждого вороного центра

Теперь мы можем запустить (для одной вороной ячейки за раз) алгоритм GRASS r.cost.coordinatesпротив нашего поверхностного слоя затрат.

Для начальной координаты используйте центр vornoi. В качестве конечной координаты выберите один из углов вашей области. Я предлагаю использовать «Knights Tour», поскольку это дает более плавные результаты.

Результат показывает линии равного времени прохождения от одного вороного центра. Обратите внимание, как полосы обвивают здания.

введите описание изображения здесь

Не уверен, как лучше всего это автоматизировать. Может быть, обработка в пакетном режиме или в Pyqgis.

Шаг 3. Слить растры

Это, вероятно, потребуется код. Алгоритм будет

create a raster 'A' to match the size of your cumulative cost images
fill raster 'A' with a suitably high number e.g. 9999
create an array of the same size as the raster.
for each cumulative cost raster number 1..N
    for each cell in image
        if cell < value in raster 'A'
            set value in raster 'A' to cell value
            set corresponding cell in array to cum. cost image number
write out array as a raster

При таком подходе должен получаться растр, в котором каждая ячейка классифицируется по центру вороноя, к которому она ближе всего, с учетом препятствий.

Затем вы можете использовать растр-полигон. Затем вы можете использовать плагин Generalize для удаления артефактов эффекта «step» из растра.

Приносим извинения за неопределенность на шагах 2 и 3 ... Я надеюсь, что кто-то присоединится к более элегантному решению :)

Стивен Кей
источник
1
Спасибо Стивену, у меня есть рабочий обход GRASS, но я надеялся на более элегантное решение, как упомянуто в описании награды.
Подземье
0

Примечание № 1 : Я не смог воспроизвести предложенную проблему, потому что инструмент Разница хорошо работал для меня в нескольких тестах, которые я выполнил (возможно, это было связано с простой геометрией проблемы или потому что инструмент был улучшен, так как вопрос был спросил 1 год назад).

Тем не менее, я предлагаю обходной путь в PyQGIS, чтобы избежать использования инструмента Разница . Все основано на локальном пересечении двух входных слоев (см. Рисунок ниже):

  1. векторный слой многоугольника, представляющий многоугольники Вороного;
  2. векторный слой многоугольника, представляющий дыры / ограничения, которые необходимо исключить из анализа.

введите описание изображения здесь

Примечание № 2 : Поскольку я не хочу использовать инструмент « Разница» , я не могу избежать создания «осколков» (см. Затем), поэтому мне нужно было запустить v.cleanинструмент для их устранения. Кроме того, как сказал @Chris W,

[...] но второе, приводящее к осколкам, не является совершенно неожиданным - в конце концов, эта область все равно будет выделена той же точке, даже если дыра присутствует. Вы можете использовать этот метод, а затем включить ленты в их соседей с помощью метода очистки .

После этих необходимых условий я выкладываю свой код:

##Voronoi_Polygons=vector polygon
##Constraints=vector polygon
##Voronoi_Cleaned=output vector

from qgis.core import *

voronoi = processing.getObject(Voronoi_Polygons)
crs = voronoi.crs().toWkt()
ex = voronoi.extent()
extent = '%f,%f,%f,%f' % (ex.xMinimum(), ex.xMaximum(), ex.yMinimum(), ex.yMaximum())

constraints = processing.getObject(Constraints)

# Create the output layer
voronoi_mod = QgsVectorLayer('Polygon?crs='+ crs, 'voronoi' , 'memory')
prov = voronoi_mod.dataProvider()
fields = voronoi.pendingFields() # Fields from the input layer
prov.addAttributes(fields) # Add input layer fields to the outLayer
voronoi_mod.updateFields()

# Spatial index containing all the 'constraints'
index_builds = QgsSpatialIndex()
for feat in constraints.getFeatures():
    index_builds.insertFeature(feat)

final_geoms = {}
final_attrs = {}

for feat in voronoi.getFeatures():
    input_geom = feat.geometry()
    input_attrs = feat.attributes()
    final_geom = []
    multi_geom = input_geom.asPolygon()
    input_geoms = [] # edges of the input geometry
    for k in multi_geom:
        input_geoms.extend(k)
    final_geom.append(input_geoms)
    idsList = index_builds.intersects(input_geom.boundingBox())
    mid_geom = [] # edges of the holes/constraints
    if len(idsList) > 0:
        req = QgsFeatureRequest().setFilterFids(idsList)
        for ft in constraints.getFeatures(req):
            geom = ft.geometry()
            hole = []
            res = geom.intersection(input_geom)
            res_geom = res.asPolygon()
            for i in res_geom:
                hole.extend(i)
                mid_geom.append(hole)
        final_geom.extend(mid_geom)
    final_geoms[feat.id()] = final_geom
    final_attrs[feat.id()] = input_attrs

# Add the features to the output layer
outGeom = QgsFeature()
for key, value in final_geoms.iteritems():
    outGeom.setGeometry(QgsGeometry.fromPolygon(value))
    outGeom.setAttributes(final_attrs[key])
    prov.addFeatures([outGeom])

# Add 'voronoi_mod' to the Layers panel
QgsMapLayerRegistry.instance().addMapLayer(voronoi_mod)

# Run 'v.clean'
processing.runalg("grass7:v.clean",voronoi_mod, 2, 0.1, extent, -1, 0.0001, Voronoi_Cleaned, None)

# Remove 'voronoi_mod' to the Layers panel
QgsMapLayerRegistry.instance().removeMapLayer(voronoi_mod)

что приводит к такому результату:

введите описание изображения здесь

Просто для ясности, это будет результат без использования v.cleanинструмента:

введите описание изображения здесь

Разница с результатом @LeaningCactus заключается в том, что к настоящему времени геометрии не нарушены и их можно «очистить» без ошибок .

МГРИ
источник
Сделайте отверстия больше, например, прорежьте всю карту, как реку, и вы увидите проблему. Добавление осколков к соседям создает многоугольники, которые выглядят совсем иначе, чем правильная ограниченная диаграмма Вороного. Я попробовал это.
Подземье
Извините, я не понимаю: вы нашли ошибку в результатах? Я тестировал код только для тех случаев, когда многоугольники были похожи на те, которые были предложены в вопросе.
МГРИТЕ
К сожалению, сейчас вы не можете протестировать код, но не могли бы вы показать результаты, полученные с изменением отверстий, которое было показано на i.stack.imgur.com/Jpfra.png ?
Подземье
Если я расширю ограничение до функции справа, я получу это . Вместо этого, если я переместлю ограничение напрямую, я получу это .
МГРИТЕ
Проблема заключается в маленьком треугольнике, на который указывает красная стрелка в моем рисунке. Это не должно быть там, но это также в ваших результатах. Похоже, этот подход решает проблему № 1 вопроса, но оставляет решение № 2 нерешенным.
Подземье