Extraiga valores ráster dentro de shapefile con pygeoprocessing o gdal

11

Me gustaría saber cómo obtener todos los valores ráster dentro de un polígono usando gdal o pygeoprocessing, sin leer toda la cuadrícula como una matriz.

pygeoprocessing y gdal pueden hacer estadísticas zonales, pero solo la función min, max, mean, stdev o count están disponibles desde dicha función. Dado que las estadísticas zonales necesitan acceder a los valores, ¿sería fácil extraer valores de la misma manera?

Encontré una pregunta muy similar aquí: (¿ Obtener el valor de píxel del ráster GDAL bajo el punto OGR sin NumPy? ) Pero solo para un "punto" particular.

egayer
fuente
Si tiene problemas al usar rasterio en el mismo script con gdal, estaba probando con pygeoprocessing (también se usa bien) y descubrí una solución alternativa.
xunilk

Respuestas:

16

Puede usar rasterio para extraer los valores de trama dentro de un polígono como en GIS SE: GDAL python cut geotiff image with geojson file

Utilizo aquí un archivo ráster de una banda y GeoPandas para el archivo de forma (en lugar de Fiona)

ingrese la descripción de la imagen aquí

import rasterio
from rasterio.mask import mask
import geopandas as gpd
shapefile = gpd.read_file("extraction.shp")
# extract the geometry in GeoJSON format
geoms = shapefile.geometry.values # list of shapely geometries
geometry = geoms[0] # shapely geometry
# transform to GeJSON format
from shapely.geometry import mapping
geoms = [mapping(geoms[0])]
# extract the raster values values within the polygon 
with rasterio.open("raster.tif") as src:
     out_image, out_transform = mask(src, geoms, crop=True)

El resultado de out_image es una matriz enmascarada de Numpy

# no data values of the original raster
no_data=src.nodata
print no_data
-9999.0
# extract the values of the masked array
data = out_image.data[0]
# extract the row, columns of the valid values
import numpy as np
row, col = np.where(data != no_data) 
elev = np.extract(data != no_data, data)

Ahora uso ¿Cómo obtengo las coordenadas de una celda en un geotif? o Python affine transforma para transformar entre el píxel y las coordenadas proyectadas con out_transform la transformación afín para los datos del subconjunto

 from rasterio import Affine # or from affine import Affine
 T1 = out_transform * Affine.translation(0.5, 0.5) # reference the pixel centre
 rc2xy = lambda r, c: (c, r) * T1  

Creación de un nuevo GeoDataFrame resultante con los valores de columna, fila y elevación

d = gpd.GeoDataFrame({'col':col,'row':row,'elev':elev})
# coordinate transformation
d['x'] = d.apply(lambda row: rc2xy(row.row,row.col)[0], axis=1)
d['y'] = d.apply(lambda row: rc2xy(row.row,row.col)[1], axis=1)
# geometry
from shapely.geometry import Point
d['geometry'] =d.apply(lambda row: Point(row['x'], row['y']), axis=1)
# first 2 points
d.head(2)
     row  col   elev       x          y            geometry  
 0    1    2  201.7!  203590.58  89773.50  POINT (203590.58 89773.50)  
 1    1    3  200.17  203625.97  89773.50  POINT (203625.97 89773.50)

# save to a shapefile
d.to_file('result.shp', driver='ESRI Shapefile')

ingrese la descripción de la imagen aquí

gene
fuente
Gracias @gene por esta respuesta completa. Sin embargo, entiendo que rasterio no funciona bien con gdal en el mismo script, lo que puede ser un problema para mí, además necesito instalar rasterio y probar antes de aceptar su respuesta.
egayer
Hola @gene, ¿por qué necesitabas usarlo en geoms = [mapping(geoms[0])]lugar de solo geoms[0]?
clifgray
1
mapping(geoms[0])= Formato GeoJSON de la geometría
gen
Hola Gene, recibí este error "Tipo de campo no válido <tipo 'numpy.ndarray'>" en la última línea (d.to_file)
ilFonta
1
data = out_image.data[0]tiró multi-dimensional sub-views are not implementedpor mí, pero data = out_image[0,:,:]funcionó. ¿Es esta una solución alternativa menos eficiente o problemática? ¿Alguna idea de por qué habría fallado como está escrito?
jbaums
2

Si tiene problemas al usar rasterio en el mismo script con gdal, estaba probando con pygeoprocessing (también se usa bien) y descubrí una solución alternativa. La secuencia de comandos completa (con rutas a mis capas) es la siguiente:

import pygeoprocessing.geoprocessing as geop
from shapely.geometry import shape, mapping, Point
from osgeo import gdal
import numpy as np 
import fiona

path = '/home/zeito/pyqgis_data/'

uri1 = path + 'aleatorio.tif'

info_raster2 = geop.get_raster_info(uri1)

geop.create_raster_from_vector_extents(base_vector_path = path + 'cut_polygon3.shp',
                                       target_raster_path = path + 'raster_from_vector_extension.tif',
                                       target_pixel_size = info_raster2['pixel_size'],
                                       target_pixel_type = info_raster2['datatype'],
                                       target_nodata = -999,
                                       fill_value = 1)

uri2 = path + 'raster_from_vector_extension.tif'

info_raster = geop.get_raster_info(uri2)

cols = info_raster['raster_size'][0]
rows = info_raster['raster_size'][1]

geotransform = info_raster['geotransform']

xsize =  geotransform[1]
ysize = -geotransform[5]

xmin = geotransform[0]
ymin = geotransform[3]

# create one-dimensional arrays for x and y
x = np.linspace(xmin + xsize/2, xmin + xsize/2 + (cols-1)*xsize, cols)
y = np.linspace(ymin - ysize/2, ymin - ysize/2 - (rows-1)*ysize, rows)

# create the mesh based on these arrays
X, Y = np.meshgrid(x, y)

X = X.reshape((np.prod(X.shape),))
Y = Y.reshape((np.prod(Y.shape),))

coords = zip(X, Y)

shapely_points = [ Point(point[0], point[1]) for point in coords ]

polygon = fiona.open(path + 'cut_polygon3.shp')
crs = polygon.crs
geom_polygon = [ feat["geometry"] for feat in polygon ]

shapely_geom_polygon = [ shape(geom) for geom in geom_polygon ]

within_points = [ (pt.x, pt.y) for pt in shapely_points if pt.within(shapely_geom_polygon[0]) ]

src_ds = gdal.Open(uri1)
rb = src_ds.GetRasterBand(1)

gt = info_raster2['geotransform']

values = [ rb.ReadAsArray(int((point[0] - gt[0]) / gt[1]), #x pixel
                          int((point[1] - gt[3]) / gt[5]), #y pixel
                          1, 1)[0][0] 
           for point in within_points ]

#creation of the resulting shapefile
schema = {'geometry': 'Point','properties': {'id': 'int', 'value':'int'},}

with fiona.open('/home/zeito/pyqgis_data/points_proc.shp', 'w', 'ESRI Shapefile', schema, crs)  as output:

    for i, point in enumerate(within_points):
        output.write({'geometry':mapping(Point(point)),'properties': {'id':i, 'value':str(values[i])}})

Después de ejecutarlo, obtuve:

ingrese la descripción de la imagen aquí

donde los valores de muestreo ráster fueron los esperados en cada punto y se incorporaron a la capa de puntos.

xunilk
fuente
Hola Xunilk, ¿cuáles son los archivos de entrada a tu script? Me gustaría usar solo el ráster y el archivo de forma con los polígonos. Muchas gracias
ilFonta