diff --git a/AUTHORS b/AUTHORS index 58df5eacbf..fb32b97887 100644 --- a/AUTHORS +++ b/AUTHORS @@ -752,6 +752,7 @@ answer newbie questions, and generally made Django that much better: Srinivas Reddy Thatiparthy Stanislas Guerra Stanislaus Madueke + Stanislav Karpov starrynight Stefane Fermgier Stefano Rivera diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 1ebecc864b..80770dfa6b 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -499,6 +499,18 @@ class GEOSGeometryBase(GEOSBase): """ return self._topology(capi.geos_buffer(self.ptr, width, quadsegs)) + def buffer_with_style(self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0): + """ + Same as buffer() but allows customizing the style of the buffer. + + End cap style can be round (1), flat (2), or square (3). + Join style can be round (1), mitre (2), or bevel (3). + Mitre ratio limit only affects mitered join style. + """ + return self._topology( + capi.geos_bufferwithstyle(self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit), + ) + @property def centroid(self): """ diff --git a/django/contrib/gis/geos/prototypes/topology.py b/django/contrib/gis/geos/prototypes/topology.py index f8c36bc6f5..9ca0dce695 100644 --- a/django/contrib/gis/geos/prototypes/topology.py +++ b/django/contrib/gis/geos/prototypes/topology.py @@ -21,6 +21,7 @@ class Topology(GEOSFuncFactory): # Topology Routines geos_boundary = Topology('GEOSBoundary') geos_buffer = Topology('GEOSBuffer', argtypes=[GEOM_PTR, c_double, c_int]) +geos_bufferwithstyle = Topology('GEOSBufferWithStyle', argtypes=[GEOM_PTR, c_double, c_int, c_int, c_int, c_double]) geos_centroid = Topology('GEOSGetCentroid') geos_convexhull = Topology('GEOSConvexHull') geos_difference = Topology('GEOSDifference', argtypes=[GEOM_PTR, GEOM_PTR]) diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 6334f76598..e79e621239 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -496,6 +496,16 @@ Topological Methods optional ``quadsegs`` keyword sets the number of segments used to approximate a quarter circle (defaults is 8). +.. method:: GEOSGeometry.buffer_with_style(width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0) + + .. versionadded:: 2.1 + + Same as :meth:`buffer`, but allows customizing the style of the buffer. + + * ``end_cap_style`` can be round (``1``), flat (``2``), or square (``3``). + * ``join_style`` can be round (``1``), mitre (``2``), or bevel (``3``). + * Mitre ratio limit (``mitre_limit``) only affects mitered join style. + .. method:: GEOSGeometry.difference(other) Returns a :class:`GEOSGeometry` representing the points making up this diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 31252432bb..33d6982443 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -70,7 +70,9 @@ Minor features :mod:`django.contrib.gis` ~~~~~~~~~~~~~~~~~~~~~~~~~ -* ... +* The new :meth:`.GEOSGeometry.buffer_with_style` method is a version of + :meth:`~.GEOSGeometry.buffer` that allows customizing the style of the + buffer. :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 4d5c9a508f..57b999bacb 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -407,6 +407,7 @@ minified minify mis misconfiguration +mitre mixin mixins modelforms diff --git a/tests/gis_tests/data/geometries.json b/tests/gis_tests/data/geometries.json index b8b1c3eab3..7786f26e9b 100644 --- a/tests/gis_tests/data/geometries.json +++ b/tests/gis_tests/data/geometries.json @@ -78,6 +78,24 @@ "width": 2.0, "quadsegs": 8 } ], + "buffer_with_style_geoms": [ + {"wkt": "POINT (0 0)", + "buffer_wkt": "POLYGON EMPTY", + "width": 5.0, "end_cap_style": 2, "join_style": 2 + }, + {"wkt": "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", + "buffer_wkt": "POLYGON ((-2 -2, -2 12, 12 12, 12 -2, -2 -2))", + "width": 2.0, "end_cap_style": 2, "join_style": 2 + }, + {"wkt": "LINESTRING (0 0, 10 0)", + "buffer_wkt": "POLYGON ((10 2, 10 -2, 0 -2, 0 2, 10 2))", + "width": 2.0, "end_cap_style": 2, "join_style": 2 + }, + {"wkt": "LINESTRING (0 0, 10 0, 10 10, 0 10)", + "buffer_wkt": "POLYGON ((8 2, 8 8, 0 8, 0 12, 12 12, 12 -2, 0 -2, 0 2, 8 2))", + "width": 2.0, "end_cap_style": 2, "join_style": 2 + } + ], "relate_geoms": [ {"wkt_a": "MULTIPOINT(80 70, 20 20, 200 170, 140 120)", "wkt_b": "MULTIPOINT(80 170, 140 120, 200 80, 80 70)", diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index 7488acbb3e..5f8d1c84b1 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -1,4 +1,5 @@ import ctypes +import itertools import json import pickle import random @@ -650,21 +651,56 @@ class GEOSTest(SimpleTestCase, TestDataMixin): self.assertEqual(d1, a) def test_buffer(self): - "Testing buffer()." - for bg in self.geometries.buffer_geoms: + bg = self.geometries.buffer_geoms[0] + g = fromstr(bg.wkt) + + # Can't use a floating-point for the number of quadsegs. + with self.assertRaises(ctypes.ArgumentError): + g.buffer(bg.width, quadsegs=1.1) + + self._test_buffer(self.geometries.buffer_geoms, 'buffer') + + def test_buffer_with_style(self): + bg = self.geometries.buffer_with_style_geoms[0] + g = fromstr(bg.wkt) + + # Can't use a floating-point for the number of quadsegs. + with self.assertRaises(ctypes.ArgumentError): + g.buffer_with_style(bg.width, quadsegs=1.1) + + # Can't use a floating-point for the end cap style. + with self.assertRaises(ctypes.ArgumentError): + g.buffer_with_style(bg.width, end_cap_style=1.2) + # Can't use a end cap style that is not in the enum. + with self.assertRaises(GEOSException): + g.buffer_with_style(bg.width, end_cap_style=55) + + # Can't use a floating-point for the join style. + with self.assertRaises(ctypes.ArgumentError): + g.buffer_with_style(bg.width, join_style=1.3) + # Can't use a join style that is not in the enum. + with self.assertRaises(GEOSException): + g.buffer_with_style(bg.width, join_style=66) + + self._test_buffer( + itertools.chain(self.geometries.buffer_geoms, self.geometries.buffer_with_style_geoms), + 'buffer_with_style', + ) + + def _test_buffer(self, geometries, buffer_method_name): + for bg in geometries: g = fromstr(bg.wkt) # The buffer we expect exp_buf = fromstr(bg.buffer_wkt) - quadsegs = bg.quadsegs - width = bg.width - - # Can't use a floating-point for the number of quadsegs. - with self.assertRaises(ctypes.ArgumentError): - g.buffer(width, float(quadsegs)) # Constructing our buffer - buf = g.buffer(width, quadsegs) + buf_kwargs = { + kwarg_name: getattr(bg, kwarg_name) + for kwarg_name in ('width', 'quadsegs', 'end_cap_style', 'join_style', 'mitre_limit') + if hasattr(bg, kwarg_name) + } + buf = getattr(g, buffer_method_name)(**buf_kwargs) self.assertEqual(exp_buf.num_coords, buf.num_coords) self.assertEqual(len(exp_buf), len(buf))