Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

polygon field added #155

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,64 @@ point = {
}
serializer = PointFieldSerializer(data={'created': now, 'point': point})
```
## PolygonField

Polygon field for GeoDjango

**Signature:** `PolygonField()`
- It takes a list of orderly pair arrays, representing points which all together are supposed to make a polygon. example:

[
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]

**Example:**

```python
# serializer

from drf_extra_fields.geo_fields import PolygonField

class PolygonFieldSerializer(serializers.Serializer):
polygon = PolygonField(required=False)
created = serializers.DateTimeField()

# use the serializer
polygon = [
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
serializer = PolygonFieldSerializer(data={'created': now, 'polygon': polygon})
```

## IntegerRangeField

Expand Down
75 changes: 75 additions & 0 deletions drf_extra_fields/geo_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from rest_framework import serializers

EMPTY_VALUES = (None, '', [], (), {})
from django.contrib.gis.geos import Polygon


class PointField(serializers.Field):
Expand Down Expand Up @@ -76,3 +77,77 @@ def to_representation(self, value):
value['latitude'] = smart_str(value.pop('latitude'))

return value



class PolygonField(serializers.Field):
"""
A field for handling GeoDjango PolyGone fields as a array format.
Expected input format:
{
[
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't support polygon with inner rings.

How about we use the following format with the key coordinates?

Polygon with single ring (The way you implemented.)

coordinates : [
     [ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ]
  ]

Polygon with multiple rings (The one that is not supported at the moment.)

coordinates : [
     [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ],
     [ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ]
  ]

From geojson specification

The "coordinates" member must be an array of LinearRing coordinate arrays. For Polygons with multiple rings, the first must be the exterior ring and any others must be interior rings or holes.

Please see django documentation for usage.


"""
type_name = 'PolygonField'
type_label = 'polygon'

default_error_messages = {
'invalid': _('Enter a valid polygon.'),
}

def __init__(self, *args, **kwargs):
super(PolygonField, self).__init__(*args, **kwargs)

def to_internal_value(self, value):
"""
Parse array data and return a polygon object
"""
if value in EMPTY_VALUES and not self.required:
return None

try:
new_value = []
for item in value:
item = list(map(float, item))
new_value.append(item)
except ValueError:
self.fail('invalid')

try:
return Polygon(new_value)
except (GEOSException, ValueError, TypeError):
self.fail('invalid')
self.fail('invalid')


def to_representation(self, value):
"""
Transform Polygon object to array of arrays.
"""
if value is None:
return value

if isinstance(value, GEOSGeometry):
value = value.boundary.array


return value

165 changes: 163 additions & 2 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
LowercaseEmailField,
DecimalRangeField,
)
from drf_extra_fields.geo_fields import PointField
from drf_extra_fields.geo_fields import PointField, PolygonField
from drf_extra_fields import compat

UNDETECTABLE_BY_IMGHDR_SAMPLE = """data:image/jpeg;base64,
Expand Down Expand Up @@ -269,13 +269,174 @@ def test_download(self):
finally:
os.remove('im.jpg')

class SavePolygon(object):
def __init__(self, polygon=None, created=None):
self.polygon = polygon
self.created = created or datetime.datetime.now()

class PolygonSerializer(serializers.Serializer):
polygon = PolygonField(required=False)
created = serializers.DateTimeField()

def update(self, instance, validated_data):
instance.polygon = validated_data['polygon']
return instance

def create(self, validated_data):
return SavePolygon(**validated_data)


class StringPolygonSerializer(PolygonSerializer):
polygon = PolygonField(required=False)


class PolygonSerializerTest(TestCase):
def test_create(self):
"""
Test for creating Polygon field in the server side
"""
now = datetime.datetime.now()
polygon = [
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
serializer = PolygonSerializer(data={'created': now, 'polygon': polygon})
saved_polygon = SavePolygon(polygon=polygon, created=now)
self.assertTrue(serializer.is_valid())
self.assertEqual(serializer.validated_data['created'], saved_polygon.created)
self.assertIsNone(serializer.validated_data['polygon'].srid)

def test_remove_with_empty_string(self):
"""
Passing empty string as data should cause polygon to be removed
"""
now = datetime.datetime.now()
polygon = [
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
saved_polygon = SavePolygon(polygon=polygon, created=now)
serializer = PolygonSerializer(data={'created': now, 'polygon': ''})
self.assertTrue(serializer.is_valid())
self.assertEqual(serializer.validated_data['created'], saved_polygon.created)
self.assertIsNone(serializer.validated_data['polygon'])

def test_validation_error_with_wrong_format(self):
"""
Passing data with the wrong format should cause validation error
"""
now = datetime.datetime.now()
serializer = PolygonSerializer(data={'created': now, 'polygon': '{[22,30], [23,31]}'})
self.assertFalse(serializer.is_valid())

def test_validation_error_with_none_closing_polygon(self):
"""
Passing data which the last point is not equal to the first point (causing an open polygon) should cause validation error
"""
polygon = [
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
]
]
now = datetime.datetime.now()
serializer = PolygonSerializer(data={'created': now, 'polygon': polygon})
self.assertFalse(serializer.is_valid())

def test_serialization(self):
"""
Regular JSON serialization should output float values
"""
from django.contrib.gis.geos import Polygon
now = datetime.datetime.now()
polygon = Polygon(
[
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
)

saved_polygon = SavePolygon(polygon=polygon, created=now)
serializer = PolygonSerializer(saved_polygon)

self.assertEqual(
serializer.data['polygon'],
[
[
51.778564453125,
35.59925232772949
],
[
50.1470947265625,
34.80929324176267
],
[
52.6080322265625,
34.492975402501536
],
[
51.778564453125,
35.59925232772949
]
]
)


class SavePoint(object):
def __init__(self, point=None, created=None):
self.point = point
self.created = created or datetime.datetime.now()


class PointSerializer(serializers.Serializer):
point = PointField(required=False)
created = serializers.DateTimeField()
Expand Down