Navigation Menu

Skip to content

Commit

Permalink
Add flags to reduce precision and remove duplicate points
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Lukach committed Apr 18, 2018
1 parent daaf0cc commit aff70a2
Show file tree
Hide file tree
Showing 3 changed files with 641 additions and 3 deletions.
20 changes: 19 additions & 1 deletion README.rst
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
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

0 comments on commit aff70a2

Please sign in to comment.