GDAL e Python: como obter coordenadas para todas as células com um valor específico?

12

Eu tenho uma grade binária Arc / Info - especificamente, uma varredura de acumulação de fluxo ArcGIS - e gostaria de identificar todas as células com um valor específico (ou em um intervalo de valores). Por fim, eu gostaria de um shapefile de pontos representando essas células.

Posso usar o QGIS para abrir o hdr.adf e obter esse resultado, o fluxo de trabalho é:

  • QGIS> menu Raster> Calculadora Raster (marque todos os pontos com o valor alvo)
  • QGIS> menu Raster> poligonizar
  • QGIS> Menu vetorial> Submenu Geometria> Centróide de polígono
  • Edite os centróides para excluir os poli-centróides indesejados (aqueles = 0)

Essa abordagem "faz o trabalho", mas não me agrada porque cria 2 arquivos que tenho que excluir e, em seguida, tenho que remover registros indesejados do shapefile dos centróides (ou seja, aqueles = 0).

Uma pergunta existente aborda esse assunto, mas é adaptada ao ArcGIS / ArcPy, e eu gostaria de permanecer no espaço do FOSS.

Alguém tem uma receita / script GDAL / Python existente que interroga os valores das células de uma varredura e, quando um valor alvo --- ou um valor no intervalo alvo --- é encontrado, um registro é adicionado a um shapefile? Isso não apenas evitaria a interação da interface do usuário, mas também criaria um resultado limpo em uma única passagem.

Tomei uma chance trabalhando contra uma das apresentações de Chris Garrard , mas o trabalho raster não está na minha casa do leme e não quero bagunçar a questão com meu código fraco.

Se alguém quiser jogar com o conjunto de dados exato, coloquei-o aqui como um .zip .


[Editar notas] Deixando isso para trás para a posteridade. Veja trocas de comentários com om_henners. Basicamente, os valores x / y (linha / coluna) foram invertidos. A resposta original tinha esta linha:

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

invertido, assim:

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

Quando encontrei o problema ilustrado na captura de tela, imaginei se havia implementado a geometria incorretamente e experimentei inverter os valores das coordenadas x / y nesta linha:

point.SetPoint(0, x, y)

..Como..

point.SetPoint(0, y, x)

No entanto, isso não funcionou. E não pensei em tentar inverter os valores na expressão Numpy do om_henners, acreditando erroneamente que lançá-los em qualquer linha era equivalente. Penso que o verdadeiro problema está relacionado com os valores x_sizee y_size, respectivamente, 30e -30que são aplicados quando os índices de linha e coluna são usados ​​para calcular coordenadas de ponto para as células.

[Edição original]

@om_henners, estou tentando sua solução, em conjunto com algumas receitas para criar arquivos shapefiles de pontos usando ogr ( invisibleroads.com , Chris Garrard ), mas estou tendo um problema em que os pontos aparecem como se refletidos em uma linha que passava a 315/135 graus.

Pontos azuis claros : criados pela minha abordagem QGIS , acima

Pontos roxos : criados pelo código py GDAL / OGR , abaixo

insira a descrição da imagem aqui


[Resolvido]

Este código Python implementa a solução completa, conforme proposto por @om_henners. Eu testei e funciona. Valeu cara!


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
fonte
1
Dica rápida para o seu código: você pode usar a projeção de varredura como sua projeção de shapefile srs.ImportFromWkt(r.GetProjection())(em vez de precisar criar uma projeção a partir de uma sequência de projeção conhecida).
Om_henners
Seu código faz tudo o que estou procurando, exceto que ele não inclui o valor da célula de varredura, pois você escreveu seu filtro numpy para incluir apenas valores = 1000. Você poderia me indicar a direção certa para incluir os valores das células de varredura no campo resultado? Obrigado!
Brent Edwards

Respostas:

18

No GDAL, você pode importar a varredura como uma 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)

Então, usando numpy, é simples obter os índices de uma matriz que correspondem a uma expressão boolan:

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

A partir da geotransformação raster, podemos obter informações como as coordenadas xey superiores e os tamanhos das células.

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

A célula superior esquerda corresponde a a[0, 0]. O tamanho Y sempre será negativo; portanto, usando os índices x e y, é possível calcular as coordenadas de cada célula com base nos í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 daqui, é bastante simples criar um shapefile usando OGR. Para algum código de exemplo, consulte esta pergunta para saber como gerar um novo conjunto de dados com informações de ponto.

om_henners
fonte
Ei, pessoal, estou tendo um pequeno problema ao implementar isso. Atualizei a pergunta para incluir o código que estou usando e uma descrição do que estou recebendo. Basicamente, o código .py está criando uma imagem espelhada (posicionamento do ponto) do que a abordagem QGIS está gerando. Os pontos da minha implementação estão fora dos limites da varredura, então o problema tem que estar no meu código. : = Espero que você possa lançar alguma luz. Obrigado!
22412 elrobis
Desculpe por isso - completamente o meu mal. Quando você importa uma varredura no GDAL, as linhas são a direção y e as colunas são a direção x. Eu atualizei o código acima, mas o truque é fazer com que os índices com(y_index, x_index) = np.nonzero(a > threshold)
om_henners
1
Além disso, por precaução, observe a adição de metade do tamanho da célula às coordenadas nas duas direções para centralizar o ponto na célula.
Om_henners #
Sim, esse era o problema. Quando encontrei o bug pela primeira vez (como mostrado na captura da tela), fiquei imaginando se havia implementado a geometria do ponto incorretamente. Tentei inverter o x / y como y / x quando fiz o .shp--- apenas que não trabalho, nem foi em qualquer lugar perto. Não fiquei chocado, já que o valor de x está na casa dos cem mil e o y está na casa dos milhões, então isso me deixou bastante confuso. Não pensei em tentar revertê-los com a expressão Numpy. Muito obrigado por sua ajuda, isso é legal. Exatamente o que eu queria. :)
elrobis
4

Por que não usar a caixa de ferramentas Sexante no QGIS? É como o Model Builder for ArcGIS. Dessa forma, você pode encadear operações e tratá-las como uma operação. Você pode automatizar a exclusão de arquivos intermediários e a remoção de registros indesejados, se não me engano.

RK
fonte
1

Pode ser útil importar os dados para o postgis (com suporte a varredura) e usar as funções lá. Este tutorial pode ter elementos que você precisa.

JaakL
fonte