summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Fedoseev <fedoseev.sergey@gmail.com>2015-11-24 14:21:32 +0500
committerTim Graham <timograham@gmail.com>2015-12-14 13:29:38 -0500
commit5146e2cf984a83c2eb9d8102ea73ee0792a9528b (patch)
treeb464f59550d16d03a227eed0aab7adeb6dbcfca9
parent8035cee92293f3319919c8248c7787ba43c05917 (diff)
Fixed #25662 -- Allowed creation of empty GEOS geometries.
-rw-r--r--django/contrib/gis/geos/collections.py3
-rw-r--r--django/contrib/gis/geos/linestring.py10
-rw-r--r--django/contrib/gis/geos/mutable_list.py2
-rw-r--r--django/contrib/gis/geos/point.py14
-rw-r--r--django/contrib/gis/geos/polygon.py6
-rw-r--r--django/contrib/gis/geos/prototypes/__init__.py11
-rw-r--r--django/contrib/gis/geos/prototypes/geom.py1
-rw-r--r--docs/ref/contrib/gis/geos.txt75
-rw-r--r--docs/releases/1.10.txt2
-rw-r--r--tests/gis_tests/geos_tests/test_geos.py57
-rw-r--r--tests/gis_tests/geos_tests/test_mutable_list.py8
11 files changed, 140 insertions, 49 deletions
diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py
index 0f27d2bcf9..becc6b0ce3 100644
--- a/django/contrib/gis/geos/collections.py
+++ b/django/contrib/gis/geos/collections.py
@@ -25,9 +25,6 @@ class GeometryCollection(GEOSGeometry):
"Initializes a Geometry Collection from a sequence of Geometry objects."
# Checking the arguments
- if not args:
- raise TypeError('Must provide at least one Geometry to initialize %s.' % self.__class__.__name__)
-
if len(args) == 1:
# If only one geometry provided or a list of geometries is provided
# in the first argument.
diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py
index d0edd631ce..cf74aad7fa 100644
--- a/django/contrib/gis/geos/linestring.py
+++ b/django/contrib/gis/geos/linestring.py
@@ -35,7 +35,14 @@ class LineString(ProjectInterpolateMixin, GEOSGeometry):
if not (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)):
raise TypeError('Invalid initialization input for LineStrings.')
+ # If SRID was passed in with the keyword arguments
+ srid = kwargs.get('srid')
+
ncoords = len(coords)
+ if not ncoords:
+ super(LineString, self).__init__(self._init_func(None), srid=srid)
+ return
+
if ncoords < self._minlength:
raise ValueError(
'%s requires at least %d points, got %s.' % (
@@ -80,9 +87,6 @@ class LineString(ProjectInterpolateMixin, GEOSGeometry):
else:
cs[i] = coords[i]
- # If SRID was passed in with the keyword arguments
- srid = kwargs.get('srid')
-
# Calling the base geometry initialization with the returned pointer
# from the function.
super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)
diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py
index 3fe9a4fbb6..8a613600c0 100644
--- a/django/contrib/gis/geos/mutable_list.py
+++ b/django/contrib/gis/geos/mutable_list.py
@@ -229,7 +229,7 @@ class ListMixin(object):
# ### Private routines ###
def _rebuild(self, newLen, newItems):
- if newLen < self._minlength:
+ if newLen and newLen < self._minlength:
raise ValueError('Must have at least %d items' % self._minlength)
if self._maxlength is not None and newLen > self._maxlength:
raise ValueError('Cannot have more than %d items' % self._maxlength)
diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py
index 34a955e924..46972629d8 100644
--- a/django/contrib/gis/geos/point.py
+++ b/django/contrib/gis/geos/point.py
@@ -14,7 +14,7 @@ class Point(GEOSGeometry):
_maxlength = 3
has_cs = True
- def __init__(self, x, y=None, z=None, srid=None):
+ def __init__(self, x=None, y=None, z=None, srid=None):
"""
The Point object may be initialized with either a tuple, or individual
parameters.
@@ -23,22 +23,21 @@ class Point(GEOSGeometry):
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
"""
- if isinstance(x, (tuple, list)):
+ if x is None:
+ coords = []
+ elif isinstance(x, (tuple, list)):
# Here a tuple or list was passed in under the `x` parameter.
- ndim = len(x)
coords = x
elif isinstance(x, six.integer_types + (float,)) and isinstance(y, six.integer_types + (float,)):
# Here X, Y, and (optionally) Z were passed in individually, as parameters.
if isinstance(z, six.integer_types + (float,)):
- ndim = 3
coords = [x, y, z]
else:
- ndim = 2
coords = [x, y]
else:
raise TypeError('Invalid parameters given for Point initialization.')
- point = self._create_point(ndim, coords)
+ point = self._create_point(len(coords), coords)
# Initializing using the address returned from the GEOS
# createPoint factory.
@@ -48,6 +47,9 @@ class Point(GEOSGeometry):
"""
Create a coordinate sequence, set X, Y, [Z], and create point
"""
+ if not ndim:
+ return capi.create_point(None)
+
if ndim < 2 or ndim > 3:
raise TypeError('Invalid point dimension: %s' % str(ndim))
diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py
index fdf7515bed..067730063f 100644
--- a/django/contrib/gis/geos/polygon.py
+++ b/django/contrib/gis/geos/polygon.py
@@ -29,7 +29,8 @@ class Polygon(GEOSGeometry):
... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
"""
if not args:
- raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
+ super(Polygon, self).__init__(self._create_polygon(0, None), **kwargs)
+ return
# Getting the ext_ring and init_holes parameters from the argument list
ext_ring = args[0]
@@ -73,6 +74,9 @@ class Polygon(GEOSGeometry):
# _construct_ring will throw a TypeError if a parameter isn't a valid ring
# If we cloned the pointers here, we wouldn't be able to clean up
# in case of error.
+ if not length:
+ return capi.create_empty_polygon()
+
rings = []
for r in items:
if isinstance(r, GEOM_PTR):
diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py
index 595555897d..32073749d8 100644
--- a/django/contrib/gis/geos/prototypes/__init__.py
+++ b/django/contrib/gis/geos/prototypes/__init__.py
@@ -9,11 +9,12 @@ from django.contrib.gis.geos.prototypes.coordseq import ( # NOQA
cs_gety, cs_getz, cs_setordinate, cs_setx, cs_sety, cs_setz, get_cs,
)
from django.contrib.gis.geos.prototypes.geom import ( # NOQA
- create_collection, create_linearring, create_linestring, create_point,
- create_polygon, destroy_geom, from_hex, from_wkb, from_wkt, geom_clone,
- geos_get_srid, geos_normalize, geos_set_srid, geos_type, geos_typeid,
- get_dims, get_extring, get_geomn, get_intring, get_nrings, get_num_coords,
- get_num_geoms, to_hex, to_wkb, to_wkt,
+ create_collection, create_empty_polygon, create_linearring,
+ create_linestring, create_point, create_polygon, destroy_geom, from_hex,
+ from_wkb, from_wkt, geom_clone, geos_get_srid, geos_normalize,
+ geos_set_srid, geos_type, geos_typeid, get_dims, get_extring, get_geomn,
+ get_intring, get_nrings, get_num_coords, get_num_geoms, to_hex, to_wkb,
+ to_wkt,
)
from django.contrib.gis.geos.prototypes.misc import * # NOQA
from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py
index e4bd0b0a51..9e14f214dc 100644
--- a/django/contrib/gis/geos/prototypes/geom.py
+++ b/django/contrib/gis/geos/prototypes/geom.py
@@ -94,6 +94,7 @@ create_linearring = GeomOutput('GEOSGeom_createLinearRing', [CS_PTR])
# Polygon and collection creation routines are special and will not
# have their argument types defined.
create_polygon = GeomOutput('GEOSGeom_createPolygon', None)
+create_empty_polygon = GeomOutput('GEOSGeom_createEmptyPolygon', None)
create_collection = GeomOutput('GEOSGeom_createCollection', None)
# Ring routines
diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt
index e53acbc99c..74fb2e5e54 100644
--- a/docs/ref/contrib/gis/geos.txt
+++ b/docs/ref/contrib/gis/geos.txt
@@ -647,7 +647,7 @@ is returned instead.
``Point``
---------
-.. class:: Point(x, y, z=None, srid=None)
+.. class:: Point(x=None, y=None, z=None, srid=None)
``Point`` objects are instantiated using arguments that represent
the component coordinates of the point or with a single sequence
@@ -656,6 +656,16 @@ is returned instead.
>>> pnt = Point(5, 23)
>>> pnt = Point([5, 23])
+ Empty ``Point`` objects may be instantiated by passing no arguments or an
+ empty sequence. The following are equivalent::
+
+ >>> pnt = Point()
+ >>> pnt = Point([])
+
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``Point`` couldn't be instantiated.
+
``LineString``
--------------
@@ -674,6 +684,16 @@ is returned instead.
>>> ls = LineString( ((0, 0), (1, 1)) )
>>> ls = LineString( [Point(0, 0), Point(1, 1)] )
+ Empty ``LineString`` objects may be instantiated by passing no arguments
+ or an empty sequence. The following are equivalent::
+
+ >>> ls = LineString()
+ >>> ls = LineString([])
+
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``LineString`` couldn't be instantiated.
+
``LinearRing``
--------------
@@ -694,16 +714,20 @@ is returned instead.
.. class:: Polygon(*args, **kwargs)
- ``Polygon`` objects may be instantiated by passing in one or
- more parameters that represent the rings of the polygon. The
- parameters must either be :class:`LinearRing` instances, or
- a sequence that may be used to construct a :class:`LinearRing`::
+ ``Polygon`` objects may be instantiated by passing in parameters that
+ represent the rings of the polygon. The parameters must either be
+ :class:`LinearRing` instances, or a sequence that may be used to construct a
+ :class:`LinearRing`::
>>> ext_coords = ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))
>>> int_coords = ((0.4, 0.4), (0.4, 0.6), (0.6, 0.6), (0.6, 0.4), (0.4, 0.4))
>>> poly = Polygon(ext_coords, int_coords)
>>> poly = Polygon(LinearRing(ext_coords), LinearRing(int_coords))
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``Polygon`` couldn't be instantiated.
+
.. classmethod:: from_bbox(bbox)
Returns a polygon object from the given bounding-box, a 4-tuple
@@ -732,27 +756,35 @@ Geometry Collections
.. class:: MultiPoint(*args, **kwargs)
- ``MultiPoint`` objects may be instantiated by passing in one
- or more :class:`Point` objects as arguments, or a single
- sequence of :class:`Point` objects::
+ ``MultiPoint`` objects may be instantiated by passing in :class:`Point`
+ objects as arguments, or a single sequence of :class:`Point` objects::
>>> mp = MultiPoint(Point(0, 0), Point(1, 1))
>>> mp = MultiPoint( (Point(0, 0), Point(1, 1)) )
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``MultiPoint`` couldn't be instantiated.
+
``MultiLineString``
-------------------
.. class:: MultiLineString(*args, **kwargs)
- ``MultiLineString`` objects may be instantiated by passing in one
- or more :class:`LineString` objects as arguments, or a single
- sequence of :class:`LineString` objects::
+ ``MultiLineString`` objects may be instantiated by passing in
+ :class:`LineString` objects as arguments, or a single sequence of
+ :class:`LineString` objects::
>>> ls1 = LineString((0, 0), (1, 1))
>>> ls2 = LineString((2, 2), (3, 3))
>>> mls = MultiLineString(ls1, ls2)
>>> mls = MultiLineString([ls1, ls2])
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``MultiLineString`` couldn't be
+ instantiated.
+
.. attribute:: merged
Returns a :class:`LineString` representing the line merge of
@@ -764,15 +796,19 @@ Geometry Collections
.. class:: MultiPolygon(*args, **kwargs)
- ``MultiPolygon`` objects may be instantiated by passing one or
- more :class:`Polygon` objects as arguments, or a single sequence
- of :class:`Polygon` objects::
+ ``MultiPolygon`` objects may be instantiated by passing :class:`Polygon`
+ objects as arguments, or a single sequence of :class:`Polygon` objects::
>>> p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
>>> p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) )
>>> mp = MultiPolygon(p1, p2)
>>> mp = MultiPolygon([p1, p2])
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``MultiPolygon`` couldn't be
+ instantiated.
+
.. attribute:: cascaded_union
.. deprecated:: 1.10
@@ -789,14 +825,19 @@ Geometry Collections
.. class:: GeometryCollection(*args, **kwargs)
- ``GeometryCollection`` objects may be instantiated by passing in
- one or more other :class:`GEOSGeometry` as arguments, or a single
- sequence of :class:`GEOSGeometry` objects::
+ ``GeometryCollection`` objects may be instantiated by passing in other
+ :class:`GEOSGeometry` as arguments, or a single sequence of
+ :class:`GEOSGeometry` objects::
>>> poly = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
>>> gc = GeometryCollection(Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly)
>>> gc = GeometryCollection((Point(0, 0), MultiPoint(Point(0, 0), Point(1, 1)), poly))
+ .. versionchanged:: 1.10
+
+ In previous versions, an empty ``GeometryCollection`` couldn't be
+ instantiated.
+
.. _prepared-geometries:
Prepared Geometries
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index d932887a51..853955f936 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -92,6 +92,8 @@ Minor features
:class:`~django.contrib.gis.db.models.functions.SymDifference`
functions on MySQL.
+* Added support for instantiating empty GEOS geometries.
+
:mod:`django.contrib.messages`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py
index da9db06c33..fd08e3037a 100644
--- a/tests/gis_tests/geos_tests/test_geos.py
+++ b/tests/gis_tests/geos_tests/test_geos.py
@@ -793,6 +793,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
p[:] = (1, 2, 3)
self.assertEqual(p, Point(1, 2, 3))
+ p[:] = ()
+ self.assertEqual(p.wkt, Point())
+
p[:] = (1, 2)
self.assertEqual(p.wkt, Point(1, 2))
@@ -804,6 +807,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
def test_linestring_list_assignment(self):
ls = LineString((0, 0), (1, 1))
+ ls[:] = ()
+ self.assertEqual(ls, LineString())
+
ls[:] = ((0, 0), (1, 1), (2, 2))
self.assertEqual(ls, LineString((0, 0), (1, 1), (2, 2)))
@@ -813,12 +819,34 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
def test_linearring_list_assignment(self):
ls = LinearRing((0, 0), (0, 1), (1, 1), (0, 0))
+ ls[:] = ()
+ self.assertEqual(ls, LinearRing())
+
ls[:] = ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))
self.assertEqual(ls, LinearRing((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
with self.assertRaises(ValueError):
ls[:] = ((0, 0), (1, 1), (2, 2))
+ def test_polygon_list_assignment(self):
+ pol = Polygon()
+
+ pol[:] = (((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)),)
+ self.assertEqual(pol, Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)),))
+
+ pol[:] = ()
+ self.assertEqual(pol, Polygon())
+
+ def test_geometry_collection_list_assignment(self):
+ p = Point()
+ gc = GeometryCollection()
+
+ gc[:] = [p]
+ self.assertEqual(gc, GeometryCollection(p))
+
+ gc[:] = ()
+ self.assertEqual(gc, GeometryCollection())
+
def test_threed(self):
"Testing three-dimensional geometries."
# Testing a 3D Point
@@ -874,16 +902,27 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
def test_emptyCollections(self):
"Testing empty geometries and collections."
- gc1 = GeometryCollection([])
- gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
- pnt = fromstr('POINT EMPTY')
- ls = fromstr('LINESTRING EMPTY')
- poly = fromstr('POLYGON EMPTY')
- mls = fromstr('MULTILINESTRING EMPTY')
- mpoly1 = fromstr('MULTIPOLYGON EMPTY')
- mpoly2 = MultiPolygon(())
+ geoms = [
+ GeometryCollection([]),
+ fromstr('GEOMETRYCOLLECTION EMPTY'),
+ GeometryCollection(),
+ fromstr('POINT EMPTY'),
+ Point(),
+ fromstr('LINESTRING EMPTY'),
+ LineString(),
+ fromstr('POLYGON EMPTY'),
+ Polygon(),
+ fromstr('MULTILINESTRING EMPTY'),
+ MultiLineString(),
+ fromstr('MULTIPOLYGON EMPTY'),
+ MultiPolygon(()),
+ MultiPolygon(),
+ ]
+
+ if numpy:
+ geoms.append(LineString(numpy.array([])))
- for g in [gc1, gc2, pnt, ls, poly, mls, mpoly1, mpoly2]:
+ for g in geoms:
self.assertEqual(True, g.empty)
# Testing len() and num_geom.
diff --git a/tests/gis_tests/geos_tests/test_mutable_list.py b/tests/gis_tests/geos_tests/test_mutable_list.py
index 67338ee865..8f2242ea31 100644
--- a/tests/gis_tests/geos_tests/test_mutable_list.py
+++ b/tests/gis_tests/geos_tests/test_mutable_list.py
@@ -299,18 +299,18 @@ class ListMixinTest(unittest.TestCase):
def test08_min_length(self):
'Length limits'
- pl, ul = self.lists_of_len()
- ul._minlength = 1
+ pl, ul = self.lists_of_len(5)
+ ul._minlength = 3
def delfcn(x, i):
del x[:i]
def setfcn(x, i):
x[:i] = []
- for i in range(self.limit - ul._minlength + 1, self.limit + 1):
+ for i in range(len(ul) - ul._minlength + 1, len(ul)):
self.assertRaises(ValueError, delfcn, ul, i)
self.assertRaises(ValueError, setfcn, ul, i)
- del ul[:ul._minlength]
+ del ul[:len(ul) - ul._minlength]
ul._maxlength = 4
for i in range(0, ul._maxlength - len(ul)):