Skip to content

Commit

Permalink
Support ingesting zipped shapefile, geojson
Browse files Browse the repository at this point in the history
If the payload's shape is a zipped shapefile or a geojson,
they will be parsed as shapes and added to the database. In
the case of geojson, the file is parsed and ingested directly.
In the case of zipped shapefiles, we save them to a temporary
directory, then load them via fiona and extract the shape from
within. fiona cannot read files from memory, and the uploaded
files could potentially be really heavy, so doing so helps
manage the load.

These are the only two formats we are supporting at the
moment, more may be added in the future.

This assumes a single Polygon input. Support for MultiPolygons
will be added in #127.
  • Loading branch information
rajadain committed Nov 2, 2022
1 parent 541a11f commit 708f935
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/app/src/components/ModalSections/FileUpload.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function UploadBox({ addFiles }) {
<Bold>ACCEPTED FILES</Bold>
</Text>
<Text color='gray.400'>
<Bold>Shapefiles:</Bold> .SHP, .SHX and .DBF
<Bold>Shapes:</Bold> Shapefile ZIP, GEOJSON
<br />
<Bold>Reference Images:</Bold> JPEG, PNG
</Text>
Expand Down
2 changes: 1 addition & 1 deletion src/app/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const ROLES = {

export const REFERENCE_IMAGE_MIME_TYPES = ['image/png', 'image/jpeg'];
export const REFERENCE_IMAGE_FILE_EXTENSIONS = ['.png', '.jpg', '.jpeg'];
export const SHAPE_FILE_EXTENSIONS = ['.shp'];
export const SHAPE_FILE_EXTENSIONS = ['.zip', '.geojson'];

export const FILE_UPLOAD_ACCEPT_STRING = [
...REFERENCE_IMAGE_MIME_TYPES,
Expand Down
43 changes: 39 additions & 4 deletions src/django/api/fields.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
from rest_framework.fields import FileField
import os
import fiona
import json
import tempfile

from django.contrib.gis.geos import Polygon
from pathlib import Path
from django.contrib.gis.geos import GEOSGeometry
from rest_framework.fields import FileField
from rest_framework.serializers import ValidationError


class ShapefileField(FileField):
def to_internal_value(self, data):
# shapefile = super().to_internal_value(data)
return Polygon()
if data.name.endswith(".zip"):
# Treat like a zipped shapefile
try:
tmpdir = tempfile.mkdtemp()
tmpfile = Path(f"{tmpdir}/{data.name}")
with open(tmpfile, "wb+") as f:
for chunk in data.chunks():
f.write(chunk)

with fiona.open(f"zip://{tmpfile}") as f:
# TODO Capture all features in the shapefile, not just first
geojson = json.dumps(f[0]["geometry"])
return GEOSGeometry(geojson)

except Exception:
raise ValidationError(
f"Could not parse {data.name} as a zipped shapefile."
)

finally:
os.remove(tmpfile)
os.rmdir(tmpdir)

if data.name.endswith(".geojson"):
geojson = data.read()
return GEOSGeometry(geojson)

raise ValidationError(
f"Incompatible file: {data.name}."
" Must be either a zipped shapefile, or a geojson."
)

0 comments on commit 708f935

Please sign in to comment.