De shapefile comprimido subido a Geopandas DataFrame en la aplicación Django

Jan 05 2021

Estoy tratando de llegar a un punto en el que pueda filtrar rápidamente miles de puntos en un shapefile. Mi aplicación Django pide un archivo de forma comprimida a cargar, en el que el archivo comprimido contiene, al menos, las .shp, .shxy .dbfarchivos. Una vez en mi vista de Django, el archivo zip es el siguiente:

request.FILES['file'] > <InMemoryUploadedFile: test.zip (application/x-zip-compressed)>

type(request.FILES['file']) > <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>

request.FILES['file'].file > <_io.BytesIO object at 0x0000028E29F8FE00>

Suponiendo que Geopandas es la mejor opción para un filtrado / enmascaramiento eficiente (si me equivoco, definitivamente estoy abierto a sugerencias), no estoy seguro de cómo pasar del estado actual a un DataFrame de Geopandas. Cuando trato de usar el read_file()método

import geopandas as gpd
gpd.read_file(request.FILES['file'].file)

Obtuve el siguiente error:

fiona.errors.DriverError: no driver

Los geopandas.read_file() documentos dicen:

Ya sea la ruta absoluta o relativa al archivo o URL que se abrirá, o cualquier objeto con un read()método (como un archivo abierto o StringIO)

No estoy seguro de cómo obtener lo que tengo en un formato apropiado para el read_file()método.

Nota: El enmascaramiento y el filtrado que busco realizar se basan en datos de atributos y no en la geometría.

Respuestas

4 user2856 Jan 05 2021 at 06:19

Puede utilizar fiona.io.ZipMemoryFiley gpd.GeoDataFrame.from_features.

Ejemplo:

import geopandas as gpd
import io
from fiona.io import ZipMemoryFile

# Just to create a BytesIO object for the demo,
# similar to your request.FILES['file'].file
zipshp = io.BytesIO(open('test.zip', 'rb').read())

with (ZipMemoryFile(zipshp)) as memfile:
    with memfile.open() as src:
        crs = src.crs
        gdf = gpd.GeoDataFrame.from_features(src, crs=crs)
        print(gdf.head())

Tenga en cuenta que originalmente no BytesCollectionincluí el, como dijo el desarrollador de fiona en un comentario en mi respuesta anterior, que la clase probablemente quedaría obsoleta. Sin embargo, si lo usa, no debería necesitarlo ZipMemoryFile. Esto funciona para mi:

import geopandas as gpd
import io
import fiona


zipshp = io.BytesIO(open('test.zip', 'rb').read())

with fiona.BytesCollection(zipshp.read()) as src:
    crs = src.crs
    gdf = gpd.GeoDataFrame.from_features(src, crs=crs)
    print(gdf.head())
1 GISUser9 Jan 06 2021 at 00:04

La respuesta de @ user2856 me llevó a la mitad de la solución. No lo hubiera sabido fiona.io.ZipMemoryFile, y eso me llevó a esta respuesta . La combinación de las dos soluciones me dio:

with ZipMemoryFile(request.FILES['file'].file) as memfile:
    with fiona.BytesCollection(memfile._initial_bytes) as f:
        gdf = gpd.GeoDataFrame.from_features(f, crs='epsg:4326')
        print(gdf.head())

Ahora tengo mis datos de shapefile en un GeoDataFrame.

Para cualquiera que tenga curiosidad, la razón por la que fui en BytesCollectionlugar de memfile.open()es porque no pude ir memfile.open()al trabajo. Lanzaría un error diciendo que al .open()método le faltaba un argumento posicional de 'ruta'.