GDAL y Python: ¿cómo obtener coordenadas para todas las celdas que tienen un valor específico?

12

Tengo un Arc / Info Binary Grid --- específicamente, un ráster de acumulación de flujo ArcGIS --- y me gustaría identificar todas las celdas que tienen un valor específico (o en un rango de valores). En última instancia, me gustaría un archivo de formas de puntos que representen estas celdas.

Puedo usar QGIS para abrir hdr.adf y obtener este resultado, el flujo de trabajo es:

  • QGIS> Menú ráster> Calculadora ráster (marque todos los puntos con el valor objetivo)
  • QGIS> Menú ráster> Poligonalizar
  • QGIS> Menú de vectores> Submenú Geometría> Centroides poligonales
  • Edite los centroides para eliminar los poli centroides no deseados (aquellos = 0)

Este enfoque "hace el trabajo", pero no me atrae porque crea 2 archivos que tengo que eliminar, luego tengo que eliminar los registros no deseados del archivo de forma de los centroides (es decir, aquellos = 0).

Una pregunta existente aborda este tema, pero está diseñada para ArcGIS / ArcPy, y me gustaría permanecer en el espacio FOSS.

¿Alguien tiene una receta / secuencia de comandos GDAL / Python existente que interrogue los valores de celda de un ráster, y cuando se encuentra un valor objetivo --- o un valor en un rango objetivo ---, se agrega un registro a un archivo de forma? Esto no solo evitaría la interacción de la interfaz de usuario, sino que crearía un resultado limpio en una sola pasada.

Intenté trabajar contra una de las presentaciones de Chris Garrard , pero el trabajo raster no está en mi timonera y no quiero saturar la pregunta con mi código débil.

Si alguien quiere jugar con el conjunto de datos exacto, lo pongo aquí como un .zip .


[Editar notas] Dejando esto atrás para la posteridad. Ver intercambios de comentarios con om_henners. Básicamente, los valores x / y (fila / columna) se voltearon. La respuesta original tenía esta línea:

(y_index, x_index) = np.nonzero(a == 1000)

invertido, así:

(x_index, y_index) = np.nonzero(a == 1000)

Cuando encontré por primera vez el problema ilustrado en la captura de pantalla, me pregunté si implementé la geometría incorrectamente y experimenté volteando los valores de coordenadas x / y en esta línea:

point.SetPoint(0, x, y)

..como..

point.SetPoint(0, y, x)

Sin embargo, eso no funcionó. Y no pensé en intentar cambiar los valores en la expresión Numpy de om_henners, creyendo erróneamente que cambiarlos en cualquier línea era equivalente. Creo que el verdadero problema se relaciona con las x_sizey y_sizelos valores, respectivamente 30y -30, que se aplican cuando los índices de fila y de columna se utilizan para calcular las coordenadas del punto para las células.

[Edición original]

@om_henners, estoy probando su solución, en concierto con un par de recetas para hacer archivos de forma de puntos usando ogr ( invisibleroads.com , Chris Garrard ), pero tengo un problema en el que los puntos aparecen como si se reflejaran a través de una línea que pasa hasta 315/135 grados.

Puntos azules claros : creados por mi enfoque QGIS , arriba

Puntos morados : creados por el código py GDAL / OGR , a continuación

ingrese la descripción de la imagen aquí


[Resuelto]

Este código de Python implementa la solución completa propuesta por @om_henners. Lo he probado y funciona. ¡Gracias hombre!


from osgeo import gdal
import numpy as np
import osgeo.ogr
import osgeo.osr

path = "D:/GIS/greeneCty/Greene_DEM/GreeneDEM30m/flowacc_gree/hdr.adf"
print "\nOpening: " + path + "\n"

r = gdal.Open(path)
band = r.GetRasterBand(1)

(upper_left_x, x_size, x_rotation, upper_left_y, y_rotation, y_size) = r.GetGeoTransform()

a = band.ReadAsArray().astype(np.float)

# This evaluation makes x/y arrays for all cell values in a range.
# I knew how many points I should get for ==1000 and wanted to test it.
(y_index, x_index) = np.nonzero((a > 999) & (a < 1001))

# This evaluation makes x/y arrays for all cells having the fixed value, 1000.
#(y_index, x_index) = np.nonzero(a == 1000)

# DEBUG: take a look at the arrays..
#print repr((y_index, x_index))

# Init the shapefile stuff..
srs = osgeo.osr.SpatialReference()
#srs.ImportFromProj4('+proj=utm +zone=15 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs')
srs.ImportFromWkt(r.GetProjection())

driver = osgeo.ogr.GetDriverByName('ESRI Shapefile')
shapeData = driver.CreateDataSource('D:/GIS/01_tutorials/flow_acc/ogr_pts.shp')

layer = shapeData.CreateLayer('ogr_pts', srs, osgeo.ogr.wkbPoint)
layerDefinition = layer.GetLayerDefn()

# Iterate over the Numpy points..
i = 0
for x_coord in x_index:
    x = x_index[i] * x_size + upper_left_x + (x_size / 2) #add half the cell size
    y = y_index[i] * y_size + upper_left_y + (y_size / 2) #to centre the point

    # DEBUG: take a look at the coords..
    #print "Coords: " + str(x) + ", " + str(y)

    point = osgeo.ogr.Geometry(osgeo.ogr.wkbPoint)
    point.SetPoint(0, x, y)

    feature = osgeo.ogr.Feature(layerDefinition)
    feature.SetGeometry(point)
    feature.SetFID(i)

    layer.CreateFeature(feature)

    i += 1

shapeData.Destroy()

print "done! " + str(i) + " points found!"
elrobis
fuente
1
Sugerencia rápida para su código: puede usar la proyección de trama como su proyección de shapefile srs.ImportFromWkt(r.GetProjection())(en lugar de tener que crear una proyección a partir de una cadena de proyecto conocida).
om_henners
Su código hace todo lo que estoy buscando, excepto que no incluye el valor de celda ráster ya que ha escrito su filtro numpy para incluir solo valores = 1000. ¿Podría señalarme en la dirección correcta para incluir los valores de celda ráster en el ¿salida? ¡Gracias!
Brent Edwards

Respuestas:

19

En GDAL puede importar el ráster como una matriz numpy.

from osgeo import gdal
import numpy as np

r = gdal.Open("path/to/raster")
band = r.GetRasterBand(1) #bands start at one
a = band.ReadAsArray().astype(np.float)

Luego, usar numpy es simple obtener los índices de una matriz que coincida con una expresión boolan:

(y_index, x_index) = np.nonzero(a > threshold)
#To demonstate this compare a.shape to band.XSize and band.YSize

Desde la geotransformación ráster podemos obtener información como las coordenadas x e y superior izquierda y los tamaños de celda.

(upper_left_x, x_size, x_rotation, upper_left_y, y_rotation, y_size) = r.GetGeoTransform()

La celda superior izquierda corresponde a a[0, 0]. El tamaño Y siempre será negativo, por lo que con los índices x e y puede calcular las coordenadas de cada celda en función de los índices.

x_coords = x_index * x_size + upper_left_x + (x_size / 2) #add half the cell size
y_coords = y_index * y_size + upper_left_y + (y_size / 2) #to centre the point

A partir de aquí, es bastante simple crear un archivo de forma usando OGR. Para obtener un código de muestra, consulte esta pregunta sobre cómo generar un nuevo conjunto de datos con información de puntos.

om_henners
fuente
Hola amigo, tengo un pequeño problema al implementar esto. Actualicé la pregunta para incluir el código que estoy usando y una instantánea de lo que estoy obteniendo. Básicamente, el código .py está creando una imagen espejo (colocación de puntos) de lo que está generando el enfoque QGIS. Los puntos en mi implementación caen fuera de los límites de la trama, por lo que el problema tiene que ver con mi código. : = Espero que puedas arrojar algo de luz. ¡Gracias!
elrobis
Perdón por eso, completamente malo. Cuando importa un ráster en GDAL, las filas son la dirección y y las columnas son la dirección x. He actualizado el código anterior, pero el truco es obtener los índices con(y_index, x_index) = np.nonzero(a > threshold)
om_henners
1
Además, por si acaso, tenga en cuenta la adición de la mitad del tamaño de la celda a las coordenadas en ambas direcciones para centrar el punto en la celda.
om_henners
Sí, ese era el problema. Cuando encontré ese error por primera vez (como se muestra en la captura de pantalla), me pregunté si había implementado la geometría del punto incorrectamente, así que intenté voltear la x / y como y / x cuando hice el .shp--- solo que no lo hizo trabajo, ni estaba cerca. No me sorprendió ya que el valor de x está en los cientos de miles, y la y está en millones, por lo que me dejó bastante confundido. No pensé en intentar darles la vuelta ante la expresión de Numpy. Muchas gracias por su ayuda, esto es genial. Exactamente lo que quería. :)
elrobis
4

¿Por qué no usar la caja de herramientas Sexante en QGIS? Es algo así como el Model Builder para ArcGIS. De esa manera, puede encadenar operaciones y tratarla como una operación. Puede automatizar la eliminación de archivos intermedios y la eliminación de registros no deseados si no me equivoco.

RK
fuente
1

Puede ser útil importar los datos a postgis (con soporte de ráster) y usar las funciones allí. Este tutorial puede tener elementos que necesita.

JaakL
fuente