summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2007-07-21 17:39:09 +0000
committerJustin Bronn <jbronn@gmail.com>2007-07-21 17:39:09 +0000
commite9224451864d39c2ba2b2b82070bbafa61563fe1 (patch)
tree7e804e3836a453aa2c226401c1088a7077012676
parent8162e3543c6164edb21127bf2eaccaecda8d6385 (diff)
gis: big changes within in GEOS internals:
(1) All Geometry types and Collections now have their own constructor (e.g., Polygon(LinearRing(..)), MultiPoint(Point(..), Point(..))) (2) Memory management improved, laying the foundation for fully mutable geometries. (3) Added set-like operations (|, &, -, ^) (4) Added & improved tests. (5) docstring changes git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5742 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/contrib/gis/geos/__init__.py9
-rw-r--r--django/contrib/gis/geos/base.py154
-rw-r--r--django/contrib/gis/geos/collections.py112
-rw-r--r--django/contrib/gis/geos/coordseq.py29
-rw-r--r--django/contrib/gis/geos/geometries.py199
-rw-r--r--django/contrib/gis/geos/libgeos.py96
-rw-r--r--django/contrib/gis/tests/geometries.py9
-rw-r--r--django/contrib/gis/tests/test_geos.py157
-rw-r--r--django/contrib/gis/utils/LayerMapping.py2
9 files changed, 582 insertions, 185 deletions
diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py
index 12714ed94a..832b8906e6 100644
--- a/django/contrib/gis/geos/__init__.py
+++ b/django/contrib/gis/geos/__init__.py
@@ -30,8 +30,13 @@
"""
from base import GEOSGeometry
-from geometries import Point, LineString, LinearRing, HAS_NUMPY
-from error import GEOSException
+from geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
+from collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
+from error import GEOSException, GEOSGeometryIndexError
+
+def fromstr(wkt_or_hex):
+ "Given a string value (wkt or hex), returns a GEOSGeometry object."
+ return GEOSGeometry(wkt_or_hex)
def hex_to_wkt(hex):
"Converts HEXEWKB into WKT."
diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py
index ef4c81118e..10213a63c0 100644
--- a/django/contrib/gis/geos/base.py
+++ b/django/contrib/gis/geos/base.py
@@ -1,35 +1,38 @@
-# Trying not to pollute the namespace.
+"""
+ This module contains the 'base' GEOSGeometry object -- all GEOS geometries
+ inherit from this object.
+"""
+
+# ctypes and types dependencies.
from ctypes import \
byref, string_at, create_string_buffer, pointer, \
c_char_p, c_double, c_int, c_size_t
-from types import StringType, IntType, FloatType, TupleType, ListType
+from types import StringType, IntType, FloatType
-# Getting GEOS-related dependencies.
+# Python and GEOS-related dependencies.
import re
from warnings import warn
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
+if HAS_NUMPY: from numpy import ndarray, array
-if HAS_NUMPY:
- from numpy import ndarray, array
-
-# For recognizing HEXEWKB.
-hex_regex = re.compile(r'^[0-9A-Fa-f]+')
+# Regular expression for recognizing HEXEWKB.
+hex_regex = re.compile(r'^[0-9A-Fa-f]+$')
class GEOSGeometry(object):
"A class that, generally, encapsulates a GEOS geometry."
#### Python 'magic' routines ####
- def __init__(self, geo_input, input_type=False, child=False):
+ def __init__(self, geo_input, input_type=False, parent=False):
"""The constructor for GEOS geometry objects. May take the following
strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form).
The `input_type` keyword has been deprecated -- geometry type is now auto-detected.
- The `child` keyword is for internal use only, and indicates to the garbage collector
- not to delete this geometry if it was spawned from a parent (e.g., the exterior
- ring from a polygon).
+ The `parent` keyword is for internal use only, and indicates to the garbage collector
+ not to delete this geometry because it was spawned from a parent (e.g., the exterior
+ ring from a polygon). Its value is the GEOSPointer of the parent geometry.
"""
# Initially, setting the pointer to NULL
@@ -55,84 +58,126 @@ class GEOSGeometry(object):
raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input))
if bool(g):
- # If we have a GEOSPointer object, just set the '_ptr' attribute with g
+ # If we have a GEOSPointer object, just set the '_ptr' attribute with input
if isinstance(g, GEOSPointer): self._ptr = g
- else: self._ptr.set(g) # Otherwise, set the address
+ else: self._ptr.set(g) # Otherwise, set with the address
else:
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
- # Setting the 'child' flag -- when the object is labeled with this flag
- # it will not be destroyed by __del__(). This is used for child geometries from
+ # Setting the 'parent' flag -- when the object is labeled with this flag
+ # it will not be destroyed by __del__(). This is used for child geometries spawned from
# parent geometries (e.g., LinearRings from a Polygon, Points from a MultiPoint, etc.).
- self._child = child
-
+ if isinstance(parent, GEOSPointer):
+ self._parent = parent
+ else:
+ self._parent = GEOSPointer(0)
+
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
self.__class__ = GEOS_CLASSES[self.geom_type]
+ # Getting the coordinate sequence for the geometry (will be None on geometries that
+ # do not have coordinate sequences)
+ self._get_cs()
+
# Extra setup needed for Geometries that may be parents.
- if isinstance(self, GeometryCollection): self._geoms = {}
- if isinstance(self, Polygon): self._rings = {}
+ if isinstance(self, (Polygon, GeometryCollection)): self._populate()
def __del__(self):
- "Destroys this geometry -- only if the pointer is valid and this is not a child geometry."
- #print 'Deleting %s (child=%s, valid=%s)' % (self.geom_type, self._child, self._ptr.valid)
- if self._ptr.valid and not self._child: lgeos.GEOSGeom_destroy(self._ptr())
+ "Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent."
+ #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
+ # Only calling destroy on valid pointers not spawned from a parent
+ if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
def __str__(self):
"WKT is used for the string representation."
return self.wkt
+ def __repr__(self):
+ return '<%s object>' % self.geom_type
+
+ # Comparison operators
def __eq__(self, other):
"Equivalence testing."
return self.equals(other)
+ def __ne__(self, other):
+ "The not equals operator."
+ return not self.equals(other)
+
+ ### Geometry set-like operations ###
+ # Thanks to Sean Gillies for inspiration:
+ # http://lists.gispython.org/pipermail/community/2007-July/001034.html
+ # g = g1 | g2
+ def __or__(self, other):
+ "Returns the union of this Geometry and the other."
+ return self.union(other)
+
+ # g = g1 & g2
+ def __and__(self, other):
+ "Returns the intersection of this Geometry and the other."
+ return self.intersection(other)
+
+ # g = g1 - g2
+ def __sub__(self, other):
+ "Return the difference this Geometry and the other."
+ return self.difference(other)
+
+ # g = g1 ^ g2
+ def __xor__(self, other):
+ "Return the symmetric difference of this Geometry and the other."
+ return self.sym_difference(other)
+
#### Coordinate Sequence Routines ####
- def _cache_cs(self):
- "Caches the coordinate sequence for this Geometry."
- if not hasattr(self, '_cs'):
- # Only these geometries are allowed to have coordinate sequences.
- if self.geom_type in ('LineString', 'LinearRing', 'Point'):
- self._cs = GEOSCoordSeq(GEOSPointer(lgeos.GEOSGeom_getCoordSeq(self._ptr())), self.hasz)
- else:
- self._cs = None
+ @property
+ def has_cs(self):
+ "Returns True if this Geometry has a coordinate sequence, False if not."
+ # Only these geometries are allowed to have coordinate sequences.
+ if isinstance(self, (Point, LineString, LinearRing)):
+ return True
+ else:
+ return False
+
+ def _get_cs(self):
+ "Gets the coordinate sequence for this Geometry."
+ if self.has_cs:
+ self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True)
+ self._cs = GEOSCoordSeq(self._ptr, self.hasz)
+ else:
+ self._cs = None
@property
def coord_seq(self):
- "Returns the coordinate sequence for the geometry."
- # Getting the coordinate sequence for the geometry
- self._cache_cs()
-
- # Returning a GEOSCoordSeq wrapped around the pointer.
+ "Returns the coordinate sequence for this Geometry."
return self._cs
#### Geometry Info ####
@property
def geom_type(self):
- "Returns a string representing the geometry type, e.g. 'Polygon'"
+ "Returns a string representing the Geometry type, e.g. 'Polygon'"
return string_at(lgeos.GEOSGeomType(self._ptr()))
@property
def geom_typeid(self):
- "Returns an integer representing the geometry type."
+ "Returns an integer representing the Geometry type."
return lgeos.GEOSGeomTypeId(self._ptr())
@property
def num_geom(self):
- "Returns the number of geometries in the geometry."
+ "Returns the number of geometries in the Geometry."
n = lgeos.GEOSGetNumGeometries(self._ptr())
if n == -1: raise GEOSException, 'Error getting number of geometries.'
else: return n
@property
def num_coords(self):
- "Returns the number of coordinates in the geometry."
+ "Returns the number of coordinates in the Geometry."
n = lgeos.GEOSGetNumCoordinates(self._ptr())
if n == -1: raise GEOSException, 'Error getting number of coordinates.'
else: return n
@property
def num_points(self):
- "Returns the number points, or coordinates, in the geometry."
+ "Returns the number points, or coordinates, in the Geometry."
return self.num_coords
@property
@@ -145,6 +190,7 @@ class GEOSGeometry(object):
status = lgeos.GEOSNormalize(self._ptr())
if status == -1: raise GEOSException, 'failed to normalize geometry'
+ ## Internal for GEOS unary & binary predicate functions ##
def _unary_predicate(self, func):
"Returns the result, or raises an exception for the given unary predicate function."
val = func(self._ptr())
@@ -164,12 +210,12 @@ class GEOSGeometry(object):
#### Unary predicates ####
@property
def empty(self):
- "Returns a boolean indicating whether the set of points in this geometry are empty."
+ "Returns a boolean indicating whether the set of points in this Geometry are empty."
return self._unary_predicate(lgeos.GEOSisEmpty)
@property
def valid(self):
- "This property tests the validity of this geometry."
+ "This property tests the validity of this Geometry."
return self._unary_predicate(lgeos.GEOSisValid)
@property
@@ -190,17 +236,17 @@ class GEOSGeometry(object):
#### Binary predicates. ####
def relate_pattern(self, other, pattern):
"""Returns true if the elements in the DE-9IM intersection matrix for
- the two Geometrys match the elements in pattern."""
+ the two Geometries match the elements in pattern."""
if len(pattern) > 9:
raise GEOSException, 'invalid intersection matrix pattern'
return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern))
def disjoint(self, other):
- "Returns true if the DE-9IM intersection matrix for the two Geometrys is FF*FF****."
+ "Returns true if the DE-9IM intersection matrix for the two Geometries is FF*FF****."
return self._binary_predicate(lgeos.GEOSDisjoint, other)
def touches(self, other):
- "Returns true if the DE-9IM intersection matrix for the two Geometrys is FT*******, F**T***** or F***T****."
+ "Returns true if the DE-9IM intersection matrix for the two Geometries is FT*******, F**T***** or F***T****."
return self._binary_predicate(lgeos.GEOSTouches, other)
def intersects(self, other):
@@ -208,12 +254,12 @@ class GEOSGeometry(object):
return self._binary_predicate(lgeos.GEOSIntersects, other)
def crosses(self, other):
- """Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T****** (for a point and a curve,
+ """Returns true if the DE-9IM intersection matrix for the two Geometries is T*T****** (for a point and a curve,
a point and an area or a line and an area) 0******** (for two curves)."""
return self._binary_predicate(lgeos.GEOSCrosses, other)
def within(self, other):
- "Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**F***."
+ "Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**F***."
return self._binary_predicate(lgeos.GEOSWithin, other)
def contains(self, other):
@@ -221,16 +267,16 @@ class GEOSGeometry(object):
return self._binary_predicate(lgeos.GEOSContains, other)
def overlaps(self, other):
- """Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T***T** (for two points
+ """Returns true if the DE-9IM intersection matrix for the two Geometries is T*T***T** (for two points
or two surfaces) 1*T***T** (for two curves)."""
return self._binary_predicate(lgeos.GEOSOverlaps, other)
def equals(self, other):
- "Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**FFF*."
+ "Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**FFF*."
return self._binary_predicate(lgeos.GEOSEquals, other)
def equals_exact(self, other, tolerance=0):
- "Returns true if the two Geometrys are exactly equal, up to a specified tolerance."
+ "Returns true if the two Geometries are exactly equal, up to a specified tolerance."
tol = c_double(tolerance)
return self._binary_predicate(lgeos.GEOSEqualsExact, other, tol)
@@ -290,7 +336,7 @@ class GEOSGeometry(object):
@property
def centroid(self):
- """The centroid is equal to the centroid of the set of component Geometrys
+ """The centroid is equal to the centroid of the set of component Geometries
of highest dimension (since the lower-dimension geometries contribute zero
"weight" to the centroid)."""
return self._unary_topology(lgeos.GEOSGetCentroid)
@@ -344,7 +390,7 @@ class GEOSGeometry(object):
def clone(self):
"Clones this Geometry."
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()))
-
+
# Class mapping dictionary
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
@@ -352,8 +398,8 @@ GEOS_CLASSES = {'Point' : Point,
'Polygon' : Polygon,
'LineString' : LineString,
'LinearRing' : LinearRing,
- 'GeometryCollection' : GeometryCollection,
'MultiPoint' : MultiPoint,
'MultiLineString' : MultiLineString,
'MultiPolygon' : MultiPolygon,
+ 'GeometryCollection' : GeometryCollection,
}
diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py
index 9d995fb13e..9105b34b2f 100644
--- a/django/contrib/gis/geos/collections.py
+++ b/django/contrib/gis/geos/collections.py
@@ -2,33 +2,94 @@
This module houses the Geometry Collection objects:
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
"""
-from ctypes import c_int
-from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer
+from ctypes import c_int, c_uint, byref, cast
+from types import TupleType, ListType
+from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, init_from_geom, get_pointer_arr, GEOM_PTR
from django.contrib.gis.geos.base import GEOSGeometry
-from django.contrib.gis.geos.error import GEOSException
+from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
+from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
+
+def init_from_poly(poly):
+ "Internal routine used for initializing Geometry Collections from Polygons."
+ # Constructing a new Polygon to take control of the rings.
+ p = Polygon(*tuple(ring for ring in poly))
+
+ # If this Polygon came from a GeometryCollection, it is a child
+ # and the parent geometry pointer is nullified.
+ if poly._parent: poly._parent.nullify()
+
+ # Nullifying the polygon pointer
+ poly._ptr.nullify()
+
+ # Returning the address of the new Polygon.
+ return p._ptr()
class GeometryCollection(GEOSGeometry):
+ _allowed = (Point, LineString, LinearRing, Polygon)
+ _typeid = 7
- def __del__(self):
- "Override the GEOSGeometry delete routine to safely take care of any spawned geometries."
- # Nullifying the pointers to internal geometries, preventing any attempted future access
- for k in self._geoms: self._geoms[k].nullify()
- super(GeometryCollection, self).__del__() # Calling the parent __del__() method.
+ def __init__(self, *args):
+ self._ptr = GEOSPointer(0) # Initially NULL
+ self._geoms = {}
+ self._parent = False
+
+ if not args:
+ raise TypeError, 'Must provide at least one LinearRing to initialize Polygon.'
+
+ if len(args) == 1: # If only one geometry provided or a list of geometries is provided
+ if isinstance(args[0], (TupleType, ListType)):
+ init_geoms = args[0]
+ else:
+ init_geoms = args
+ else:
+ init_geoms = args
+
+ # Ensuring that only the permitted geometries are allowed in this collection
+ if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
+ raise TypeError, 'Invalid Geometry type encountered in the arguments.'
+
+ # Creating the geometry pointer array
+ ngeom = len(init_geoms)
+ geoms = get_pointer_arr(ngeom)
+ # Incrementing through each input geometry.
+ for i in xrange(ngeom):
+ if isinstance(init_geoms[i], Polygon):
+ # Special care is taken when importing from Polygons
+ geoms[i] = cast(init_from_poly(init_geoms[i]), GEOM_PTR)
+ else:
+ geoms[i] = cast(init_from_geom(init_geoms[i]), GEOM_PTR)
+
+ # Calling the parent class, using the pointer returned from GEOS createCollection()
+ super(GeometryCollection, self).__init__(lgeos.GEOSGeom_createCollection(c_int(self._typeid), byref(geoms), c_uint(ngeom)))
+
+ def __del__(self):
+ "Overloaded deletion method for Geometry Collections."
+ #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
+ # If this geometry is still valid, it hasn't been modified by others.
+ if self._ptr.valid:
+ # Nullifying pointers to internal geometries, preventing any attempted future access.
+ for k in self._geoms: self._geoms[k].nullify()
+ super(GeometryCollection, self).__del__()
+ else:
+ # Internal memory has become part of other Geometry objects, must delete the
+ # internal objects which are still valid individually, since calling destructor
+ # on entire geometry will result in an attempted deletion of NULL pointers for
+ # the missing components.
+ for k in self._geoms:
+ if self._geoms[k].valid:
+ lgeos.GEOSGeom_destroy(self._geoms[k].address)
+ self._geoms[k].nullify()
+
def __getitem__(self, index):
"For indexing on the multiple geometries."
+ # Checking the index and returning the corresponding GEOS geometry.
self._checkindex(index)
-
- # Setting an entry in the _geoms dictionary for the requested geometry.
- if not index in self._geoms:
- self._geoms[index] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(index)))
-
- # Cloning the GEOS Geometry first, before returning it.
- return GEOSGeometry(self._geoms[index], child=True)
+ return GEOSGeometry(self._geoms[index], parent=self._ptr)
def __iter__(self):
"For iteration on the multiple geometries."
- for i in xrange(self.__len__()):
+ for i in xrange(len(self)):
yield self.__getitem__(i)
def __len__(self):
@@ -40,7 +101,20 @@ class GeometryCollection(GEOSGeometry):
if index < 0 or index >= self.num_geom:
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
+ def _populate(self):
+ "Populates the internal child geometry dictionary."
+ self._geoms = {}
+ for i in xrange(self.num_geom):
+ self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i)))
+
+
# MultiPoint, MultiLineString, and MultiPolygon class definitions.
-class MultiPoint(GeometryCollection): pass
-class MultiLineString(GeometryCollection): pass
-class MultiPolygon(GeometryCollection): pass
+class MultiPoint(GeometryCollection):
+ _allowed = Point
+ _typeid = 4
+class MultiLineString(GeometryCollection):
+ _allowed = (LineString, LinearRing)
+ _typeid = 5
+class MultiPolygon(GeometryCollection):
+ _allowed = Polygon
+ _typeid = 6
diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py
index eba49f768b..c6f84ea4b2 100644
--- a/django/contrib/gis/geos/coordseq.py
+++ b/django/contrib/gis/geos/coordseq.py
@@ -1,6 +1,8 @@
-from django.contrib.gis.geos.libgeos import lgeos
+from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
from ctypes import c_double, c_int, c_uint, byref
+from types import ListType, TupleType
+if HAS_NUMPY: from numpy import ndarray
"""
This module houses the GEOSCoordSeq object, and is used internally
@@ -14,6 +16,8 @@ class GEOSCoordSeq(object):
#### Python 'magic' routines ####
def __init__(self, ptr, z=False):
"Initializes from a GEOS pointer."
+ if not isinstance(ptr, GEOSPointer):
+ raise TypeError, 'Coordinate sequence should initialize with a GEOSPointer.'
self._ptr = ptr
self._z = z
@@ -39,6 +43,14 @@ class GEOSCoordSeq(object):
def __setitem__(self, index, value):
"Can use the index [] operator to set coordinate sequence at an index."
+ # Checking the input value
+ if isinstance(value, (ListType, TupleType)):
+ pass
+ elif HAS_NUMPY and isinstance(value, ndarray):
+ pass
+ else:
+ raise TypeError, 'Must set coordinate with a sequence (list, tuple, or numpy array).'
+ # Checking the dims of the input
if self.dims == 3 and self._z:
n_args = 3
set_3d = True
@@ -47,6 +59,7 @@ class GEOSCoordSeq(object):
set_3d = False
if len(value) != n_args:
raise TypeError, 'Dimension of value does not match.'
+ # Setting the X, Y, Z
self.setX(index, value[0])
self.setY(index, value[1])
if set_3d: self.setZ(index, value[2])
@@ -73,11 +86,11 @@ class GEOSCoordSeq(object):
dim = c_uint(dimension)
idx = c_uint(index)
- # 'd' is the value of the point, passed in by reference
+ # 'd' is the value of the ordinate, passed in by reference
d = c_double()
- status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr(), idx, dim, byref(d))
+ status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr.coordseq(), idx, dim, byref(d))
if status == 0:
- raise GEOSException, 'could not retrieve %th ordinate at index: %s' % (str(dimension), str(index))
+ raise GEOSException, 'could not retrieve %sth ordinate at index: %s' % (dimension, index)
return d.value
def setOrdinate(self, dimension, index, value):
@@ -90,7 +103,7 @@ class GEOSCoordSeq(object):
idx = c_uint(index)
# Setting the ordinate
- status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr(), idx, dim, c_double(value))
+ status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr.coordseq(), idx, dim, c_double(value))
if status == 0:
raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index)
@@ -123,7 +136,7 @@ class GEOSCoordSeq(object):
def size(self):
"Returns the size of this coordinate sequence."
n = c_uint(0)
- status = lgeos.GEOSCoordSeq_getSize(self._ptr(), byref(n))
+ status = lgeos.GEOSCoordSeq_getSize(self._ptr.coordseq(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq size.'
return n.value
@@ -132,7 +145,7 @@ class GEOSCoordSeq(object):
def dims(self):
"Returns the dimensions of this coordinate sequence."
n = c_uint(0)
- status = lgeos.GEOSCoordSeq_getDimensions(self._ptr(), byref(n))
+ status = lgeos.GEOSCoordSeq_getDimensions(self._ptr.coordseq(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq dimensions.'
return n.value
@@ -146,7 +159,7 @@ class GEOSCoordSeq(object):
@property
def clone(self):
"Clones this coordinate sequence."
- pass
+ return GEOSCoordSeq(GEOSPointer(0, lgeos.GEOSCoordSeq_clone(self._ptr.coordseq())), self.hasz)
@property
def tuple(self):
diff --git a/django/contrib/gis/geos/geometries.py b/django/contrib/gis/geos/geometries.py
index 449a79becb..cdd3d49922 100644
--- a/django/contrib/gis/geos/geometries.py
+++ b/django/contrib/gis/geos/geometries.py
@@ -1,12 +1,16 @@
-from ctypes import c_double, c_int, c_uint
+"""
+ This module houses the Point, LineString, LinearRing, and Polygon OGC
+ geometry classes. All geometry classes in this module inherit from
+ GEOSGeometry.
+"""
+
+from ctypes import c_double, c_int, c_uint, byref, cast
from types import FloatType, IntType, ListType, TupleType
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
-from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
+from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, init_from_geom, GEOM_PTR, HAS_NUMPY
from django.contrib.gis.geos.base import GEOSGeometry
-from django.contrib.gis.geos.error import GEOSException
-
-if HAS_NUMPY:
- from numpy import ndarray, array
+from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
+if HAS_NUMPY: from numpy import ndarray, array
class Point(GEOSGeometry):
@@ -17,6 +21,8 @@ class Point(GEOSGeometry):
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
"""
+ self._ptr = GEOSPointer(0) # Initially NULL
+
if isinstance(x, (TupleType, ListType)):
# Here a tuple or list was passed in under the ``x`` parameter.
ndim = len(x)
@@ -52,14 +58,17 @@ class Point(GEOSGeometry):
# Initializing from the geometry, and getting a Python object
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs))
+ def __len__(self):
+ "Returns the number of dimensions for this Point (either 2 or 3)."
+ if self.hasz: return 3
+ else: return 2
+
def _getOrdinate(self, dim, idx):
"The coordinate sequence getOrdinate() wrapper."
- self._cache_cs()
return self._cs.getOrdinate(dim, idx)
def _setOrdinate(self, dim, idx, value):
"The coordinate sequence setOrdinate() wrapper."
- self._cache_cs()
self._cs.setOrdinate(dim, idx, value)
def get_x(self):
@@ -100,38 +109,50 @@ class Point(GEOSGeometry):
### Tuple setting and retrieval routines. ###
def get_tuple(self):
"Returns a tuple of the point."
- self._cache_cs()
return self._cs.tuple
- def set_tuple(self):
- "Sets the tuple for this point object."
- pass
-
+ def set_tuple(self, tup):
+ "Sets the coordinates of the point with the given tuple."
+ self._cs[0] = tup
+
# The tuple property
tuple = property(get_tuple, set_tuple)
class LineString(GEOSGeometry):
#### Python 'magic' routines ####
- def __init__(self, coords, ring=False):
- """Initializes on the given sequence, may take lists, tuples, or NumPy arrays
- of X,Y pairs."""
+ def __init__(self, *args, **kwargs):
+ """Initializes on the given sequence -- may take lists, tuples, NumPy arrays
+ of X,Y pairs, or Point objects. If Point objects are used, ownership is
+ _not_ transferred to the LineString object.
+
+ Examples:
+ ls = LineString((1, 1), (2, 2))
+ ls = LineString([(1, 1), (2, 2)])
+ ls = LineString(array([(1, 1), (2, 2)]))
+ ls = LineString(Point(1, 1), Point(2, 2))
+ """
+ self._ptr = GEOSPointer(0) # Initially NULL
+
+ # If only one argument was provided, then set the coords array appropriately
+ if len(args) == 1: coords = args[0]
+ else: coords = args
if isinstance(coords, (TupleType, ListType)):
+ # Getting the number of coords and the number of dimensions -- which
+ # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
ncoords = len(coords)
- first = True
- for coord in coords:
- if not isinstance(coord, (TupleType, ListType)):
+ if coords: ndim = len(coords[0])
+ else: raise TypeError, 'Cannot initialize on empty sequence.'
+ self._checkdim(ndim)
+ # Incrementing through each of the coordinates and verifying
+ for i in xrange(1, ncoords):
+ if not isinstance(coords[i], (TupleType, ListType, Point)):
raise TypeError, 'each coordinate should be a sequence (list or tuple)'
- if first:
- ndim = len(coord)
- self._checkdim(ndim)
- first = False
- else:
- if len(coord) != ndim: raise TypeError, 'Dimension mismatch.'
+ if len(coords[i]) != ndim: raise TypeError, 'Dimension mismatch.'
numpy_coords = False
elif HAS_NUMPY and isinstance(coords, ndarray):
- shape = coords.shape
+ shape = coords.shape # Using numpy's shape.
if len(shape) != 2: raise TypeError, 'Too many dimensions.'
self._checkdim(shape[1])
ncoords = shape[0]
@@ -141,40 +162,38 @@ class LineString(GEOSGeometry):
raise TypeError, 'Invalid initialization input for LineStrings.'
# Creating the coordinate sequence
- cs = GEOSCoordSeq(GEOSPointer(create_cs(c_uint(ncoords), c_uint(ndim))))
+ cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim))))
# Setting each point in the coordinate sequence
for i in xrange(ncoords):
if numpy_coords: cs[i] = coords[i,:]
+ elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
else: cs[i] = coords[i]
# Getting the initialization function
- if ring:
+ if kwargs.get('ring', False):
func = lgeos.GEOSGeom_createLinearRing
else:
func = lgeos.GEOSGeom_createLineString
# Calling the base geometry initialization with the returned pointer from the function.
- super(LineString, self).__init__(func(cs._ptr()))
+ super(LineString, self).__init__(func(cs._ptr.coordseq()))
def __getitem__(self, index):
"Gets the point at the specified index."
- self._cache_cs()
return self._cs[index]
def __setitem__(self, index, value):
"Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
- self._cache_cs()
self._cs[index] = value
def __iter__(self):
"Allows iteration over this LineString."
for i in xrange(self.__len__()):
- yield self.__getitem__(index)
+ yield self.__getitem__(i)
def __len__(self):
"Returns the number of points in this LineString."
- self._cache_cs()
return len(self._cs)
def _checkdim(self, dim):
@@ -184,55 +203,103 @@ class LineString(GEOSGeometry):
@property
def tuple(self):
"Returns a tuple version of the geometry from the coordinate sequence."
- self._cache_cs()
return self._cs.tuple
def _listarr(self, func):
"""Internal routine that returns a sequence (list) corresponding with
the given function. Will return a numpy array if possible."""
- lst = [func(i) for i in xrange(self.__len__())] # constructing the list, using the function
+ lst = [func(i) for i in xrange(len(self))] # constructing the list, using the function
if HAS_NUMPY: return array(lst) # ARRRR!
else: return lst
@property
def array(self):
"Returns a numpy array for the LineString."
- self._cache_cs()
return self._listarr(self._cs.__getitem__)
@property
def x(self):
"Returns a list or numpy array of the X variable."
- self._cache_cs()
return self._listarr(self._cs.getX)
@property
def y(self):
"Returns a list or numpy array of the Y variable."
- self._cache_cs()
return self._listarr(self._cs.getY)
@property
def z(self):
"Returns a list or numpy array of the Z variable."
- self._cache_cs()
if not self.hasz: return None
else: return self._listarr(self._cs.getZ)
# LinearRings are LineStrings used within Polygons.
class LinearRing(LineString):
- def __init__(self, coords):
+ def __init__(self, *args):
"Overriding the initialization function to set the ring keyword."
- super(LinearRing, self).__init__(coords, ring=True)
+ kwargs = {'ring' : True}
+ super(LinearRing, self).__init__(*args, **kwargs)
class Polygon(GEOSGeometry):
+ def __init__(self, *args):
+ """Initializes on an exterior ring and a sequence of holes (both instances of LinearRings.
+ All LinearRing instances used for creation will become owned by this Polygon.
+
+ Examples, where shell, hole1, and hole2 are valid LinearRing geometries:
+ poly = Polygon(shell, hole1, hole2)
+ poly = Polygon(shell, (hole1, hole2))
+ """
+ self._ptr = GEOSPointer(0) # Initially NULL
+ self._rings = {}
+ if not args:
+ raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
+
+ # Getting the ext_ring and init_holes parameters from the argument list
+ ext_ring = args[0]
+ init_holes = args[1:]
+ if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)):
+ init_holes = init_holes[0]
+
+ # Ensuring the exterior ring parameter is a LinearRing object
+ if not isinstance(ext_ring, LinearRing):
+ raise TypeError, 'First argument for Polygon initialization must be a LinearRing.'
+
+ # Making sure all of the holes are LinearRing objects
+ if False in [isinstance(hole, LinearRing) for hole in init_holes]:
+ raise TypeError, 'Holes parameter must be a sequence of LinearRings.'
+
+ # Getting the holes
+ nholes = len(init_holes)
+ holes = get_pointer_arr(nholes)
+ for i in xrange(nholes):
+ # Casting to the Geometry Pointer type
+ holes[i] = cast(init_from_geom(init_holes[i]), GEOM_PTR)
+
+ # Getting the shell pointer address,
+ shell = init_from_geom(ext_ring)
+
+ # Calling with the GEOS createPolygon factory.
+ super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)))
+
def __del__(self):
- "Override the GEOSGeometry delete routine to safely take care of any spawned rings."
- # Nullifying the pointers to internal rings, preventing any attempted future access
- for k in self._rings: self._rings[k].nullify()
- super(Polygon, self).__del__() # Calling the parent __del__() method.
-
+ "Overloaded deletion method for Polygons."
+ #print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
+ # If this geometry is still valid, it hasn't been modified by others.
+ if self._ptr.valid:
+ # Nulling the pointers to internal rings, preventing any attempted future access
+ for k in self._rings: self._rings[k].nullify()
+ super(Polygon, self).__del__()
+ else:
+ # Internal memory has become part of other objects; must delete the
+ # internal objects which are still valid individually, since calling
+ # destructor on entire geometry will result in an attempted deletion
+ # of NULL pointers for the missing components.
+ for k in self._rings:
+ if self._rings[k].valid:
+ lgeos.GEOSGeom_destroy(self._rings[k].address)
+ self._rings[k].nullify()
+
def __getitem__(self, index):
"""Returns the ring at the specified index. The first index, 0, will always
return the exterior ring. Indices > 0 will return the interior ring."""
@@ -247,13 +314,22 @@ class Polygon(GEOSGeometry):
def __iter__(self):
"Iterates over each ring in the polygon."
- for i in xrange(self.__len__()):
+ for i in xrange(len(self)):
yield self.__getitem__(i)
def __len__(self):
"Returns the number of rings in this Polygon."
return self.num_interior_rings + 1
+ def _populate(self):
+ "Populates the internal rings dictionary."
+ # Getting the exterior ring first for the 0th index.
+ self._rings = {0 : GEOSPointer(lgeos.GEOSGetExteriorRing(self._ptr()))}
+
+ # Getting the interior rings.
+ for i in xrange(self.num_interior_rings):
+ self._rings[i+1] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i)))
+
def get_interior_ring(self, ring_i):
"""Gets the interior ring at the specified index,
0 is for the first interior ring, not the exterior ring."""
@@ -262,13 +338,9 @@ class Polygon(GEOSGeometry):
if ring_i < 0 or ring_i >= self.num_interior_rings:
raise IndexError, 'ring index out of range'
- # Placing the ring in internal rings dictionary.
- idx = ring_i+1 # the index for the polygon is +1 because of the exterior ring
- if not idx in self._rings:
- self._rings[idx] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(ring_i)))
-
- # Returning the ring at the given index.
- return GEOSGeometry(self._rings[idx], child=True)
+ # Returning the ring from the internal ring dictionary (have to
+ # add one to the index)
+ return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr)
#### Polygon Properties ####
@property
@@ -282,17 +354,18 @@ class Polygon(GEOSGeometry):
if n == -1: raise GEOSException, 'Error getting the number of interior rings.'
else: return n
- @property
- def exterior_ring(self):
+ def get_ext_ring(self):
"Gets the exterior ring of the Polygon."
- # Returns exterior ring
- self._rings[0] = GEOSPointer(lgeos.GEOSGetExteriorRing((self._ptr())))
- return GEOSGeometry(self._rings[0], child=True)
+ return GEOSGeometry(self._rings[0], parent=self._ptr)
- @property
- def shell(self):
- "Gets the shell (exterior ring) of the Polygon."
- return self.exterior_ring
+ def set_ext_ring(self):
+ "Sets the exterior ring of the Polygon."
+ # Sets the exterior ring
+ raise NotImplementedError
+
+ # properties for the exterior ring/shell
+ exterior_ring = property(get_ext_ring)
+ shell = property(get_ext_ring)
@property
def tuple(self):
diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py
index f0d8642e59..141d3fa55c 100644
--- a/django/contrib/gis/geos/libgeos.py
+++ b/django/contrib/gis/geos/libgeos.py
@@ -5,9 +5,7 @@
"""
from django.contrib.gis.geos.error import GEOSException
-from ctypes import \
- c_char_p, c_int, c_size_t, c_ubyte, pointer, addressof, \
- CDLL, CFUNCTYPE, POINTER, Structure
+from ctypes import c_char_p, c_int, pointer, CDLL, CFUNCTYPE, POINTER, Structure
import os, sys
# NumPy supported?
@@ -64,29 +62,55 @@ error_h = ERRORFUNC(error_h)
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
lgeos.initGEOS(notice_h, error_h)
+#### GEOS Geometry Pointer utilities. ####
+
+# Opaque GEOS geometry structure
+class GEOSGeom_t(Structure):
+ "Opaque structure used when arrays of geometries are needed as parameters."
+ pass
+# Pointer to opaque geometry structure
+GEOM_PTR = POINTER(GEOSGeom_t)
+# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection GEOS routines
+def get_pointer_arr(n):
+ "Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
+ GeomArr = GEOM_PTR * n
+ return GeomArr()
+
+#### GEOS Pointer object and routines ####
class GEOSPointer(object):
- """The GEOSPointer provides a layer of abstraction to accessing the values returned by
- GEOS geometry creation routines."""
+ """The GEOSPointer provides a layer of abstraction in accessing the values returned by
+ GEOS geometry creation routines. Memory addresses (integers) are kept in a C pointer,
+ which allows parent geometries to be 'nullified' if their children's memory is used
+ in construction of another geometry. Related coordinate sequence pointers are kept
+ in this object for the same reason."""
### Python 'magic' routines ###
- def __init__(self, ptr):
- "Given a ctypes pointer(c_int)"
- if isinstance(ptr, int):
- self._ptr = pointer(c_int(ptr))
+ def __init__(self, address, coordseq=0):
+ "Initializes on an address (an integer)."
+ if isinstance(address, int):
+ self._geom = pointer(c_int(address))
+ self._coordseq = pointer(c_int(coordseq))
else:
raise TypeError, 'GEOSPointer object must initialize with an integer.'
def __call__(self):
"""If the pointer is NULL, then an exception will be raised, otherwise the
- address value (a GEOSGeom_ptr) will be returned."""
+ address value (an integer) will be returned."""
if self.valid: return self.address
- else: raise GEOSException, 'GEOS pointer no longer valid (was the parent geometry deleted?)'
+ else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
+
+ def __bool__(self):
+ "Returns True when the GEOSPointer is valid."
+ return self.valid
+
+ def __str__(self):
+ return str(self.address)
### GEOSPointer Properties ###
@property
def address(self):
"Returns the address of the GEOSPointer (represented as an integer)."
- return self._ptr.contents.value
+ return self._geom.contents.value
@property
def valid(self):
@@ -94,13 +118,55 @@ class GEOSPointer(object):
if bool(self.address): return True
else: return False
+ ### Coordinate Sequence properties ###
+ def coordseq(self):
+ "If the coordinate sequence pointer is NULL (0), an exception will be raised."
+ if self.coordseq_valid: return self.coordseq_address
+ else: raise GEOSException, 'GEOS coordinate sequence pointer invalid (was this geometry or the parent geometry deleted or modified?)'
+
+ @property
+ def coordseq_address(self):
+ "Returns the address of the related coordinate sequence."
+ return self._coordseq.contents.value
+
+ @property
+ def coordseq_valid(self):
+ "Returns True if the coordinate sequence address is valid, False otherwise."
+ if bool(self.coordseq_address): return True
+ else: return False
+
### GEOSPointer Methods ###
- def set(self, address):
+ def set(self, address, coordseq=False):
"Sets this pointer with the new address (represented as an integer)"
if not isinstance(address, int):
raise TypeError, 'GEOSPointer must be set with an address (an integer).'
- self._ptr.contents = c_int(address)
+ if coordseq:
+ self._coordseq.contents = c_int(address)
+ else:
+ self._geom.contents = c_int(address)
def nullify(self):
- "Nullify this geometry pointer (set the address to 0)."
+ """Nullify this geometry pointer (set the address to 0). This does not delete
+ any memory, rather, it sets the GEOS pointer to a NULL address, to prevent
+ access to addressses of deleted objects."""
+ # Nullifying both the geometry and coordinate sequence pointer.
self.set(0)
+ self.set(0, coordseq=True)
+
+def init_from_geom(geom):
+ """During initialization of geometries from other geometries, this routine is
+ used to nullify any parent geometries (since they will now be missing memory
+ components) and to nullify the geometry itself to prevent future access.
+ Only the address (an integer) of the current geometry is returned for use in
+ initializing the new geometry."""
+ # First getting the memory address of the geometry.
+ address = geom._ptr()
+
+ # If the geometry is a child geometry, then the parent geometry pointer is
+ # nullified.
+ if geom._parent: geom._parent.nullify()
+
+ # Nullifying the geometry pointer
+ geom._ptr.nullify()
+
+ return address
diff --git a/django/contrib/gis/tests/geometries.py b/django/contrib/gis/tests/geometries.py
index 40b73e4737..7b5a2cce8f 100644
--- a/django/contrib/gis/tests/geometries.py
+++ b/django/contrib/gis/tests/geometries.py
@@ -109,17 +109,26 @@ multilinestrings = (TestGeom('MULTILINESTRING ((0 0, 0 100), (100 0, 100 100))',
topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0, -5.0 0.0))'),
TestGeom('POLYGON ((0.0 -5.0, 0.0 5.0, 10.0 5.0, 10.0 -5.0, 0.0 -5.0))')
),
+ (TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'),
+ TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
+ ),
)
intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'),
+ TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))')
)
union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'),
+ TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'),
)
diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'),
+ TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
)
+sdiff_geoms = ( TestGeom('MULTIPOLYGON (((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0)),((0 0,5 0,5 5,10 5,10 -5,0 -5,0 0)))'),
+ TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
+ )
relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),
TestGeom('MULTIPOINT(80 170, 140 120, 200 80, 80 70)'),
diff --git a/django/contrib/gis/tests/test_geos.py b/django/contrib/gis/tests/test_geos.py
index 103c789f55..5999b61409 100644
--- a/django/contrib/gis/tests/test_geos.py
+++ b/django/contrib/gis/tests/test_geos.py
@@ -1,9 +1,12 @@
import unittest
-from django.contrib.gis.geos import GEOSGeometry, GEOSException, Point, LineString, LinearRing, HAS_NUMPY
+from django.contrib.gis.geos import \
+ GEOSException, GEOSGeometryIndexError, \
+ GEOSGeometry, Point, LineString, LinearRing, Polygon, \
+ MultiPoint, MultiLineString, MultiPolygon, GeometryCollection, \
+ fromstr, HAS_NUMPY
from geometries import *
-if HAS_NUMPY:
- from numpy import array
+if HAS_NUMPY: from numpy import array
class GEOSTest(unittest.TestCase):
@@ -52,10 +55,12 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(p.z, pnt.z)
self.assertEqual(p.z, pnt.tuple[2], 9)
tup_args = (p.x, p.y, p.z)
+ set_tup = (2.71, 3.14, 5.23)
else:
self.assertEqual(False, pnt.hasz)
self.assertEqual(None, pnt.z)
tup_args = (p.x, p.y)
+ set_tup = (2.71, 3.14)
# Centroid operation on point should be point itself
self.assertEqual(p.centroid, pnt.centroid.tuple)
@@ -71,6 +76,11 @@ class GEOSTest(unittest.TestCase):
pnt.x = 2.71
self.assertEqual(3.14, pnt.y)
self.assertEqual(2.71, pnt.x)
+
+ # Setting via the tuple property
+ pnt.tuple = set_tup
+ self.assertEqual(set_tup, pnt.tuple)
+
prev = pnt # setting the previous geometry
def test02b_multipoints(self):
@@ -83,6 +93,7 @@ class GEOSTest(unittest.TestCase):
self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
+ self.assertRaises(GEOSGeometryIndexError, mpnt.__getitem__, len(mpnt))
self.assertEqual(mp.centroid, mpnt.centroid.tuple)
self.assertEqual(mp.points, tuple(m.tuple for m in mpnt))
for p in mpnt:
@@ -107,17 +118,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
self.assertEqual(False, ls == prev)
-
+ self.assertRaises(GEOSGeometryIndexError, ls.__getitem__, len(ls))
prev = ls
# Creating a LineString from a tuple, list, and numpy array
- ls2 = LineString(ls.tuple)
- self.assertEqual(ls, ls2)
- ls3 = LineString([list(tup) for tup in ls.tuple])
- self.assertEqual(ls, ls3)
- if HAS_NUMPY:
- ls4 = LineString(array(ls.tuple))
- self.assertEqual(ls, ls4)
+ self.assertEqual(ls, LineString(ls.tuple)) # tuple
+ self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
+ self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
+ self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
+ if HAS_NUMPY: self.assertEqual(ls, LineString(array(ls.tuple))) # as numpy array
def test03b_multilinestring(self):
"Testing MultiLineString objects."
@@ -139,7 +148,11 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(ls.geom_typeid, 1)
self.assertEqual(ls.empty, False)
- def test04a_linearring(self):
+ self.assertRaises(GEOSGeometryIndexError, ml.__getitem__, len(ml))
+ self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
+ self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
+
+ def test04_linearring(self):
"Testing LinearRing objects."
for rr in linearrings:
lr = GEOSGeometry(rr.wkt)
@@ -150,13 +163,10 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(False, lr.empty)
# Creating a LinearRing from a tuple, list, and numpy array
- lr2 = LinearRing(lr.tuple)
- self.assertEqual(lr, lr2)
- lr3 = LinearRing([list(tup) for tup in lr.tuple])
- self.assertEqual(lr, lr3)
- if HAS_NUMPY:
- lr4 = LineString(array(lr.tuple))
- self.assertEqual(lr, lr4)
+ self.assertEqual(lr, LinearRing(lr.tuple))
+ self.assertEqual(lr, LinearRing(*lr.tuple))
+ self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
+ if HAS_NUMPY: self.assertEqual(lr, LineString(array(lr.tuple)))
def test05a_polygons(self):
"Testing Polygon objects."
@@ -180,6 +190,7 @@ class GEOSTest(unittest.TestCase):
# Testing the geometry equivalence
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
+ self.assertEqual(True, poly != prev)
# Testing the exterior ring
ring = poly.exterior_ring
@@ -194,6 +205,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(ring.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2)
+ # Testing polygon construction.
+ self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
+ self.assertRaises(TypeError, Polygon, 'foo')
+
+ rings = tuple(r.clone() for r in poly)
+ self.assertEqual(poly, Polygon(rings[0], rings[1:]))
+ self.assertEqual(poly.wkt, Polygon(*tuple(r.clone() for r in poly)).wkt)
+ self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
+
# Setting the second point of the first ring (which should set the
# first point of the polygon).
prev = poly.clone() # Using clone() to get a copy of the current polygon
@@ -221,10 +241,13 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(mp.num_geom, mpoly.num_geom)
self.assertEqual(mp.n_p, mpoly.num_coords)
self.assertEqual(mp.num_geom, len(mpoly))
+ self.assertRaises(GEOSGeometryIndexError, mpoly.__getitem__, len(mpoly))
for p in mpoly:
self.assertEqual(p.geom_type, 'Polygon')
self.assertEqual(p.geom_typeid, 3)
self.assertEqual(p.valid, True)
+ self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
+
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
def test06_memory_hijinks(self):
@@ -254,7 +277,7 @@ class GEOSTest(unittest.TestCase):
self.assertRaises(GEOSException, str, ring2)
#### Memory issues with geometries from Geometry Collections
- mp = GEOSGeometry('MULTIPOINT(85 715, 235 1400, 4620 1711)')
+ mp = fromstr('MULTIPOINT(85 715, 235 1400, 4620 1711)')
# Getting the points
pts = [p for p in mp]
@@ -278,7 +301,82 @@ class GEOSTest(unittest.TestCase):
# after it has been deleted.
del mp
for p in pts:
- self.assertRaises(GEOSException, str, p)
+ self.assertRaises(GEOSException, str, p) # tests p's geometry pointer
+ self.assertRaises(GEOSException, p.get_tuple) # tests p's coordseq pointer
+
+ # Now doing this with a GeometryCollection
+ polywkt = polygons[3].wkt # a 'real life' polygon.
+ lrwkt = linearrings[0].wkt # a 'real life' linear ring
+ poly = fromstr(polywkt)
+ linring = fromstr(lrwkt)
+
+ # Pulling out the shell and cloning our initial geometries for later comparison.
+ shell = poly.shell
+ polyc = poly.clone()
+ linringc = linring.clone()
+
+ gc = GeometryCollection(poly, linring, Point(5, 23))
+
+ # Should no longer be able to access these variables
+ self.assertRaises(GEOSException, str, poly)
+ self.assertRaises(GEOSException, str, shell)
+ self.assertRaises(GEOSException, str, linring)
+
+ r1 = gc[1] # pulling out the ring
+ pnt = gc[2] # pulling the point from the geometry collection
+
+ # Now lets create a MultiPolygon from the geometry collection components
+ mpoly = MultiPolygon(gc[0], Polygon(gc[1]))
+ self.assertEqual(polyc.wkt, mpoly[0].wkt)
+
+ # Should no longer be able to access the geometry collection directly
+ self.assertRaises(GEOSException, len, gc)
+
+ # BUT, should still be able to access the Point we obtained earlier, but
+ # not the linear ring (since it is now part of the MultiPolygon.
+ self.assertEqual(5, pnt.x)
+ self.assertEqual(23, pnt.y)
+
+ # __len__ is called on the coordinate sequence pointer -- make sure its nullified as well
+ self.assertRaises(GEOSException, len, r1)
+ self.assertRaises(GEOSException, str, r1)
+
+ # Can't access point after deletion of parent geometry.
+ del gc
+ self.assertRaises(GEOSException, str, pnt)
+
+ # Cleaning up.
+ del polyc
+ del mpoly
+
+ #### Memory issues with creating geometries from coordinate sequences within other geometries
+
+ # Creating the initial polygon from the following tuples, and then pulling out
+ # the individual rings.
+ ext_tup = ((0, 0), (0, 7), (7, 7), (7, 0), (0, 0))
+ itup1 = ((1, 1), (1, 2), (2, 2), (2, 1), (1, 1))
+ itup2 = ((4, 4), (4, 5), (5, 5), (5, 4), (4, 4))
+ poly1 = Polygon(LinearRing(ext_tup), LinearRing(itup1), LinearRing(itup2))
+ shell = poly1.shell
+ hole1 = poly1[1]
+ hole2 = poly1[2]
+
+ # Creating a Polygon from the shell and one of the holes
+ poly2 = Polygon(shell, hole1)
+
+ # We should no longer be able to access the original Polygon, its
+ # shell or its first internal ring.
+ self.assertRaises(GEOSException, str, poly1)
+ self.assertRaises(GEOSException, str, shell)
+ self.assertRaises(GEOSException, str, hole1)
+
+ # BUT, the second hole is still accessible.
+ self.assertEqual(itup2, hole2.tuple)
+
+ # Deleting the first polygon, and ensuring that
+ # the second hole is now gone for good.
+ del poly1
+ self.assertRaises(GEOSException, str, hole2)
def test08_coord_seq(self):
"Testing Coordinate Sequence objects."
@@ -309,7 +407,6 @@ class GEOSTest(unittest.TestCase):
def test09_relate_pattern(self):
"Testing relate() and relate_pattern()."
-
g = GEOSGeometry('POINT (0 0)')
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
@@ -333,6 +430,7 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, a.intersects(b))
i2 = a.intersection(b)
self.assertEqual(i1, i2)
+ self.assertEqual(i1, a & b)
def test11_union(self):
"Testing union()."
@@ -343,6 +441,7 @@ class GEOSTest(unittest.TestCase):
u1 = GEOSGeometry(union_geoms[i].wkt)
u2 = a.union(b)
self.assertEqual(u1, u2)
+ self.assertEqual(u1, a | b) # Union ('|') operator
def test12_difference(self):
"Testing difference()."
@@ -353,8 +452,20 @@ class GEOSTest(unittest.TestCase):
d1 = GEOSGeometry(diff_geoms[i].wkt)
d2 = a.difference(b)
self.assertEqual(d1, d2)
+ self.assertEqual(d1, a - b) # Difference ('-') operator
+
+ def test13_symdifference(self):
+ "Testing sym_difference()."
+ for i in xrange(len(topology_geoms)):
+ g_tup = topology_geoms[i]
+ a = GEOSGeometry(g_tup[0].wkt)
+ b = GEOSGeometry(g_tup[1].wkt)
+ d1 = GEOSGeometry(sdiff_geoms[i].wkt)
+ d2 = a.sym_difference(b)
+ self.assertEqual(d1, d2)
+ self.assertEqual(d1, a ^ b) # Symmetric difference ('^') operator
- def test13_buffer(self):
+ def test14_buffer(self):
"Testing buffer()."
for i in xrange(len(buffer_geoms)):
g_tup = buffer_geoms[i]
diff --git a/django/contrib/gis/utils/LayerMapping.py b/django/contrib/gis/utils/LayerMapping.py
index 8c93590ae2..f5d3758860 100644
--- a/django/contrib/gis/utils/LayerMapping.py
+++ b/django/contrib/gis/utils/LayerMapping.py
@@ -228,7 +228,7 @@ class LayerMapping:
"A class that maps OGR Layers to Django Models."
def __init__(self, model, data, mapping, layer=0, source_srs=None):
- "Takes the Django model, the mapping (dictionary), and the SHP file."
+ "Takes the Django model, the data source, and the mapping (dictionary)"
# Getting the field names and types from the model
fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)