Descargar datos ráster a python desde postgis usando psycopg2

13

Tengo datos ráster en una tabla de postgres que quiero ingresar a python como una matriz numpy. Estoy usando psycopg2 para conectarme a la base de datos. Puedo descargar los datos pero vuelven como una cadena (probablemente un binario serializado).

¿Alguien sabe cómo tomar esta cadena y convertirla en una matriz numpy?

Exploré otras opciones para descargar el ráster, como usar st_astiff y codificar para descargar el archivo hexadecimal y usar xxd, pero eso no funcionó. Sigo recibiendo el error 'rt_raster_to_gdal: no se pudo cargar el controlador GDAL de salida' y no tengo permisos para configurar las variables de entorno para poder activar los controladores.

TL, DR: desea importar datos ráster en una matriz numpy (usando python).

Mayank Agarwal
fuente

Respuestas:

14

rt_raster_to_gdal: no se pudo cargar el controlador GDAL de salida

En cuanto al primer error con ST_AsTIFF , debe habilitar sus controladores GDAL, que por defecto no están habilitados para PostGIS 2.1. Consulte el manual sobre formas de hacerlo. Por ejemplo, tengo una variable de entorno configurada en una computadora con Windows con:

POSTGIS_GDAL_ENABLED_DRIVERS=GTiff PNG JPEG GIF XYZ DTED USGSDEM AAIGrid

que se puede confirmar con PostGIS con:

SELECT short_name, long_name
FROM ST_GDALDrivers();

PostGIS a Numpy

Puede exportar la salida a un archivo GeoTIFF de memoria virtual para que GDAL lo lea en una matriz Numpy. Para obtener pistas sobre los archivos virtuales utilizados en GDAL, consulte esta publicación de blog .

import os
import psycopg2
from osgeo import gdal

# Adjust this to connect to a PostGIS database
conn = psycopg2.connect(...)
curs = conn.cursor()

# Make a dummy table with raster data
curs.execute("""\
    SELECT ST_AsRaster(ST_Buffer(ST_Point(1, 5), 10), 10, 10, '8BUI', 1) AS rast
    INTO TEMP mytable;
""")

# Use a virtual memory file, which is named like this
vsipath = '/vsimem/from_postgis'

# Download raster data into Python as GeoTIFF, and make a virtual file for GDAL
curs.execute("SELECT ST_AsGDALRaster(rast, 'GTiff') FROM mytable;")
gdal.FileFromMemBuffer(vsipath, bytes(curs.fetchone()[0]))

# Read first band of raster with GDAL
ds = gdal.Open(vsipath)
band = ds.GetRasterBand(1)
arr = band.ReadAsArray()

# Close and clean up virtual memory file
ds = band = None
gdal.Unlink(vsipath)

print(arr)  # this is a 2D numpy array

Muestra un punto tamponado rasterizado.

[[0 0 0 1 1 1 1 0 0 0]
 [0 1 1 1 1 1 1 1 1 0]
 [0 1 1 1 1 1 1 1 1 0]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [0 1 1 1 1 1 1 1 1 0]
 [0 1 1 1 1 1 1 1 1 0]
 [0 0 0 1 1 1 1 0 0 0]]

Tenga en cuenta que usé un formato 'GTiff' en el ejemplo, pero otros formatos podrían ser más adecuados. Por ejemplo, si tiene un gran ráster que necesita ser transferido a través de una conexión lenta a Internet, intente usar 'PNG' para comprimirlo.

Mike T
fuente
Eso es muy útil.
John Powell
Muy útil. ¡Gracias! Todavía me encuentro con este problema que: ERROR: rt_raster_to_gdal: No se pudo cargar el controlador GDAL de salida, pero creo que tengo una solución para eso. ¡gracias de nuevo!
Mayank Agarwal
@MayankAgarwal actualizó la respuesta para el error rt_raster_to_gdal.
Mike T
6

Creo que la pregunta era si puedes leer desde las tablas de trama postgis SIN los controladores de gdal habilitados. Como todas las cosas de Python, ¡puedes!

Asegúrese de seleccionar su resultado ráster como WKBinary:

seleccione St_AsBinary (rast) ...

Utilice el siguiente script para descifrar WKBinary en un formato de imagen de Python. Prefiero opencv, porque maneja un número arbitrario de bandas de imagen, pero uno puede usar PIL / low si 1 o 3 bandas son más habituales.

Solo manejo imágenes de bytes por ahora, pero es relativamente trivial expandirse a otros tipos de datos.

Espero que esto sea útil.

estructura de importación
importar numpy como np
importar cv2

# Función para descifrar el encabezado WKB
def wkbHeader (sin formato):
    # Ver http://trac.osgeo.org/postgis/browser/trunk/raster/doc/RFC2-WellKnownBinaryFormat

    encabezado = {}

    encabezado ['endianess'] = struct.unpack ('B', raw [0]) [0]
    encabezado ['versión'] = struct.unpack ('H', sin procesar [1: 3]) [0]
    encabezado ['nbands'] = struct.unpack ('H', raw [3: 5]) [0]
    encabezado ['scaleX'] = struct.unpack ('d', raw [5:13]) [0]
    encabezado ['scaleY'] = struct.unpack ('d', raw [13:21]) [0]
    encabezado ['ipX'] = struct.unpack ('d', raw [21:29]) [0]
    encabezado ['ipY'] = struct.unpack ('d', raw [29:37]) [0]
    encabezado ['skewX'] = struct.unpack ('d', raw [37:45]) [0]
    encabezado ['skewY'] = struct.unpack ('d', raw [45:53]) [0]
    encabezado ['srid'] = struct.unpack ('i', raw [53:57]) [0]
    encabezado ['ancho'] = struct.unpack ('H', sin procesar [57:59]) [0]
    header ['height'] = struct.unpack ('H', raw [59:61]) [0]

    encabezado de retorno

# Función para descifrar los datos ráster de WKB 
def wkbImage (sin formato):
    h = wkbHeader (sin procesar)
    img = [] # matriz para almacenar bandas de imágenes
    offset = 61 # longitud bruta del encabezado en bytes
    para i en rango (h ['nbands']):
        # Determinar pixtype para esta banda
        pixtype = struct.unpack ('B', raw [offset]) [0] >> 4
        # Por ahora, solo manejamos byte sin firmar
        si pixtype == 4:
            band = np.frombuffer (raw, dtype = 'uint8', count = h ['width'] * h ['height'], offset = offset + 1)
            img.append ((np.reshape (band, ((h ['height'], h ['width'])))))
            offset = offset + 2 + h ['ancho'] * h ['alto']
        # para hacer: manejar otros tipos de datos 

    return cv2.merge (tupla (img))

GGL
fuente
Eso es muy útil. He estado teniendo muchos problemas con gdal en un entorno conda, pero este enfoque funcionó por primera vez, y es bueno poder profundizar un poco en la estructura también.
John Powell