diff --git a/requirements.txt b/requirements.txt index 19df5b6f..33162d17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ django>=4 django-computedfields +fiona psycopg2-binary transifex-client djangorestframework diff --git a/src/django_oapif/decorators.py b/src/django_oapif/decorators.py index 36357e5d..e0317fa2 100644 --- a/src/django_oapif/decorators.py +++ b/src/django_oapif/decorators.py @@ -1,11 +1,12 @@ from typing import Any, Callable, Dict, Optional from django.db.models import Model -from rest_framework import viewsets +from rest_framework import renderers, viewsets from rest_framework_gis.serializers import GeoFeatureModelSerializer from django_oapif.metadata import OAPIFMetadata from django_oapif.mixins import OAPIFDescribeModelViewSetMixin +from django_oapif.renderers import FGBRenderer from django_oapif.urls import oapif_router from .filters import BboxFilterBackend @@ -57,6 +58,7 @@ class Meta: else: """ viewset_serializer_class = AutoSerializer + viewset_renderer_classes = [renderers.JSONRenderer, FGBRenderer] viewset_oapif_geom_lookup = ( "geom" # one day this will be retrieved automatically from the serializer ) @@ -65,6 +67,7 @@ class Meta: class Viewset(OAPIFDescribeModelViewSetMixin, viewsets.ModelViewSet): queryset = Model.objects.all() serializer_class = viewset_serializer_class + renderer_classes = viewset_renderer_classes # TODO: these should probably be moved to the mixin oapif_title = Model._meta.verbose_name diff --git a/src/django_oapif/renderers.py b/src/django_oapif/renderers.py new file mode 100644 index 00000000..decfa14d --- /dev/null +++ b/src/django_oapif/renderers.py @@ -0,0 +1,28 @@ +import io +from typing import OrderedDict + +import fiona +from django.http import StreamingHttpResponse +from rest_framework import renderers + + +class FGBRenderer(renderers.BaseRenderer): + format = "fgb" + media_type = "application/octet-stream" + # FIXME: This should be sent by the model. + schema = {"geometry": "Point", "properties": {"name": "str", "_serialized": "str"}} + + def render( + self, data: OrderedDict, accepted_media_type=None, renderer_context=None + ) -> StreamingHttpResponse: + """Renders pre-serialized Python objects as a flatgeobuf binary stream""" + features = (fiona.Feature.from_dict(obj) for obj in data["features"]) + buffer_wrapper = io.BytesIO() + + with fiona.open( + buffer_wrapper, mode="w", driver="FlatGeobuf", schema=self.schema + ) as fh: + for feature in features: + fh.write(feature) + + return StreamingHttpResponse(buffer_wrapper.getvalue())