From e6cbe5bf276d85b58fd597b36f2d3f628dcf97d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 20 Sep 2016 15:57:37 +0200 Subject: [PATCH] add GeometryField and features api endpoint --- src/c3nav/api/fields.py | 24 ++++++++++++++ src/c3nav/api/serializers.py | 11 ++++++- src/c3nav/api/urls.py | 1 + src/c3nav/api/views/editor.py | 14 +++++++-- src/c3nav/mapdata/fields.py | 17 ++++++++++ .../migrations/0004_auto_20160920_1356.py | 31 +++++++++++++++++++ src/c3nav/mapdata/models/feature.py | 6 ++-- src/requirements/production.txt | 1 + 8 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/c3nav/api/fields.py create mode 100644 src/c3nav/mapdata/fields.py create mode 100644 src/c3nav/mapdata/migrations/0004_auto_20160920_1356.py diff --git a/src/c3nav/api/fields.py b/src/c3nav/api/fields.py new file mode 100644 index 00000000..2d2e3509 --- /dev/null +++ b/src/c3nav/api/fields.py @@ -0,0 +1,24 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from shapely.geometry import mapping, shape + + +class GeometryField(serializers.DictField): + """ + shapely geometry objects serialized using GeoJSON + """ + default_error_messages = { + 'invalid': _('Invalid GeoJSON.') + } + + def to_representation(self, obj): + geojson = mapping(obj) + return super().to_representation(geojson) + + def to_internal_value(self, data): + geojson = super().to_internal_value(data) + try: + return shape(geojson) + except: + raise ValidationError(_('Invalid GeoJSON.')) diff --git a/src/c3nav/api/serializers.py b/src/c3nav/api/serializers.py index 9f29b89a..79631b40 100644 --- a/src/c3nav/api/serializers.py +++ b/src/c3nav/api/serializers.py @@ -2,7 +2,8 @@ from django.conf import settings from rest_framework import serializers from ..editor.hosters import get_hoster_for_package -from ..mapdata.models import Level, Package, Source +from ..mapdata.models import Feature, Level, Package, Source +from .fields import GeometryField from .permissions import can_access_package @@ -42,6 +43,14 @@ class FeatureTypeSerializer(serializers.Serializer): color = serializers.CharField() +class FeatureSerializer(serializers.ModelSerializer): + geometry = GeometryField() + + class Meta: + model = Feature + fields = ('name', 'package', 'feature_type', 'geometry') + + class HosterSerializer(serializers.Serializer): name = serializers.CharField() base_url = serializers.CharField() diff --git a/src/c3nav/api/urls.py b/src/c3nav/api/urls.py index c5059173..9c196358 100644 --- a/src/c3nav/api/urls.py +++ b/src/c3nav/api/urls.py @@ -9,6 +9,7 @@ router.register(r'levels', mapdata_views.LevelViewSet) router.register(r'packages', mapdata_views.PackageViewSet) router.register(r'sources', mapdata_views.SourceViewSet) router.register(r'featuretypes', mapdata_views.FeatureTypeViewSet, base_name='featuretype') +router.register(r'features', editor_views.FeatureViewSet) router.register(r'hosters', editor_views.HosterViewSet, base_name='hoster') diff --git a/src/c3nav/api/views/editor.py b/src/c3nav/api/views/editor.py index 76c625dc..c58f485f 100644 --- a/src/c3nav/api/views/editor.py +++ b/src/c3nav/api/views/editor.py @@ -1,9 +1,10 @@ from django.http import Http404 from rest_framework.response import Response -from rest_framework.viewsets import ViewSet +from rest_framework.viewsets import ModelViewSet, ViewSet from ...editor.hosters import hosters -from ..serializers import HosterSerializer +from ...mapdata.models import Feature +from ..serializers import FeatureSerializer, HosterSerializer class HosterViewSet(ViewSet): @@ -19,3 +20,12 @@ class HosterViewSet(ViewSet): raise Http404 serializer = HosterSerializer(hosters[pk], context={'request': request}) return Response(serializer.data) + + +class FeatureViewSet(ModelViewSet): + """ + Get all Map Features including ones that are only part of the current session + """ + queryset = Feature.objects.all() + serializer_class = FeatureSerializer + lookup_value_regex = '[^/]+' diff --git a/src/c3nav/mapdata/fields.py b/src/c3nav/mapdata/fields.py new file mode 100644 index 00000000..cd41b368 --- /dev/null +++ b/src/c3nav/mapdata/fields.py @@ -0,0 +1,17 @@ +import json + +from django.db import models +from shapely.geometry import mapping, shape + + +class GeometryField(models.TextField): + def from_db_value(self, value, expression, connection, context): + if value is None: + return value + return shape(json.loads(value)) + + def to_python(self, value): + return shape(json.loads(value)) + + def get_prep_value(self, value): + return json.dumps(mapping(value)) diff --git a/src/c3nav/mapdata/migrations/0004_auto_20160920_1356.py b/src/c3nav/mapdata/migrations/0004_auto_20160920_1356.py new file mode 100644 index 00000000..87fa318d --- /dev/null +++ b/src/c3nav/mapdata/migrations/0004_auto_20160920_1356.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2016-09-20 13:56 +from __future__ import unicode_literals + +import c3nav.mapdata.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0003_package_commit_id'), + ] + + operations = [ + migrations.RemoveField( + model_name='feature', + name='type', + ), + migrations.AddField( + model_name='feature', + name='feature_type', + field=models.CharField(choices=[('building', 'Building'), ('room', 'Room'), ('outside', 'Outside Area'), ('obstacle', 'Obstacle')], default=None, max_length=50), + preserve_default=False, + ), + migrations.AlterField( + model_name='feature', + name='geometry', + field=c3nav.mapdata.fields.GeometryField(), + ), + ] diff --git a/src/c3nav/mapdata/models/feature.py b/src/c3nav/mapdata/models/feature.py index da65b07d..43165a7d 100644 --- a/src/c3nav/mapdata/models/feature.py +++ b/src/c3nav/mapdata/models/feature.py @@ -3,6 +3,8 @@ from collections import OrderedDict, namedtuple from django.db import models from django.utils.translation import ugettext_lazy as _ +from ..fields import GeometryField + class FeatureType(namedtuple('FeatureType', ('name', 'title', 'title_plural', 'geomtype', 'color'))): def __init__(self, *args, **kwartgs): @@ -27,8 +29,8 @@ class Feature(models.Model): name = models.SlugField(_('feature identifier'), primary_key=True, max_length=50, help_text=_('e.g. noc')) package = models.ForeignKey('Package', on_delete=models.CASCADE, related_name='features', verbose_name=_('map package')) - type = models.CharField(max_length=50, choices=TYPES) - geometry = models.TextField() + feature_type = models.CharField(max_length=50, choices=TYPES) + geometry = GeometryField() class FeatureTitle(models.Model): diff --git a/src/requirements/production.txt b/src/requirements/production.txt index fca2a277..82814034 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -4,3 +4,4 @@ django-compressor==2.1 csscompressor djangorestframework>=3.4,<3.5 django-filter>=0.14,<0.15 +shapely>=1.5,<1.6