Herramientas de modelo Gravity / Huff

26

Estoy buscando una manera de simular un modelo de gravedad utilizando una capa basada en puntos.

A todos mis puntos se les asigna un valor z y cuanto mayor sea este valor, mayor será su "esfera de influencia". Esta influencia es inversamente proporcional a la distancia al centro.

Es un modelo típico de Huff, cada punto es un máximo local y los valles entre ellos indican los límites de la zona de influencia entre ellos.

Probé varios algoritmos de Arcgis (IDW, asignación de costos, interpolación polinómica) y QGIS (complemento de mapa de calor), pero no encontré nada que pudiera ayudarme. También encontré este hilo , pero no es muy útil para mí.

Como alternativa, también podría estar satisfecho con una forma de generar diagramas de Voronoi si hay una manera de influir en el tamaño de cada celda por el valor z del punto correspondiente.

Damien
fuente

Respuestas:

13

Aquí hay una pequeña función de Python QGIS que implementa esto. Requiere el complemento rasterlang (el repositorio debe agregarse a QGIS manualmente).

Espera tres parámetros obligatorios: la capa de puntos, una capa ráster (para determinar el tamaño y la resolución de la salida) y un nombre de archivo para la capa de salida. También puede proporcionar un argumento opcional para determinar el exponente de la función de disminución de distancia.

Los pesos para los puntos deben estar en la primera columna de atributos de la capa de puntos.

El ráster resultante se agrega automáticamente al lienzo.

Aquí hay un ejemplo de cómo ejecutar el script. Los puntos tienen pesos entre 20 y 90, y la cuadrícula tiene un tamaño de 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
fuente
3
(+1) El enfoque se ve bien. Pero, ¿por qué tomas la raíz cuadrada y luego la vuelves a cuadrar en informática curGravity? Eso es una pérdida de tiempo computacional. Otro conjunto de cálculos desperdiciados implica la normalización de todas las cuadrículas de "gravedad" antes de encontrar el máximo: en su lugar, encuentre su máximo y normalice eso por la suma.
whuber
¿Eso no cuadra toda la fracción?
lynxlynxlynx
1
Jake, todavía no necesitas la raíz cuadrada: solo olvídalo por completo y usa la mitad del exponente deseado. En otras palabras, si z es la suma de los cuadrados de las diferencias de coordenadas, en lugar de calcular (sqrt (z)) ^ p, que son dos operaciones moderadamente caras, solo calcule z ^ (p / 2), que (porque p / 2 es un número precalculado ) es solo una operación de trama, y ​​también conduce a un código más claro. Esta idea se hace evidente cuando aplica modelos gravitacionales como se pretendían originalmente: los tiempos de viaje. Ya no hay ninguna fórmula de raíz cuadrada, por lo que aumenta el tiempo de viaje a la potencia -p / 2.
whuber
Muchas gracias, esto se parece a lo que necesito. Solo un problema, no estoy tan acostumbrado a Python y nunca usé la extensión Rasterlang. Lo instalé en mi versión QGIS, pero estoy atascado con un "error de sintaxis". ¿Su función ya está implementada en la extensión rasterlang? Si no, ¿cómo hago eso? ¡Gracias por tu ayuda! http://i.imgur.com/NhiAe9p.png
Damien
1
@Jake: Ok, creo que empiezo a entender cómo funciona la consola. Hice lo que dijiste y el código parece entenderse correctamente. Ahora tengo otro error relacionado con un paquete de python "shape_base.py". ¿Mi instalación QGIS carece de algunas características? http://i.imgur.com/TT0i2Cl.png
Damien