Do arquivo de forma compactado enviado ao Geopandas DataFrame no aplicativo Django

Jan 05 2021

Estou tentando chegar a um ponto em que posso filtrar rapidamente milhares de pontos em um shapefile. A minha aplicação Django pede um shapefile zipado para upload, onde o arquivo compactado contém pelo menos os .shp, .shxe .dbfarquivos. Uma vez na visualização do Django, o arquivo zip é o seguinte:

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>

Supondo que o Geopandas seja a melhor opção para filtragem / mascaramento eficiente (se estiver errado, estou aberto a sugestões), não tenho certeza de como ir do estado atual para um DataFrame do Geopandas. Quando tento usar o read_file()método

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

Eu obtenho o seguinte erro:

fiona.errors.DriverError: no driver

O estado dos geopandas.read_file() documentos :

Tanto o caminho absoluto ou relativo para o arquivo ou URL a ser aberto, ou qualquer objeto com um read()método (como um arquivo aberto ou StringIO)

Não tenho certeza de como colocar o que tenho em um formato apropriado para o read_file()método.

Observação: o mascaramento e a filtragem que estou tentando realizar estão nos dados de atributo e não na geometria.

Respostas

4 user2856 Jan 05 2021 at 06:19

Você pode usar fiona.io.ZipMemoryFilee gpd.GeoDataFrame.from_features.

Exemplo:

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())

Nota, originalmente não incluí o BytesCollectioncomo o desenvolvedor fiona declarou em um comentário sobre minha resposta anterior que a classe provavelmente seria descontinuada. No entanto, se você usá-lo, não será necessário ZipMemoryFile. Isso funciona para mim:

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

A resposta de @ user2856 me ajudou a chegar a uma solução. Eu não teria sabido fiona.io.ZipMemoryFile, e isso me levou a esta resposta . Combinar as duas soluções me deu:

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())

Agora tenho meus dados do arquivo de forma em um GeoDataFrame.

Para quem está curioso, optei por seguir em BytesCollectionvez de memfile.open()porque não consegui memfile.open()trabalhar. Ele geraria um erro dizendo que o .open()método não tinha um argumento posicional de 'caminho'.