Skip to content

Commit 46acc15

Browse files
authored
Merge pull request #161 from Cadasta/master
Flag to reduce precision of GeometryField
2 parents 0a27bcb + aff70a2 commit 46acc15

File tree

3 files changed

+641
-3
lines changed

3 files changed

+641
-3
lines changed

README.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ Provides a ``GeometryField``, which is a subclass of Django Rest Framework
7474
geometry fields, providing custom ``to_native`` and ``from_native``
7575
methods for GeoJSON input/output.
7676

77+
This field takes two optional arguments:
78+
79+
``precision``: Passes coordinates through Python's builtin ``round()`` function (`docs
80+
<https://docs.python.org/3/library/functions.html#round>`_), rounding values to
81+
the provided level of precision. E.g. A Point with lat/lng of
82+
``[51.0486, -114.0708]`` passed through a ``GeometryField(precision=2)``
83+
would return a Point with a lat/lng of ``[51.05, -114.07]``.
84+
85+
``remove_duplicates``: Remove sequential duplicate coordinates from line and
86+
polygon geometries. This is particularly useful when used with the ``precision``
87+
argument, as the likelihood of duplicate coordinates increase as precision of
88+
coordinates are reduced.
89+
90+
**Note:** While both above arguments are designed to reduce the
91+
byte size of the API response, they will also increase the processing time
92+
required to render the response. This will likely be negligible for small GeoJSON
93+
responses but may become an issue for large responses.
94+
7795
**New in 0.9.3:** there is no need to define this field explicitly in your serializer,
7896
it's mapped automatically during initialization in ``rest_framework_gis.apps.AppConfig.ready()``.
7997

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

96-
Provides a ``GeoModelSerializer``, which is a sublass of DRF
114+
Provides a ``GeoModelSerializer``, which is a subclass of DRF
97115
``ModelSerializer``. This serializer updates the field\_mapping
98116
dictionary to include field mapping of GeoDjango geometry fields to the
99117
above ``GeometryField``.

rest_framework_gis/fields.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,29 @@ class GeometryField(Field):
1818
"""
1919
type_name = 'GeometryField'
2020

21-
def __init__(self, **kwargs):
21+
def __init__(self, precision=None, remove_duplicates=False, **kwargs):
22+
self.precision = precision
23+
self.remove_dupes = remove_duplicates
2224
super(GeometryField, self).__init__(**kwargs)
2325
self.style = {'base_template': 'textarea.html'}
2426

2527
def to_representation(self, value):
2628
if isinstance(value, dict) or value is None:
2729
return value
2830
# we expect value to be a GEOSGeometry instance
29-
return GeoJsonDict(value.geojson)
31+
geojson = GeoJsonDict(value.geojson)
32+
if geojson['type'] == 'GeometryCollection':
33+
geometries = geojson.get('geometries')
34+
else:
35+
geometries = [geojson]
36+
for geometry in geometries:
37+
if self.precision is not None:
38+
geometry['coordinates'] = self._recursive_round(
39+
geometry['coordinates'], self.precision)
40+
if self.remove_dupes:
41+
geometry['coordinates'] = self._rm_redundant_points(
42+
geometry['coordinates'], geometry['type'])
43+
return geojson
3044

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

65+
def _recursive_round(self, value, precision):
66+
"""
67+
Round all numbers within an array or nested arrays
68+
value: number or nested array of numbers
69+
precision: integer valueue of number of decimals to keep
70+
"""
71+
if hasattr(value, '__iter__'):
72+
return tuple(self._recursive_round(v, precision) for v in value)
73+
return round(value, precision)
74+
75+
def _rm_redundant_points(self, geometry, geo_type):
76+
"""
77+
Remove redundant coordinate pairs from geometry
78+
geometry: array of coordinates or nested-array of coordinates
79+
geo_type: GeoJSON type attribute for provided geometry, used to
80+
determine structure of provided `geometry` argument
81+
"""
82+
if geo_type in ('MultiPoint', 'LineString'):
83+
close = (geo_type == 'LineString')
84+
output = []
85+
for coord in geometry:
86+
coord = tuple(coord)
87+
if not output or coord != output[-1]:
88+
output.append(coord)
89+
if close and len(output) == 1:
90+
output.append(output[0])
91+
return tuple(output)
92+
if geo_type in ('MultiLineString', 'Polygon'):
93+
return [
94+
self._rm_redundant_points(c, 'LineString') for c in geometry]
95+
if geo_type == 'MultiPolygon':
96+
return [self._rm_redundant_points(c, 'Polygon') for c in geometry]
97+
return geometry
98+
5199

52100
class GeometrySerializerMethodField(SerializerMethodField):
53101
def to_representation(self, value):

0 commit comments

Comments
 (0)