Ferramentas de modelo Gravidade / Huff

26

Estou procurando uma maneira de simular um modelo de gravidade usando uma camada baseada em pontos.

Todos os meus pontos recebem um valor z e, quanto maior esse valor, maior é sua "esfera de influência". Essa influência é inversamente proporcional à distância do centro.

É um modelo típico de Huff, cada ponto é um máximo local e os vales entre eles indicam os limites da zona de influência entre eles.

Tentei vários algoritmos do Arcgis (IDW, alocação de custos, interpolação polinomial) e QGIS (plugin do heatmap), mas não encontrei nada que pudesse me ajudar. Eu também encontrei este tópico , mas não é muito útil para mim.

Como alternativa, eu também poderia estar satisfeito com uma maneira de gerar diagramas de Voronoi se houver uma maneira de influenciar o tamanho de cada célula pelo valor-z do ponto correspondente.

Damien
fonte

Respostas:

13

Aqui está uma pequena função python do QGIS que implementa isso. Requer o plug-in rasterlang (o repositório deve ser adicionado ao QGIS manualmente).

Ele espera três parâmetros obrigatórios: a camada de pontos, uma camada de varredura (para determinar o tamanho e a resolução da saída) e um nome de arquivo para a camada de saída. Você também pode fornecer um argumento opcional para determinar o expoente da função de decaimento à distância.

Os pesos dos pontos precisam estar na primeira coluna de atributo da camada de pontos.

A varredura resultante é adicionada automaticamente à tela.

Aqui está um exemplo de como executar o script. Os pontos têm pesos entre 20 e 90, e a grade tem 60 por 50 unidades de mapa.

points = qgis.utils.iface.mapCanvas().layer(0)
raster = qgis.utils.iface.mapCanvas().layer(1)
huff(points,raster,"output.tiff",2)

from rasterlang.layers import layerAsArray
from rasterlang.layers import writeGeoTiff
import numpy as np

def huff(points, raster, outputfile, decay=1):
    if points.type() != QgsMapLayer.VectorLayer:
        print "Error: First argument is not a vector layer (but it has to be)"
        return
    if raster.type() != QgsMapLayer.RasterLayer:
        print "Error: Second argument is not a raster layer (but it has to be)"
        return
    b = layerAsArray(raster)
    e = raster.extent()
    provider = points.dataProvider()
    extent = [e.xMinimum(),e.yMinimum(),e.xMaximum(),e.yMaximum()]
    xcols = np.size(layerAsArray(raster),1)
    ycols = np.size(layerAsArray(raster),0)
    xvec = np.linspace(extent[0], extent[2], xcols, endpoint=False)
    xvec = xvec + (xvec[1]-xvec[0])/2
    yvec = np.linspace(extent[3], extent[1], ycols, endpoint=False)
    yvec = yvec + (yvec[1]-yvec[0])/2
    coordArray = np.meshgrid(xvec,yvec)
    gravity = b
    point = QgsFeature()
    provider.select( provider.attributeIndexes() )
    while provider.nextFeature(point):
      coord = point.geometry().asPoint()
      weight = point.attributeMap()[0].toFloat()[0]
      curGravity = weight * ( (coordArray[0]-coord[0])**2 + (coordArray[1]-coord[1])**2)**(-decay/2)
      gravity = np.dstack((gravity, curGravity))
    gravitySum = np.sum(gravity,2)
    huff = np.max(gravity,2)/gravitySum
    np.shape(huff) 
    writeGeoTiff(huff,extent,outputfile)
    rlayer = QgsRasterLayer(outputfile)
    QgsMapLayerRegistry.instance().addMapLayer(rlayer)
Jake
fonte
3
(+1) A abordagem parece boa. Mas por que você pega a raiz quadrada e depois a quadrada novamente na computação curGravity? Isso é uma perda de tempo computacional. Outro conjunto desperdiçado de cálculos envolve a normalização de todas as grades de "gravidade" antes de encontrar o máximo: em vez disso, encontre o máximo e normalize pela soma.
whuber
Isso não equaciona a fração inteira?
Lynxlynxlynx
11
Jake, você ainda não precisa da raiz quadrada: esqueça-a completamente e use metade do expoente pretendido. Em outras palavras, se z é a soma dos quadrados das diferenças de coordenadas, em vez de computar (sqrt (z)) ^ p, que são duas operações moderadamente caras, apenas calcule z ^ (p / 2), que (porque p / 2 é um número pré-computado ) é apenas uma operação de varredura - e também leva a um código mais claro. Essa idéia surge quando você aplica modelos gravitacionais como eles foram originalmente destinados: a tempos de viagem. Não existe mais nenhuma fórmula de raiz quadrada, portanto você aumenta o tempo de viagem para a potência -p / 2.
whuber
Muito obrigado, isso parece o que eu preciso. Apenas um problema, eu não estou tão acostumado a python e nunca usei a extensão Rasterlang. Eu o instalei na minha versão do QGIS, mas estou com um "erro de sintaxe". Sua função já está implementada na extensão rasterlang? Se não, como faço isso? Obrigado pela ajuda! http://i.imgur.com/NhiAe9p.png
Damien
11
@ Jake: Ok, acho que começo a entender como o console funciona. Fiz o que você disse e o código parece ser entendido corretamente. Agora, tenho outro erro relacionado a um pacote python "shape_base.py". Minha instalação do QGIS não possui alguns recursos? http://i.imgur.com/TT0i2Cl.png
Damien