Commit aff70a26 authored by Anthony Lukach's avatar Anthony Lukach

Add flags to reduce precision and remove duplicate points

parent daaf0ccd
......@@ -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()``.
......@@ -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``.
......
......@@ -18,7 +18,9 @@ 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'}
......@@ -26,7 +28,19 @@ class GeometryField(Field):
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:
......@@ -48,6 +62,40 @@ class GeometryField(Field):
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):
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment