Skip to content

Commit

Permalink
Merge pull request #161 from Cadasta/master
Browse files Browse the repository at this point in the history
Flag to reduce precision of GeometryField
  • Loading branch information
nemesifier authored Apr 27, 2018
2 parents 0a27bcb + aff70a2 commit 46acc15
Show file tree
Hide file tree
Showing 3 changed files with 641 additions and 3 deletions.
20 changes: 19 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ Provides a ``GeometryField``, which is a subclass of Django Rest Framework
geometry fields, providing custom ``to_native`` and ``from_native``
methods for GeoJSON input/output.

This field takes two optional arguments:

``precision``: Passes coordinates through Python's builtin ``round()`` function (`docs
<https://docs.python.org/3/library/functions.html#round>`_), rounding values to
the provided level of precision. E.g. A Point with lat/lng of
``[51.0486, -114.0708]`` passed through a ``GeometryField(precision=2)``
would return a Point with a lat/lng of ``[51.05, -114.07]``.

``remove_duplicates``: Remove sequential duplicate coordinates from line and
polygon geometries. This is particularly useful when used with the ``precision``
argument, as the likelihood of duplicate coordinates increase as precision of
coordinates are reduced.

**Note:** While both above arguments are designed to reduce the
byte size of the API response, they will also increase the processing time
required to render the response. This will likely be negligible for small GeoJSON
responses but may become an issue for large responses.

**New in 0.9.3:** there is no need to define this field explicitly in your serializer,
it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``.

Expand All @@ -93,7 +111,7 @@ GeoModelSerializer (DEPRECATED)
**Deprecated, will be removed in 1.0**: Using this serializer is not needed anymore since 0.9.3 if you add
``rest_framework_gis`` in ``settings.INSTALLED_APPS``

Provides a ``GeoModelSerializer``, which is a sublass of DRF
Provides a ``GeoModelSerializer``, which is a subclass of DRF
``ModelSerializer``. This serializer updates the field\_mapping
dictionary to include field mapping of GeoDjango geometry fields to the
above ``GeometryField``.
Expand Down
52 changes: 50 additions & 2 deletions rest_framework_gis/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,29 @@ class GeometryField(Field):
"""
type_name = 'GeometryField'

def __init__(self, **kwargs):
def __init__(self, precision=None, remove_duplicates=False, **kwargs):
self.precision = precision
self.remove_dupes = remove_duplicates
super(GeometryField, self).__init__(**kwargs)
self.style = {'base_template': 'textarea.html'}

def to_representation(self, value):
if isinstance(value, dict) or value is None:
return value
# we expect value to be a GEOSGeometry instance
return GeoJsonDict(value.geojson)
geojson = GeoJsonDict(value.geojson)
if geojson['type'] == 'GeometryCollection':
geometries = geojson.get('geometries')
else:
geometries = [geojson]
for geometry in geometries:
if self.precision is not None:
geometry['coordinates'] = self._recursive_round(
geometry['coordinates'], self.precision)
if self.remove_dupes:
geometry['coordinates'] = self._rm_redundant_points(
geometry['coordinates'], geometry['type'])
return geojson

def to_internal_value(self, value):
if value == '' or value is None:
Expand All @@ -48,6 +62,40 @@ def validate_empty_values(self, data):
self.fail('required')
return super(GeometryField, self).validate_empty_values(data)

def _recursive_round(self, value, precision):
"""
Round all numbers within an array or nested arrays
value: number or nested array of numbers
precision: integer valueue of number of decimals to keep
"""
if hasattr(value, '__iter__'):
return tuple(self._recursive_round(v, precision) for v in value)
return round(value, precision)

def _rm_redundant_points(self, geometry, geo_type):
"""
Remove redundant coordinate pairs from geometry
geometry: array of coordinates or nested-array of coordinates
geo_type: GeoJSON type attribute for provided geometry, used to
determine structure of provided `geometry` argument
"""
if geo_type in ('MultiPoint', 'LineString'):
close = (geo_type == 'LineString')
output = []
for coord in geometry:
coord = tuple(coord)
if not output or coord != output[-1]:
output.append(coord)
if close and len(output) == 1:
output.append(output[0])
return tuple(output)
if geo_type in ('MultiLineString', 'Polygon'):
return [
self._rm_redundant_points(c, 'LineString') for c in geometry]
if geo_type == 'MultiPolygon':
return [self._rm_redundant_points(c, 'Polygon') for c in geometry]
return geometry


class GeometrySerializerMethodField(SerializerMethodField):
def to_representation(self, value):
Expand Down
Loading

0 comments on commit 46acc15

Please sign in to comment.