Considerando buracos / restrições na criação do polígono de Voronoi no QGIS?

12

Estou tentando criar polígonos voronoi no QGIS que considerariam "buracos" no domínio geral. Um exemplo seria:

insira a descrição da imagem aqui

Na verdade, eu criei os Voronois nesta imagem usando o QGIS através do comando GRASS e, em seguida, usando a ferramenta "Diferença" para criar os furos. Um shapefile de polígono separado, que contém as extensões dos furos, foi usado como a camada "Diferença". Um aplicativo de exemplo seria criar polígonos em torno de pontos de amostragem coletados entre estruturas que deveriam ser excluídas da análise.

Dois problemas surgem aqui:

  1. A função "diferença" não parece funcionar 100% corretamente, com alguns limites de polígono se estendendo para os "orifícios". Isso pode ser corrigido encontrando a linha na Tabela de Atributos que não possui um número de ID de polígono (ou ID de "0").

  2. Esse tipo de "perfuração" posterior pode resultar em polígonos descontínuos, como mostra a seta vermelha na imagem.

Minha pergunta é: existe uma ferramenta ou plug-in Voronoi que considere a presença de "buracos" no centro do domínio como um processo de uma etapa e também elimine a geração de polígonos descontínuos? Eu imagino que essa ferramenta estenda um limite de polígono até a interseção mais próxima com outro limite, a menos que esse outro limite atinja primeiro um limite de "buraco".

LeaningCactus
fonte
Isso seria semelhante, mas o oposto de (acho) usar uma máscara de ambiente no ArcGIS . Isso permitiria restringir os polígonos criados a um limite específico. No entanto, não conheço nenhuma ferramenta que faça uso de limites / buracos complexos (embora talvez no ArcGIS a máscara possa ser tão complexa - eu não a testei e tentei mais tarde, se tiver tempo).
Chris W
Eu testei a teoria do ArcGIS, e não vai funcionar. De acordo com a pergunta vinculada, você pode restringir os resultados a uma forma externa. No entanto, um corte na forma é ignorado pelos polis resultantes. Além disso, se esse furo também tiver alguns pontos, a ferramenta falha e falha ao executar. Não posso explicar sua primeira questão com diferença, mas a segunda resultando em lascas não é totalmente inesperada - afinal, essa área ainda seria alocada no mesmo ponto, mesmo que o buraco estivesse presente. Você pode usar esse método e incorporar as lascas nos vizinhos com um método de limpeza.
Chris W
2
Você pode resolver isso potencialmente rasterizando. Com uma máscara raster, a distância euclidiana desapareceria dos seus pontos até atingir as células que saem de outro ponto ou a sua máscara raster (sua descrição do slam do limite). Então você faz uma limpeza de zona e vetoriza o resultado para obter polígonos.
Chris W
1
Eu garantiria que a geometria voronoi seja válida executando v.clean e verifique a geometria. Por fim, execute Diferença para criar os furos.
klewis
O que é Voronoi sobre esses buracos? Você não está querendo fazer buracos de forma limpa? Por que nenhuma camada de polígono não funcionaria?
Mdsumner

Respostas:

3

Isso pode ser possível usando rasters. Primeiro converta seus pontos e polígonos de limite em uma varredura de alta resolução. Defina uma máscara para seus limites usando r.mask. Em seguida, execute o r.grow.distanceGRASS e use o Value= output. Isso fornecerá a você cada pixel, que é o ponto mais próximo. Converta isso novamente em polígonos de vetor. Pode haver etapas extras necessárias para se livrar dos polígonos da tira.

Guillaume
fonte
2

Isso certamente é possível com rasters.

Esperamos que esta captura de tela mostre o problema mais claramente. A porção B dos voronoi está mais próxima 'do corvo voa' do centro original dos voronoi, mas isso não leva em conta o fato de que levaria mais tempo para caminhar pelo prédio. Meu entendimento da pergunta do OP é que os voronoi precisam levar em conta essa distância extra para caminhar pelo prédio.

insira a descrição da imagem aqui

Gosto da sugestão do @Guillaume. No entanto, quando tentei, tive problemas r.grow.distancepara honrar a máscara (veja abaixo. As ondulações não devem passar pelos edifícios).

Meu conhecimento de Grass não é tão forte quanto poderia ser, então talvez eu esteja fazendo algo estúpido. Definitivamente, verifique essa sugestão primeiro, pois será muito menos trabalhoso que o meu ;-)

insira a descrição da imagem aqui

Etapa 1 - Crie uma superfície de custo

O primeiro passo é criar uma superfície de custo. Isso só precisa ser feito uma vez.

  • crie uma camada editável, buracos e tudo.
  • adicione um campo chamado 'unidade', defina-o como 1.
  • usando polígono para varredura em sua camada vetorial "perfurada" (a que possui os furos), usando o campo 'unit'. Agora você tem uma "máscara" de camada, em que 1 é espaço livre e 0 está construindo.
  • use a calculadora raster para transformar isso em uma superfície de custo. Definirei 'outdoor' como 1 e 'indoor' como 9999. Isso tornará a proibição de mover-se através de edifícios proibitivamente.

    (("máscara @ 1" = 1) * 1) + (("máscara @ 1" = 0) * 9999)

Você pode obter mais resultados 'orgânicos' adicionando um pouco de ruído à superfície de custo (por exemplo, use número aleatório de 1 a 3, em vez de apenas 1 para pxiels externos).

Etapa 2. Crie rasters de custo cumulativos para cada centro voronoi

Agora podemos executar (para uma célula voronoi de cada vez) o algoritmo GRASS r.cost.coordinatesna nossa camada de superfície de custo.

Para a coordenada inicial, use o centro vornoi. Para a coordenada final, escolha um dos cantos da sua área. Sugiro usar o 'Knights Tour', pois isso fornece resultados mais suaves.

O resultado mostra linhas de tempo de viagem igual a partir de um centro voronoi. Observe como as bandas envolvem os prédios.

insira a descrição da imagem aqui

Não tenho certeza da melhor maneira de automatizar isso. Talvez processando o modo em lote ou feito em pyqgis.

Etapa 3. Mesclar as rasters

Provavelmente precisará de código. O algoritmo seria

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

Essa abordagem deve gerar uma varredura em que cada célula é categorizada pelo centro voronoi mais próximo, levando em consideração os obstáculos.

Você poderia usar a varredura em polígono. Você pode usar o plug-in Generalizar para remover os artefatos de efeito "etapa" da varredura.

Pedimos desculpas pela imprecisão nos passos 2 e 3 ... Espero que alguém concorde com uma solução mais elegante :)

Steven Kay
fonte
1
Obrigado Steven, eu tenho uma solução de varredura GRASS funcional, mas esperava uma solução mais elegante, como mencionado na descrição da recompensa.
Subterrâneo
0

Nota nº 1 : Não foi possível reproduzir o problema proposto porque a ferramenta Diferença funcionou bem para mim em vários testes que realizei (talvez devido à geometria simples do problema ou porque a ferramenta foi aprimorada desde que a pergunta foi perguntou há 1 ano).

No entanto, proponho uma solução alternativa no PyQGIS para evitar o uso da ferramenta Diferença . Tudo é baseado na interseção local entre duas camadas de entrada (veja a figura abaixo):

  1. uma camada vetorial de polígono representando os polígonos de Voronoi;
  2. uma camada vetorial de polígono representando os furos / restrições que precisam ser excluídos da análise.

insira a descrição da imagem aqui

Nota 2 : Como eu não quero usar a ferramenta Diferença , não consigo evitar a criação de "lascas" (veja então), então eu precisava executar a v.cleanferramenta para eliminá-las. Além disso, como @Chris W disse,

[...] mas o segundo que resulta em lascas não é totalmente inesperado - afinal, essa área ainda seria alocada no mesmo ponto, mesmo que o buraco estivesse presente. Você pode usar esse método e incorporar as lascas nos vizinhos com um método de limpeza .

Após essas premissas necessárias, eu publico meu código:

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

o que leva a esse resultado:

insira a descrição da imagem aqui

Apenas para maior clareza, este seria o resultado sem o uso da v.cleanferramenta:

insira a descrição da imagem aqui

A diferença com o resultado do @LeaningCactus é que, até agora, as geometrias não estão quebradas e podem ser "limpas" sem erros .

mgri
fonte
Faça orifícios mais longos, por exemplo, cortando o mapa inteiro, como um rio, e você verá o problema. Adicionar lascas aos vizinhos cria polígonos que parecem muito diferentes de um diagrama de Voronoi com restrição adequada. Eu tentei isso.
Subterrâneo
Desculpe, eu não entendo: você encontrou algum erro nos resultados? Eu apenas testei o código para os casos em que os polígonos eram semelhantes aos propostos na pergunta.
MGRI
Infelizmente, não é possível testar o código agora, mas você poderia mostrar os resultados obtidos com a alteração dos furos esboçados em i.stack.imgur.com/Jpfra.png ?
Subterrâneo
Se eu estender a restrição até o recurso à direita, obtenho isso . Em vez disso, se eu mover diretamente a restrição, obtenho isso .
MGRI
O pequeno triângulo que a seta vermelha no meu desenho aponta é a questão. Não deveria estar lá, mas também está nos seus resultados. Parece que essa abordagem resolve o problema nº 1 da pergunta, mas deixa a segunda solução não resolvida.
Subterrâneo