Fixed #25662 -- Allowed creation of empty GEOS geometries.

This commit is contained in:
Sergey Fedoseev 2015-11-24 14:21:32 +05:00 committed by Tim Graham
parent 8035cee922
commit 5146e2cf98
11 changed files with 140 additions and 49 deletions

View File

@ -25,9 +25,6 @@ class GeometryCollection(GEOSGeometry):
"Initializes a Geometry Collection from a sequence of Geometry objects." "Initializes a Geometry Collection from a sequence of Geometry objects."
# Checking the arguments # 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 len(args) == 1:
# If only one geometry provided or a list of geometries is provided # If only one geometry provided or a list of geometries is provided
# in the first argument. # in the first argument.

View File

@ -35,7 +35,14 @@ class LineString(ProjectInterpolateMixin, GEOSGeometry):
if not (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)): if not (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)):
raise TypeError('Invalid initialization input for LineStrings.') raise TypeError('Invalid initialization input for LineStrings.')
# If SRID was passed in with the keyword arguments
srid = kwargs.get('srid')
ncoords = len(coords) ncoords = len(coords)
if not ncoords:
super(LineString, self).__init__(self._init_func(None), srid=srid)
return
if ncoords < self._minlength: if ncoords < self._minlength:
raise ValueError( raise ValueError(
'%s requires at least %d points, got %s.' % ( '%s requires at least %d points, got %s.' % (
@ -80,9 +87,6 @@ class LineString(ProjectInterpolateMixin, GEOSGeometry):
else: else:
cs[i] = coords[i] 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 # Calling the base geometry initialization with the returned pointer
# from the function. # from the function.
super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid) super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)

View File

@ -229,7 +229,7 @@ class ListMixin(object):
# ### Private routines ### # ### Private routines ###
def _rebuild(self, newLen, newItems): 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) raise ValueError('Must have at least %d items' % self._minlength)
if self._maxlength is not None and newLen > self._maxlength: if self._maxlength is not None and newLen > self._maxlength:
raise ValueError('Cannot have more than %d items' % self._maxlength) raise ValueError('Cannot have more than %d items' % self._maxlength)

View File

@ -14,7 +14,7 @@ class Point(GEOSGeometry):
_maxlength = 3 _maxlength = 3
has_cs = True 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 The Point object may be initialized with either a tuple, or individual
parameters. parameters.
@ -23,22 +23,21 @@ class Point(GEOSGeometry):
>>> p = Point((5, 23)) # 2D point, passed in as a tuple >>> p = Point((5, 23)) # 2D point, passed in as a tuple
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters >>> 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. # Here a tuple or list was passed in under the `x` parameter.
ndim = len(x)
coords = x coords = x
elif isinstance(x, six.integer_types + (float,)) and isinstance(y, six.integer_types + (float,)): 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. # Here X, Y, and (optionally) Z were passed in individually, as parameters.
if isinstance(z, six.integer_types + (float,)): if isinstance(z, six.integer_types + (float,)):
ndim = 3
coords = [x, y, z] coords = [x, y, z]
else: else:
ndim = 2
coords = [x, y] coords = [x, y]
else: else:
raise TypeError('Invalid parameters given for Point initialization.') 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 # Initializing using the address returned from the GEOS
# createPoint factory. # createPoint factory.
@ -48,6 +47,9 @@ class Point(GEOSGeometry):
""" """
Create a coordinate sequence, set X, Y, [Z], and create point 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: if ndim < 2 or ndim > 3:
raise TypeError('Invalid point dimension: %s' % str(ndim)) raise TypeError('Invalid point dimension: %s' % str(ndim))

View File

@ -29,7 +29,8 @@ class Polygon(GEOSGeometry):
... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) ... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
""" """
if not args: 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 # Getting the ext_ring and init_holes parameters from the argument list
ext_ring = args[0] 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 # _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 # If we cloned the pointers here, we wouldn't be able to clean up
# in case of error. # in case of error.
if not length:
return capi.create_empty_polygon()
rings = [] rings = []
for r in items: for r in items:
if isinstance(r, GEOM_PTR): if isinstance(r, GEOM_PTR):

View File

@ -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, cs_gety, cs_getz, cs_setordinate, cs_setx, cs_sety, cs_setz, get_cs,
) )
from django.contrib.gis.geos.prototypes.geom import ( # NOQA from django.contrib.gis.geos.prototypes.geom import ( # NOQA
create_collection, create_linearring, create_linestring, create_point, create_collection, create_empty_polygon, create_linearring,
create_polygon, destroy_geom, from_hex, from_wkb, from_wkt, geom_clone, create_linestring, create_point, create_polygon, destroy_geom, from_hex,
geos_get_srid, geos_normalize, geos_set_srid, geos_type, geos_typeid, from_wkb, from_wkt, geom_clone, geos_get_srid, geos_normalize,
get_dims, get_extring, get_geomn, get_intring, get_nrings, get_num_coords, geos_set_srid, geos_type, geos_typeid, get_dims, get_extring, get_geomn,
get_num_geoms, to_hex, to_wkb, to_wkt, 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.misc import * # NOQA
from django.contrib.gis.geos.prototypes.predicates import ( # NOQA from django.contrib.gis.geos.prototypes.predicates import ( # NOQA

View File

@ -94,6 +94,7 @@ create_linearring = GeomOutput('GEOSGeom_createLinearRing', [CS_PTR])
# Polygon and collection creation routines are special and will not # Polygon and collection creation routines are special and will not
# have their argument types defined. # have their argument types defined.
create_polygon = GeomOutput('GEOSGeom_createPolygon', None) create_polygon = GeomOutput('GEOSGeom_createPolygon', None)
create_empty_polygon = GeomOutput('GEOSGeom_createEmptyPolygon', None)
create_collection = GeomOutput('GEOSGeom_createCollection', None) create_collection = GeomOutput('GEOSGeom_createCollection', None)
# Ring routines # Ring routines

View File

@ -647,7 +647,7 @@ is returned instead.
``Point`` ``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 ``Point`` objects are instantiated using arguments that represent
the component coordinates of the point or with a single sequence 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)
>>> 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`` ``LineString``
-------------- --------------
@ -674,6 +684,16 @@ is returned instead.
>>> ls = LineString( ((0, 0), (1, 1)) ) >>> ls = LineString( ((0, 0), (1, 1)) )
>>> ls = LineString( [Point(0, 0), Point(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`` ``LinearRing``
-------------- --------------
@ -694,16 +714,20 @@ is returned instead.
.. class:: Polygon(*args, **kwargs) .. class:: Polygon(*args, **kwargs)
``Polygon`` objects may be instantiated by passing in one or ``Polygon`` objects may be instantiated by passing in parameters that
more parameters that represent the rings of the polygon. The represent the rings of the polygon. The parameters must either be
parameters must either be :class:`LinearRing` instances, or :class:`LinearRing` instances, or a sequence that may be used to construct a
a sequence that may be used to construct a :class:`LinearRing`:: :class:`LinearRing`::
>>> ext_coords = ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)) >>> 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)) >>> 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(ext_coords, int_coords)
>>> poly = Polygon(LinearRing(ext_coords), LinearRing(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) .. classmethod:: from_bbox(bbox)
Returns a polygon object from the given bounding-box, a 4-tuple Returns a polygon object from the given bounding-box, a 4-tuple
@ -732,27 +756,35 @@ Geometry Collections
.. class:: MultiPoint(*args, **kwargs) .. class:: MultiPoint(*args, **kwargs)
``MultiPoint`` objects may be instantiated by passing in one ``MultiPoint`` objects may be instantiated by passing in :class:`Point`
or more :class:`Point` objects as arguments, or a single objects as arguments, or a single sequence of :class:`Point` objects::
sequence of :class:`Point` objects::
>>> mp = MultiPoint(Point(0, 0), Point(1, 1)) >>> mp = MultiPoint(Point(0, 0), Point(1, 1))
>>> 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`` ``MultiLineString``
------------------- -------------------
.. class:: MultiLineString(*args, **kwargs) .. class:: MultiLineString(*args, **kwargs)
``MultiLineString`` objects may be instantiated by passing in one ``MultiLineString`` objects may be instantiated by passing in
or more :class:`LineString` objects as arguments, or a single :class:`LineString` objects as arguments, or a single sequence of
sequence of :class:`LineString` objects:: :class:`LineString` objects::
>>> ls1 = LineString((0, 0), (1, 1)) >>> ls1 = LineString((0, 0), (1, 1))
>>> ls2 = LineString((2, 2), (3, 3)) >>> ls2 = LineString((2, 2), (3, 3))
>>> mls = MultiLineString(ls1, ls2) >>> mls = MultiLineString(ls1, ls2)
>>> mls = MultiLineString([ls1, ls2]) >>> mls = MultiLineString([ls1, ls2])
.. versionchanged:: 1.10
In previous versions, an empty ``MultiLineString`` couldn't be
instantiated.
.. attribute:: merged .. attribute:: merged
Returns a :class:`LineString` representing the line merge of Returns a :class:`LineString` representing the line merge of
@ -764,15 +796,19 @@ Geometry Collections
.. class:: MultiPolygon(*args, **kwargs) .. class:: MultiPolygon(*args, **kwargs)
``MultiPolygon`` objects may be instantiated by passing one or ``MultiPolygon`` objects may be instantiated by passing :class:`Polygon`
more :class:`Polygon` objects as arguments, or a single sequence objects as arguments, or a single sequence of :class:`Polygon` objects::
of :class:`Polygon` objects::
>>> p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) ) >>> p1 = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) )
>>> p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) ) >>> p2 = Polygon( ((1, 1), (1, 2), (2, 2), (1, 1)) )
>>> mp = MultiPolygon(p1, p2) >>> mp = MultiPolygon(p1, p2)
>>> mp = MultiPolygon([p1, p2]) >>> mp = MultiPolygon([p1, p2])
.. versionchanged:: 1.10
In previous versions, an empty ``MultiPolygon`` couldn't be
instantiated.
.. attribute:: cascaded_union .. attribute:: cascaded_union
.. deprecated:: 1.10 .. deprecated:: 1.10
@ -789,14 +825,19 @@ Geometry Collections
.. class:: GeometryCollection(*args, **kwargs) .. class:: GeometryCollection(*args, **kwargs)
``GeometryCollection`` objects may be instantiated by passing in ``GeometryCollection`` objects may be instantiated by passing in other
one or more other :class:`GEOSGeometry` as arguments, or a single :class:`GEOSGeometry` as arguments, or a single sequence of
sequence of :class:`GEOSGeometry` objects:: :class:`GEOSGeometry` objects::
>>> poly = Polygon( ((0, 0), (0, 1), (1, 1), (0, 0)) ) >>> 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)
>>> 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:
Prepared Geometries Prepared Geometries

View File

@ -92,6 +92,8 @@ Minor features
:class:`~django.contrib.gis.db.models.functions.SymDifference` :class:`~django.contrib.gis.db.models.functions.SymDifference`
functions on MySQL. functions on MySQL.
* Added support for instantiating empty GEOS geometries.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -793,6 +793,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
p[:] = (1, 2, 3) p[:] = (1, 2, 3)
self.assertEqual(p, Point(1, 2, 3)) self.assertEqual(p, Point(1, 2, 3))
p[:] = ()
self.assertEqual(p.wkt, Point())
p[:] = (1, 2) p[:] = (1, 2)
self.assertEqual(p.wkt, Point(1, 2)) self.assertEqual(p.wkt, Point(1, 2))
@ -804,6 +807,9 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
def test_linestring_list_assignment(self): def test_linestring_list_assignment(self):
ls = LineString((0, 0), (1, 1)) ls = LineString((0, 0), (1, 1))
ls[:] = ()
self.assertEqual(ls, LineString())
ls[:] = ((0, 0), (1, 1), (2, 2)) ls[:] = ((0, 0), (1, 1), (2, 2))
self.assertEqual(ls, LineString((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): def test_linearring_list_assignment(self):
ls = LinearRing((0, 0), (0, 1), (1, 1), (0, 0)) 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)) 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))) self.assertEqual(ls, LinearRing((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
ls[:] = ((0, 0), (1, 1), (2, 2)) 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): def test_threed(self):
"Testing three-dimensional geometries." "Testing three-dimensional geometries."
# Testing a 3D Point # Testing a 3D Point
@ -874,16 +902,27 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
def test_emptyCollections(self): def test_emptyCollections(self):
"Testing empty geometries and collections." "Testing empty geometries and collections."
gc1 = GeometryCollection([]) geoms = [
gc2 = fromstr('GEOMETRYCOLLECTION EMPTY') GeometryCollection([]),
pnt = fromstr('POINT EMPTY') fromstr('GEOMETRYCOLLECTION EMPTY'),
ls = fromstr('LINESTRING EMPTY') GeometryCollection(),
poly = fromstr('POLYGON EMPTY') fromstr('POINT EMPTY'),
mls = fromstr('MULTILINESTRING EMPTY') Point(),
mpoly1 = fromstr('MULTIPOLYGON EMPTY') fromstr('LINESTRING EMPTY'),
mpoly2 = MultiPolygon(()) LineString(),
fromstr('POLYGON EMPTY'),
Polygon(),
fromstr('MULTILINESTRING EMPTY'),
MultiLineString(),
fromstr('MULTIPOLYGON EMPTY'),
MultiPolygon(()),
MultiPolygon(),
]
for g in [gc1, gc2, pnt, ls, poly, mls, mpoly1, mpoly2]: if numpy:
geoms.append(LineString(numpy.array([])))
for g in geoms:
self.assertEqual(True, g.empty) self.assertEqual(True, g.empty)
# Testing len() and num_geom. # Testing len() and num_geom.

View File

@ -299,18 +299,18 @@ class ListMixinTest(unittest.TestCase):
def test08_min_length(self): def test08_min_length(self):
'Length limits' 'Length limits'
pl, ul = self.lists_of_len() pl, ul = self.lists_of_len(5)
ul._minlength = 1 ul._minlength = 3
def delfcn(x, i): def delfcn(x, i):
del x[:i] del x[:i]
def setfcn(x, i): def setfcn(x, i):
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, delfcn, ul, i)
self.assertRaises(ValueError, setfcn, ul, i) self.assertRaises(ValueError, setfcn, ul, i)
del ul[:ul._minlength] del ul[:len(ul) - ul._minlength]
ul._maxlength = 4 ul._maxlength = 4
for i in range(0, ul._maxlength - len(ul)): for i in range(0, ul._maxlength - len(ul)):