7

I'm trying to get to a point where I can quickly filter thousands of points in a shapefile. My Django application asks for a zipped shapefile to upload, where the zipped file contains at least the .shp, .shx, and .dbf files. Once in my Django view, the zip file is as follows:

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>

Assuming Geopandas is the best option for efficient filtering/masking (if I'm wrong, I'm definitely open to suggestions), I'm not sure how to go from current state to a Geopandas DataFrame. When I try to use the read_file() method

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

I get the following error:

fiona.errors.DriverError: no driver

The geopandas.read_file() docs state:

Either the absolute or relative path to the file or URL to be opened, or any object with a read() method (such as an open file or StringIO)

I'm not sure how to get what I have into an appropriate format for the read_file() method.

Note: The masking and filtering I'm looking to perform are on attribute data and not the geometry.

Taras
  • 32,823
  • 4
  • 66
  • 137
GISUser9
  • 825
  • 11
  • 24
  • I think you can also use this syntax: zipfile = "zip:///Users/name/Downloads/cb_2017_us_state_500k.zip" states = geopandas.read_file(zipfile) from the official docs - https://geopandas.org/en/stable/docs/user_guide/io.html – user88484 May 24 '22 at 07:49

3 Answers3

6

You can use fiona.io.ZipMemoryFile and gpd.GeoDataFrame.from_features.

Example:

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

Note, I originally didn't include the BytesCollection as the fiona developer stated in a comment on my previous answer that the class would likely be deprecated. However, if you use it, you shouldn't need ZipMemoryFile. This works for me:

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

user2856
  • 65,736
  • 6
  • 115
  • 196
3

@user2856's answer got me half way to a solution. I would not have known about fiona.io.ZipMemoryFile, and that led me to this answer. Combining the two solutions gave me:

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

I now have my shapefile data in a GeoDataFrame.

For anyone who's curious, the reason I went with BytesCollection instead of memfile.open() is because I couldn't get memfile.open() to work. It would throw an error saying the .open() method was missing a 'path' positional argument.

GISUser9
  • 825
  • 11
  • 24
0

Based on the docs, you only need geopandas and fiona library installed:

You can also load ZIP files that contain your data:

zipfile = "filename.zip" states = geopandas.read_file(zipfile)

Note: fiona library is mentioned in previous gpd docs, and in this one for other features. not sure it is strictly needed for zip files, but it's a useful library for spatial manipulations

Ohad
  • 1
  • 2