summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2007-08-30 01:38:44 +0000
committerJustin Bronn <jbronn@gmail.com>2007-08-30 01:38:44 +0000
commitf4203ef757e8bd077b6c77345cc984a4074debe3 (patch)
tree75a560ab58cbb641441bab9a9105c1e797a21224
parentcb64f5375cc1884226cad05b671e8660214a4a92 (diff)
gis: geos: Memory-management refactor, changes include:
(1) Moved GEOSPointer to its own module, and now tracks children geometries. (2) Fixed 'deep-indexing' issues for nested rings in GeometryCollections/MultiPolygons (e.g., mpoly[0][1] = new_ring). (3) Added simplify() -- simplifies geometries using the Douglas-Peucker algorithm. (4) Conformed docstrings to Django coding style. (5) GEOSGeometry no longer uses `parent` and `input_type` keywords. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6024 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/contrib/gis/geos/__init__.py1
-rw-r--r--django/contrib/gis/geos/base.py249
-rw-r--r--django/contrib/gis/geos/collections.py75
-rw-r--r--django/contrib/gis/geos/coordseq.py29
-rw-r--r--django/contrib/gis/geos/error.py11
-rw-r--r--django/contrib/gis/geos/geometries.py142
-rw-r--r--django/contrib/gis/geos/libgeos.py103
-rw-r--r--django/contrib/gis/geos/pointer.py265
-rw-r--r--django/contrib/gis/tests/test_geos.py167
9 files changed, 670 insertions, 372 deletions
diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py
index 67eda8de26..ca9691e243 100644
--- a/django/contrib/gis/geos/__init__.py
+++ b/django/contrib/gis/geos/__init__.py
@@ -33,6 +33,7 @@ from django.contrib.gis.geos.base import GEOSGeometry
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
+from django.contrib.gis.geos.libgeos import geos_version
def fromstr(wkt_or_hex, **kwargs):
"Given a string value (wkt or hex), returns a GEOSGeometry object."
diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py
index 4302fd0e45..e36db67ae0 100644
--- a/django/contrib/gis/geos/base.py
+++ b/django/contrib/gis/geos/base.py
@@ -1,8 +1,7 @@
"""
This module contains the 'base' GEOSGeometry object -- all GEOS geometries
- inherit from this object.
+ inherit from this object.
"""
-
# ctypes and types dependencies.
from ctypes import \
byref, string_at, create_string_buffer, pointer, \
@@ -11,11 +10,10 @@ from types import StringType, UnicodeType, IntType, FloatType
# Python and GEOS-related dependencies.
import re
-from warnings import warn
-from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY, ISQLQuote
-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
+from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
+from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY, ISQLQuote
+from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
# to prevent potentially malicious input from reaching the underlying C
@@ -26,28 +24,23 @@ wkt_regex = re.compile(r'^(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTIL
class GEOSGeometry(object):
"A class that, generally, encapsulates a GEOS geometry."
+ # Initially, all geometries use a NULL pointer.
+ _ptr = NULL_GEOM
+
#### Python 'magic' routines ####
- def __init__(self, geo_input, input_type=False, parent=None, srid=None):
- """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 `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.
+ def __init__(self, geo_input, srid=None):
"""
+ The base constructor for GEOS geometry objects, and may take the following
+ string inputs: WKT and HEXEWKB (a PostGIS-specific canonical form).
- # Initially, setting the pointer to NULL
- self._ptr = GEOSPointer(0)
+ The `srid` keyword is used to specify the Source Reference Identifier
+ (SRID) number for this Geometry. If not set, the SRID will be None.
+ """
if isinstance(geo_input, UnicodeType):
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
geo_input = geo_input.encode('ascii')
-
if isinstance(geo_input, StringType):
- if input_type: warn('input_type keyword is deprecated')
-
if hex_regex.match(geo_input):
# If the regex matches, the geometry is in HEX form.
sz = c_size_t(len(geo_input))
@@ -58,47 +51,41 @@ class GEOSGeometry(object):
g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input))
else:
raise GEOSException, 'given string input "%s" unrecognized as WKT or HEXEWKB.' % geo_input
-
elif isinstance(geo_input, (IntType, GEOSPointer)):
- # When the input is either a raw pointer value (an integer), or a GEOSPointer object.
+ # When the input is either a memory address (an integer), or a
+ # GEOSPointer object.
g = geo_input
else:
# Invalid geometry type.
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 input
- if isinstance(g, GEOSPointer): self._ptr = g
- else: self._ptr.set(g) # Otherwise, set with the address
+ # Setting the pointer object with a valid pointer.
+ self._ptr = GEOSPointer(g)
else:
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
- # 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.).
- if isinstance(parent, GEOSPointer):
- self._parent = parent
- else:
- self._parent = GEOSPointer(0)
-
# Setting the SRID, if given.
if srid and isinstance(srid, int): self.srid = srid
# 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()
+ # Setting the coordinate sequence for the geometry (will be None on
+ # geometries that do not have coordinate sequences)
+ self._set_cs()
- # Extra setup needed for Geometries that may be parents.
+ # _populate() needs to be called for parent Geometries.
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
def __del__(self):
- "Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent."
- #print 'base: 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())
+ """
+ Destroys this Geometry; in other words, frees the memory used by the
+ GEOS C++ object -- but only if the pointer is not a child Geometry
+ (e.g., don't delete the LinearRings spawned from a Polygon).
+ """
+ #print 'base: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid)
+ if not self._ptr.child: self._ptr.destroy()
def __str__(self):
"WKT is used for the string representation."
@@ -156,29 +143,38 @@ class GEOSGeometry(object):
# g1 ^= g2
def __ixor__(self, other):
- "Reassigns this Geometry to the symmetric difference of this Geometry and the other."
+ """
+ Reassigns this Geometry to the symmetric difference of this Geometry
+ and the other.
+ """
return self.sym_difference(other)
+ #### Internal GEOSPointer-related routines. ####
def _nullify(self):
- """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."""
+ """
+ Returns the address of this Geometry, and nullifies any related pointers.
+ This function is called if this Geometry is used in the initialization
+ of another Geometry.
+ """
# First getting the memory address of the geometry.
address = self._ptr()
# If the geometry is a child geometry, then the parent geometry pointer is
# nullified.
- if self._parent: self._parent.nullify()
-
+ if self._ptr.child:
+ p = self._ptr.parent
+ # If we have a grandchild (a LinearRing from a MultiPolygon or
+ # GeometryCollection), then nullify the collection as well.
+ if p.child: p.parent.nullify()
+ p.nullify()
+
# Nullifying the geometry pointer
self._ptr.nullify()
return address
def _reassign(self, new_geom):
- "Internal routine for reassigning internal pointer to a new geometry."
+ "Reassigns the internal pointer to that of the new Geometry."
# Only can re-assign when given a pointer or a geometry.
if not isinstance(new_geom, (GEOSPointer, GEOSGeometry)):
raise TypeError, 'cannot reassign geometry on given type: %s' % type(new_geom)
@@ -186,8 +182,8 @@ class GEOSGeometry(object):
# Re-assigning the internal GEOSPointer to the new geometry, nullifying
# the new Geometry in the process.
- if isinstance(new_geom, GEOSGeometry): self._ptr.set(new_geom._nullify())
- else: self._ptr = new_geom
+ if isinstance(new_geom, GEOSPointer): self._ptr = new_geom
+ else: self._ptr = GEOSPointer(new_geom._nullify())
# The new geometry class may be different from the original, so setting
# the __class__ and populating the internal geometry or ring dictionary.
@@ -200,10 +196,10 @@ class GEOSGeometry(object):
if proto == ISQLQuote:
return self
else:
- raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
+ raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
def getquoted(self):
- "Returns a properly quoted string for use in PostgresSQL/PostGIS."
+ "Returns a properly quoted string for use in PostgreSQL/PostGIS."
# Using ST_GeomFromText(), corresponds to SQL/MM ISO standard.
return "ST_GeomFromText('%s', %s)" % (self.wkt, self.srid or -1)
@@ -217,10 +213,11 @@ class GEOSGeometry(object):
else:
return False
- def _get_cs(self):
- "Gets the coordinate sequence for this Geometry."
+ def _set_cs(self):
+ "Sets the coordinate sequence for this Geometry."
if self.has_cs:
- self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True)
+ if not self._ptr.coordseq_valid:
+ self._ptr.set_coordseq(lgeos.GEOSGeom_getCoordSeq(self._ptr()))
self._cs = GEOSCoordSeq(self._ptr, self.hasz)
else:
self._cs = None
@@ -272,16 +269,20 @@ class GEOSGeometry(object):
## 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."""
+ """
+ Returns the result, or raises an exception for the given unary predicate
+ function.
+ """
val = func(self._ptr())
if val == 0: return False
elif val == 1: return True
else: raise GEOSException, '%s: exception occurred.' % func.__name__
def _binary_predicate(self, func, other, *args):
- """Returns the result, or raises an exception for the given binary
- predicate function."""
+ """
+ Returns the result, or raises an exception for the given binary
+ predicate function.
+ """
if not isinstance(other, GEOSGeometry):
raise TypeError, 'Binary predicate operation ("%s") requires another GEOSGeometry instance.' % func.__name__
val = func(self._ptr(), other._ptr(), *args)
@@ -292,7 +293,10 @@ 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
@@ -317,20 +321,26 @@ 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 Geometries match the elements in pattern."""
+ """
+ Returns true if the elements in the DE-9IM intersection matrix for 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 Geometries
- 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 Geometries
- 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):
@@ -338,14 +348,18 @@ 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 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)."""
+ """
+ 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 Geometries
- 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):
@@ -353,18 +367,24 @@ 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 Geometries
- is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves)."""
+ """
+ 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 Geometries
- 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 Geometries are exactly equal, up to a
- specified tolerance."""
+ """
+ Returns true if the two Geometries are exactly equal, up to a
+ specified tolerance.
+ """
return self._binary_predicate(lgeos.GEOSEqualsExact, other,
c_double(tolerance))
@@ -383,12 +403,12 @@ class GEOSGeometry(object):
#### Output Routines ####
@property
def wkt(self):
- "Returns the WKT of the Geometry."
+ "Returns the WKT (Well-Known Text) of the Geometry."
return string_at(lgeos.GEOSGeomToWKT(self._ptr()))
@property
def hex(self):
- "Returns the WKBHEX of the Geometry."
+ "Returns the HEXEWKB of the Geometry."
sz = c_size_t()
h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz))
return string_at(h, sz.value)
@@ -401,21 +421,26 @@ class GEOSGeometry(object):
#### Topology Routines ####
def _unary_topology(self, func, *args):
- """Returns a GEOSGeometry for the given unary (takes only one Geomtery
- as a paramter) topological operation."""
+ """
+ Returns a GEOSGeometry for the given unary (takes only one Geomtery
+ as a paramter) topological operation.
+ """
return GEOSGeometry(func(self._ptr(), *args), srid=self.srid)
def _binary_topology(self, func, other, *args):
- """Returns a GEOSGeometry for the given binary (takes two Geometries
- as parameters) topological operation."""
+ """
+ Returns a GEOSGeometry for the given binary (takes two Geometries
+ as parameters) topological operation.
+ """
return GEOSGeometry(func(self._ptr(), other._ptr(), *args), srid=self.srid)
def buffer(self, width, quadsegs=8):
- """Returns a geometry that represents all points whose distance from this
- Geometry is less than or equal to distance. Calculations are in the
- Spatial Reference System of this Geometry. The optional third parameter sets
- the number of segment used to approximate a quarter circle (defaults to 8).
- (Text from PostGIS documentation at ch. 6.1.3)
+ """
+ Returns a geometry that represents all points whose distance from this
+ Geometry is less than or equal to distance. Calculations are in the
+ Spatial Reference System of this Geometry. The optional third parameter sets
+ the number of segment used to approximate a quarter circle (defaults to 8).
+ (Text from PostGIS documentation at ch. 6.1.3)
"""
if not isinstance(width, (FloatType, IntType)):
raise TypeError, 'width parameter must be a float'
@@ -430,9 +455,11 @@ class GEOSGeometry(object):
@property
def centroid(self):
- """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)."""
+ """
+ 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)
@property
@@ -442,8 +469,10 @@ class GEOSGeometry(object):
@property
def convex_hull(self):
- """Returns the smallest convex Polygon that contains all the points
- in the Geometry."""
+ """
+ Returns the smallest convex Polygon that contains all the points
+ in the Geometry.
+ """
return self._unary_topology(lgeos.GEOSConvexHull)
@property
@@ -451,8 +480,16 @@ class GEOSGeometry(object):
"Computes an interior point of this Geometry."
return self._unary_topology(lgeos.GEOSPointOnSurface)
+ def simplify(self, tolerance=0.0):
+ """
+ Returns the Geometry, simplified using the Douglas-Peucker algorithm
+ to the specified tolerance (higher tolerance => less points). If no
+ tolerance provided, defaults to 0.
+ """
+ return self._unary_topology(lgeos.GEOSSimplify, c_double(tolerance))
+
def relate(self, other):
- "Returns the DE-9IM intersection matrix for this geometry and the other."
+ "Returns the DE-9IM intersection matrix for this Geometry and the other."
return string_at(lgeos.GEOSRelate(self._ptr(), other._ptr()))
def difference(self, other):
@@ -461,8 +498,10 @@ class GEOSGeometry(object):
return self._binary_topology(lgeos.GEOSDifference, other)
def sym_difference(self, other):
- """Returns a set combining the points in this Geometry not in other,
- and the points in other not in this Geometry."""
+ """
+ Returns a set combining the points in this Geometry not in other,
+ and the points in other not in this Geometry.
+ """
return self._binary_topology(lgeos.GEOSSymDifference, other)
def intersection(self, other):
@@ -483,9 +522,11 @@ class GEOSGeometry(object):
else: return a.value
def distance(self, other):
- """Returns the distance between the closest points on this Geometry
- and the other. Units will be in those of the coordinate system. of
- the Geometry."""
+ """
+ Returns the distance between the closest points on this Geometry
+ and the other. Units will be in those of the coordinate system of
+ the Geometry.
+ """
if not isinstance(other, GEOSGeometry):
raise TypeError, 'distance() works only on other GEOS Geometries.'
dist = c_double()
@@ -495,8 +536,10 @@ class GEOSGeometry(object):
@property
def length(self):
- """Returns the length of this Geometry (e.g., 0 for point, or the
- circumfrence of a Polygon)."""
+ """
+ Returns the length of this Geometry (e.g., 0 for point, or the
+ circumfrence of a Polygon).
+ """
l = c_double()
status = lgeos.GEOSLength(self._ptr(), byref(l))
if status != 1: return None
diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py
index bf5dd76158..2575e5b5df 100644
--- a/django/contrib/gis/geos/collections.py
+++ b/django/contrib/gis/geos/collections.py
@@ -1,13 +1,14 @@
"""
- This module houses the Geometry Collection objects:
- GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
+ This module houses the Geometry Collection objects:
+ GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
"""
from ctypes import c_int, c_uint, byref, cast
from types import TupleType, ListType
-from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, GEOM_PTR
from django.contrib.gis.geos.base import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
+from django.contrib.gis.geos.libgeos import lgeos, get_pointer_arr, GEOM_PTR
+from django.contrib.gis.geos.pointer import GEOSPointer
class GeometryCollection(GEOSGeometry):
_allowed = (Point, LineString, LinearRing, Polygon)
@@ -15,16 +16,14 @@ class GeometryCollection(GEOSGeometry):
def __init__(self, *args, **kwargs):
"Initializes a Geometry Collection from a sequence of Geometry objects."
- # Setting up the collection for creation
- self._ptr = GEOSPointer(0) # Initially NULL
- self._geoms = {}
- self._parent = None
# 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
+ if len(args) == 1:
+ # If only one geometry provided or a list of geometries is provided
+ # in the first argument.
if isinstance(args[0], (TupleType, ListType)):
init_geoms = args[0]
else:
@@ -36,40 +35,47 @@ class GeometryCollection(GEOSGeometry):
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
+ # Creating the geometry pointer array, and populating each element in
+ # the array with the address of the Geometry returned by _nullify().
ngeom = len(init_geoms)
geoms = get_pointer_arr(ngeom)
-
- # Incrementing through each input geometry.
for i in xrange(ngeom):
geoms[i] = cast(init_geoms[i]._nullify(), 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)), **kwargs)
+ # Calling the parent class, using the pointer returned from the
+ # GEOS createCollection() factory.
+ addr = lgeos.GEOSGeom_createCollection(c_int(self._typeid),
+ byref(geoms), c_uint(ngeom))
+ super(GeometryCollection, self).__init__(addr, **kwargs)
def __del__(self):
"Overloaded deletion method for Geometry Collections."
- #print 'collection: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
+ #print 'collection: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.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()
+ # Nullifying pointers to internal Geometries, preventing any
+ # attempted future access.
+ for g in self._ptr: g.nullify()
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()
+ # Internal memory has become part of other Geometry objects; must
+ # delete the internal objects which are still valid individually,
+ # because calling the destructor on the entire geometry will result
+ # in an attempted deletion of NULL pointers for the missing
+ # components (which may crash Python).
+ for g in self._ptr:
+ if len(g) > 0:
+ # The collection geometry is a Polygon, destroy any leftover
+ # LinearRings.
+ for r in g: r.destroy()
+ g.destroy()
+
super(GeometryCollection, self).__del__()
def __getitem__(self, index):
"Returns the Geometry from this Collection at the given index (0-based)."
# Checking the index and returning the corresponding GEOS geometry.
self._checkindex(index)
- return GEOSGeometry(self._geoms[index], parent=self._ptr, srid=self.srid)
+ return GEOSGeometry(self._ptr[index], srid=self.srid)
def __setitem__(self, index, geom):
"Sets the Geometry at the specified index."
@@ -105,14 +111,23 @@ class GeometryCollection(GEOSGeometry):
def _nullify(self):
"Overloaded from base method to nullify geometry references in this Collection."
# Nullifying the references to the internal Geometry objects from this Collection.
- for k in self._geoms: self._geoms[k].nullify()
+ for g in self._ptr: g.nullify()
return super(GeometryCollection, self)._nullify()
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)))
+ "Internal routine that populates the internal children geometries list."
+ ptr_list = []
+ for i in xrange(len(self)):
+ # Getting the geometry pointer for the geometry at the index.
+ geom_ptr = lgeos.GEOSGetGeometryN(self._ptr(), c_int(i))
+
+ # Adding the coordinate sequence to the list, or using None if the
+ # collection Geometry doesn't support coordinate sequences.
+ if lgeos.GEOSGeomTypeId(geom_ptr) in (0, 1, 2):
+ ptr_list.append((geom_ptr, lgeos.GEOSGeom_getCoordSeq(geom_ptr)))
+ else:
+ ptr_list.append((geom_ptr, None))
+ self._ptr.set_children(ptr_list)
@property
def kml(self):
diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py
index 54a6abf009..33beac5548 100644
--- a/django/contrib/gis/geos/coordseq.py
+++ b/django/contrib/gis/geos/coordseq.py
@@ -1,15 +1,15 @@
-from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
+"""
+ This module houses the GEOSCoordSeq object, and is used internally
+ by GEOSGeometry to house the actual coordinates of the Point,
+ LineString, and LinearRing geometries.
+"""
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
+from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY
+from django.contrib.gis.geos.pointer import GEOSPointer
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
- by GEOSGeometry to house the actual coordinates of the Point,
- LineString, and LinearRing geometries.
-"""
-
class GEOSCoordSeq(object):
"The internal representation of a list of coordinates inside a Geometry."
@@ -31,18 +31,18 @@ class GEOSCoordSeq(object):
return int(self.size)
def __str__(self):
- "The string representation of the coordinate sequence."
+ "Returns the string representation of the coordinate sequence."
return str(self.tuple)
def __getitem__(self, index):
- "Can use the index [] operator to get coordinate sequence at an index."
+ "Returns the coordinate sequence value at the given index."
coords = [self.getX(index), self.getY(index)]
if self.dims == 3 and self._z:
coords.append(self.getZ(index))
return tuple(coords)
def __setitem__(self, index, value):
- "Can use the index [] operator to set coordinate sequence at an index."
+ "Sets the coordinate sequence value at the given index."
# Checking the input value
if isinstance(value, (ListType, TupleType)):
pass
@@ -66,7 +66,7 @@ class GEOSCoordSeq(object):
#### Internal Routines ####
def _checkindex(self, index):
- "Checks the index."
+ "Checks the given index."
sz = self.size
if (sz < 1) or (index < 0) or (index >= sz):
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
@@ -78,7 +78,7 @@ class GEOSCoordSeq(object):
#### Ordinate getting and setting routines ####
def getOrdinate(self, dimension, index):
- "Gets the value for the given dimension and index."
+ "Returns the value for the given dimension and index."
self._checkindex(index)
self._checkdim(dimension)
@@ -152,7 +152,10 @@ class GEOSCoordSeq(object):
@property
def hasz(self):
- "Inherits this from the parent geometry."
+ """
+ Returns whether this coordinate sequence is 3D. This property value is
+ inherited from the parent Geometry.
+ """
return self._z
### Other Methods ###
diff --git a/django/contrib/gis/geos/error.py b/django/contrib/gis/geos/error.py
index 219a71929c..460db5e57b 100644
--- a/django/contrib/gis/geos/error.py
+++ b/django/contrib/gis/geos/error.py
@@ -1,15 +1,20 @@
+"""
+ This module houses the GEOS exceptions, specifically, GEOSException and
+ GEOSGeometryIndexError.
+"""
class GEOSException(Exception):
"The base GEOS exception, indicates a GEOS-related error."
pass
class GEOSGeometryIndexError(GEOSException, KeyError):
- """This exception is raised when an invalid index is encountered, and has
+ """
+ This exception is raised when an invalid index is encountered, and has
the 'silent_variable_feature' attribute set to true. This ensures that
django's templates proceed to use the next lookup type gracefully when
an Exception is raised. Fixes ticket #4740.
"""
# "If, during the method lookup, a method raises an exception, the exception
- # will be propagated, unless the exception has an attribute silent_variable_failure
- # whose value is True." -- django template docs.
+ # will be propagated, unless the exception has an attribute
+ # `silent_variable_failure` whose value is True." -- Django template docs.
silent_variable_failure = True
diff --git a/django/contrib/gis/geos/geometries.py b/django/contrib/gis/geos/geometries.py
index 2a5b5df600..cc4bdc58ea 100644
--- a/django/contrib/gis/geos/geometries.py
+++ b/django/contrib/gis/geos/geometries.py
@@ -3,36 +3,33 @@
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, get_pointer_arr, GEOM_PTR, HAS_NUMPY
from django.contrib.gis.geos.base import GEOSGeometry
+from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
+from django.contrib.gis.geos.libgeos import lgeos, get_pointer_arr, GEOM_PTR, HAS_NUMPY
+from django.contrib.gis.geos.pointer import GEOSPointer
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
if HAS_NUMPY: from numpy import ndarray, array
class Point(GEOSGeometry):
def __init__(self, x, y=None, z=None, srid=None):
- """The Point object may be initialized with either a tuple, or individual
- parameters. For example:
+ """
+ The Point object may be initialized with either a tuple, or individual
+ parameters. For example:
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
"""
- # Setting-up for Point Creation
- self._ptr = GEOSPointer(0) # Initially NULL
- self._parent = None
-
if isinstance(x, (TupleType, ListType)):
- # 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)
if ndim < 2 or ndim > 3:
raise TypeError, 'Invalid sequence parameter: %s' % str(x)
coords = x
elif isinstance(x, (IntType, FloatType)) and isinstance(y, (IntType, FloatType)):
- # 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, (IntType, FloatType)):
ndim = 3
coords = [x, y, z]
@@ -42,22 +39,17 @@ class Point(GEOSGeometry):
else:
raise TypeError, 'Invalid parameters given for Point initialization.'
- # Creating the coordinate sequence
+ # Creating the coordinate sequence, and setting X, Y, [Z]
cs = create_cs(c_uint(1), c_uint(ndim))
-
- # Setting the X
status = lgeos.GEOSCoordSeq_setX(cs, c_uint(0), c_double(coords[0]))
if not status: raise GEOSException, 'Could not set X during Point initialization.'
-
- # Setting the Y
status = lgeos.GEOSCoordSeq_setY(cs, c_uint(0), c_double(coords[1]))
if not status: raise GEOSException, 'Could not set Y during Point initialization.'
-
- # Setting the Z
if ndim == 3:
status = lgeos.GEOSCoordSeq_setZ(cs, c_uint(0), c_double(coords[2]))
- # Initializing from the geometry, and getting a Python object
+ # Initializing using the address returned from the GEOS
+ # createPoint factory.
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs), srid=srid)
def __len__(self):
@@ -125,9 +117,10 @@ class LineString(GEOSGeometry):
#### Python 'magic' routines ####
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.
+ """
+ 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))
@@ -135,11 +128,7 @@ class LineString(GEOSGeometry):
ls = LineString(array([(1, 1), (2, 2)]))
ls = LineString(Point(1, 1), Point(2, 2))
"""
- # Setting up for LineString creation
- self._ptr = GEOSPointer(0) # Initially NULL
- self._parent = None
-
- # If only one argument was provided, then set the coords array appropriately
+ # If only one argument provided, set the coords array appropriately
if len(args) == 1: coords = args[0]
else: coords = args
@@ -166,10 +155,9 @@ class LineString(GEOSGeometry):
else:
raise TypeError, 'Invalid initialization input for LineStrings.'
- # Creating the coordinate sequence
+ # Creating a coordinate sequence object because it is easier to
+ # set the points using GEOSCoordSeq.__setitem__().
cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim))), z=bool(ndim==3))
-
- # 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
@@ -184,7 +172,8 @@ class LineString(GEOSGeometry):
# If SRID was passed in with the keyword arguments
srid = kwargs.get('srid', None)
- # Calling the base geometry initialization with the returned pointer from the function.
+ # Calling the base geometry initialization with the returned pointer
+ # from the function.
super(LineString, self).__init__(func(cs._ptr.coordseq()), srid=srid)
def __getitem__(self, index):
@@ -214,9 +203,11 @@ class LineString(GEOSGeometry):
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(len(self))] # constructing the list, using the function
+ """
+ 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(len(self))]
if HAS_NUMPY: return array(lst) # ARRRR!
else: return lst
@@ -251,16 +242,16 @@ class LinearRing(LineString):
class Polygon(GEOSGeometry):
def __init__(self, *args, **kwargs):
- """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.
+ """
+ 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))
+ Below are some examples of initialization, 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._parent = None
- self._rings = {}
if not args:
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
@@ -293,26 +284,28 @@ class Polygon(GEOSGeometry):
def __del__(self):
"Overloaded deletion method for Polygons."
- #print 'polygon: 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()
- elif not self._parent:
- # 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. Not performed on
- # children Polygons from MultiPolygon or GeometryCollection objects.
- for k in self._rings:
- if self._rings[k].valid:
- lgeos.GEOSGeom_destroy(self._rings[k].address)
- self._rings[k].nullify()
+ #print 'polygon: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid)
+ # Not performed on children Polygons from MultiPolygon or GeometryCollection objects.
+ if not self._ptr.child:
+ # 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 r in self._ptr: r.nullify()
+ else:
+ # Internal memory has become part of other Geometry objects; must
+ # delete the internal objects which are still valid individually,
+ # because calling the destructor on entire geometry will result
+ # in an attempted deletion of NULL pointers for the missing
+ # components (which may crash Python).
+ for r in self._ptr: r.destroy()
super(Polygon, self).__del__()
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."""
+ """
+ Returns the ring at the specified index. The first index, 0, will always
+ return the exterior ring. Indices > 0 will return the interior ring.
+ """
if index == 0:
return self.exterior_ring
else:
@@ -353,31 +346,36 @@ class Polygon(GEOSGeometry):
def _nullify(self):
"Overloaded from base method to nullify ring references as well."
# Nullifying the references to the internal rings of this Polygon.
- for k in self._rings: self._rings[k].nullify()
+ for r in self._ptr: r.nullify()
return super(Polygon, self)._nullify()
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)))
+ "Internal routine for populating the internal ring pointers."
+ # Only populate if there aren't already children pointers.
+ if len(self._ptr) == 0:
+ # Getting the exterior ring pointer address.
+ ring_list = [lgeos.GEOSGetExteriorRing(self._ptr())]
+ # Getting the interior ring pointer addresses.
+ ring_list += [lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i)) for i in xrange(self.num_interior_rings)]
+ # Getting the coordinate sequence pointer address for each of the rings.
+ ptr_list = [(ring_ptr, lgeos.GEOSGeom_getCoordSeq(ring_ptr)) for ring_ptr in ring_list]
+ # Setting the children pointers.
+ self._ptr.set_children(ptr_list)
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."""
+ """
+ Gets the interior ring at the specified index, 0 is for the first
+ interior ring, not the exterior ring.
+ """
# Returning the ring from the internal ring dictionary (have to add one
# to index since all internal rings come after the exterior ring)
self._checkindex(ring_i+1)
- return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr, srid=self.srid)
+ return GEOSGeometry(self._ptr[ring_i+1], srid=self.srid)
#### Polygon Properties ####
@property
def num_interior_rings(self):
"Returns the number of interior rings."
-
# Getting the number of rings
n = lgeos.GEOSGetNumInteriorRings(self._ptr())
@@ -387,7 +385,7 @@ class Polygon(GEOSGeometry):
def get_ext_ring(self):
"Gets the exterior ring of the Polygon."
- return GEOSGeometry(self._rings[0], parent=self._ptr, srid=self.srid)
+ return GEOSGeometry(self._ptr[0], srid=self.srid)
def set_ext_ring(self, ring):
"Sets the exterior ring of the Polygon."
diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py
index 1a373e8682..65e80b40de 100644
--- a/django/contrib/gis/geos/libgeos.py
+++ b/django/contrib/gis/geos/libgeos.py
@@ -1,15 +1,15 @@
"""
This module houses the ctypes initialization procedures, as well
- as the notice and error handler function callbacks (get called
- when an error occurs in GEOS).
+ as the notice and error handler function callbacks (get called
+ when an error occurs in GEOS).
- This module also houses GEOS Pointer utilities, including the
- GEOSPointer class, get_pointer_arr(), GEOM_PTR, and init_from_geom().
+ This module also houses GEOS Pointer utilities, including
+ get_pointer_arr(), and GEOM_PTR.
"""
from django.contrib.gis.geos.error import GEOSException
-from ctypes import c_char_p, c_int, pointer, CDLL, CFUNCTYPE, POINTER, Structure
-import os, sys
+from ctypes import c_char_p, c_int, string_at, CDLL, CFUNCTYPE, POINTER, Structure
+import atexit, os, sys
# NumPy supported?
try:
@@ -18,7 +18,7 @@ try:
except ImportError:
HAS_NUMPY = False
-# Are psycopg2 and GeoDjango models being used?
+# Is psycopg2 available?
try:
from psycopg2.extensions import ISQLQuote
except (ImportError, EnvironmentError):
@@ -67,11 +67,11 @@ error_h = ERRORFUNC(error_h)
# The initGEOS routine should be called first, however, that routine takes
# the notice and error functions as parameters. Here is the C code that
-# we need to wrap:
+# is wrapped:
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
lgeos.initGEOS(notice_h, error_h)
-#### GEOS Geometry Pointer object, related C data structures, and functions. ####
+#### GEOS Geometry C data structures, and utility functions. ####
# Opaque GEOS geometry structure
class GEOSGeom_t(Structure):
@@ -81,87 +81,16 @@ class GEOSGeom_t(Structure):
# Pointer to opaque geometry structure
GEOM_PTR = POINTER(GEOSGeom_t)
-# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection GEOS routines
+# 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()
-class GEOSPointer(object):
- """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."""
+def geos_version():
+ "Returns the string version of GEOS."
+ return string_at(lgeos.GEOSversion())
- ### Python 'magic' routines ###
- 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 (an integer) will be returned."""
- if self.valid: return self.address
- else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
-
- def __nonzero__(self):
- "Returns True when the GEOSPointer is valid."
- return self.valid
-
- def __str__(self):
- return str(self.address)
-
- def __repr__(self):
- return 'GEOSPointer(%s)' % self.address
-
- ### GEOSPointer Properties ###
- @property
- def address(self):
- "Returns the address of the GEOSPointer (represented as an integer)."
- return self._geom.contents.value
-
- @property
- def valid(self):
- "Tests whether this GEOSPointer is valid."
- if bool(self.address): return True
- else: return False
-
- ### Coordinate Sequence routines and 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, 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).'
- 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). 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)
+# Calling the finishGEOS() upon exit of the interpreter.
+atexit.register(lgeos.finishGEOS)
diff --git a/django/contrib/gis/geos/pointer.py b/django/contrib/gis/geos/pointer.py
new file mode 100644
index 0000000000..50c0970d95
--- /dev/null
+++ b/django/contrib/gis/geos/pointer.py
@@ -0,0 +1,265 @@
+"""
+ This module houses the GEOSPointer class, used by GEOS Geometry objects
+ internally for memory management. Do not modify unless you _really_
+ know what you're doing.
+"""
+from ctypes import cast, c_int, c_void_p, pointer, POINTER, Structure
+from django.contrib.gis.geos.error import GEOSException
+from django.contrib.gis.geos.libgeos import lgeos
+
+# This C data structure houses the memory addresses (integers) of the
+# pointers returned from the GEOS C routines.
+class GEOSStruct(Structure): pass
+GEOSStruct._fields_ = [("geom", POINTER(c_int)), # for the geometry
+ ("cs", POINTER(c_int)), # for geometries w/coordinate sequences
+ ("parent", POINTER(GEOSStruct)), # points to the GEOSStruct of the parent
+ ("child", c_void_p), # points to an array of GEOSStructs
+ ("nchild", c_int), # the number of children
+ ]
+
+class GEOSPointer(object):
+ """
+ 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'
+ when a child's memory is used in construction of another geometry.
+
+ This object is also used to store pointers for any associated coordinate
+ sequence and may store the pointers for any children geometries.
+ """
+
+ #### Python 'magic' routines ####
+ def __init__(self, address, coordseq=0):
+ """
+ Initializes on an address (an integer), another GEOSPointer, or a
+ GEOSStruct object.
+ """
+ if isinstance(address, int):
+ self._struct = GEOSStruct()
+ # Integer addresses passed in, use the 'set' methods.
+ self.set(address)
+ if coordseq: self.set_coordseq(coordseq)
+ elif isinstance(address, GEOSPointer):
+ # Initializing from another GEOSPointer
+ self._struct = address._struct
+ elif isinstance(address, GEOSStruct):
+ # GEOSStruct passed directly in as a parameter
+ self._struct = address
+ else:
+ raise TypeError, 'GEOSPointer object must initialize with an integer.'
+
+ def __call__(self):
+ """
+ Returns the address value (an integer) of the GEOS Geometry pointer.
+ If the pointer is NULL, a GEOSException will be raised, thus preventing
+ an invalid memory address from being passed to a C routine.
+ """
+ if self.valid: return self.address
+ else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
+
+ def __getitem__(self, index):
+ "Returns a GEOSpointer object at the given child index."
+ n_child = len(self)
+ if n_child:
+ if index < 0 or index >= n_child:
+ raise IndexError, 'invalid child index'
+ else:
+ CHILD_PTR = POINTER(GEOSStruct * len(self))
+ return GEOSPointer(cast(self._struct.child, CHILD_PTR).contents[index])
+ else:
+ raise GEOSException, 'This GEOSPointer is not a parent'
+
+ def __iter__(self):
+ """
+ Iterates over the children Geometry pointers, return as GEOSPointer
+ objects to the caller.
+ """
+ for i in xrange(len(self)):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of children Geometry pointers (or 0 if no children)."
+ return self._struct.nchild
+
+ def __nonzero__(self):
+ "Returns True when the GEOSPointer is valid."
+ return self.valid
+
+ def __str__(self):
+ "Returns the string representation of this GEOSPointer."
+ # First getting the address for the Geometry pointer.
+ if self.valid: geom_addr = self.address
+ else: geom_addr = 0
+ # If there's a coordinate sequence, construct accoringly.
+ if self.coordseq_valid:
+ return 'GEOSPointer(%s, %s)' % (geom_addr, self.coordseq_address)
+ else:
+ return 'GEOSPointer(%s)' % geom_addr
+
+ def __repr__(self):
+ return str(self)
+
+ #### GEOSPointer Properties ####
+ @property
+ def address(self):
+ "Returns the address of the GEOSPointer (represented as an integer)."
+ return self._struct.geom.contents.value
+
+ @property
+ def valid(self):
+ "Tests whether this GEOSPointer is valid."
+ return bool(self._struct.geom)
+
+ #### Parent & Child Properties ####
+ @property
+ def parent(self):
+ "Returns the GEOSPointer for the parent or None."
+ if self.child:
+ return GEOSPointer(self._struct.parent.contents)
+ else:
+ return None
+
+ @property
+ def child(self):
+ "Returns True if the GEOSPointer has a parent."
+ return bool(self._struct.parent)
+
+ #### Coordinate Sequence routines and properties ####
+ def coordseq(self):
+ """
+ If the coordinate sequence pointer is NULL or 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._struct.cs.contents.value
+
+ @property
+ def coordseq_valid(self):
+ "Returns True if the coordinate sequence address is valid, False otherwise."
+ return bool(self._struct.cs)
+
+ #### GEOSPointer Methods ####
+ def destroy(self):
+ """
+ Calls GEOSGeom_destroy on the address of this pointer, and nullifies
+ this pointer. Use VERY carefully, as trying to destroy an address that
+ no longer holds a valid GEOS Geometry may crash Python.
+ """
+ if self.valid:
+ # ONLY on valid geometries.
+ lgeos.GEOSGeom_destroy(self.address)
+ self.nullify()
+
+ def set(self, address):
+ """
+ Sets the address of this pointer with the given address (represented
+ as an integer). Using 0 or None will set the pointer to NULL.
+ """
+ if address in (0, None):
+ self._struct.geom = None
+ else:
+ self._struct.geom.contents = c_int(address)
+
+ def set_coordseq(self, address):
+ """
+ Sets the address of the coordinate sequence associated with
+ this pointer.
+ """
+ if address in (0, None):
+ self._struct.cs = None
+ else:
+ self._struct.cs.contents = c_int(address)
+
+ def set_children(self, ptr_list):
+ """
+ Sets children pointers with the given pointer list (of integers).
+ Alternatively, a list of tuples for the geometry and coordinate
+ sequence pointers of the children may be used.
+ """
+ # The number of children geometries is the number of pointers (or
+ # tuples) passed in via the `ptr_list`.
+ n_child = len(ptr_list)
+
+ # Determining whether coordinate sequences pointers were passed in.
+ if isinstance(ptr_list[0], (tuple, list)):
+ self._child_cs = True
+ else:
+ self._child_cs = False
+
+ # Dynamically creating the C types for the children array (CHILD_ARR),
+ # initializing with the created type, and creating a parent pointer
+ # for the children.
+ CHILD_ARR = GEOSStruct * n_child
+ children = CHILD_ARR()
+ parent = pointer(self._struct)
+
+ # Incrementing through each of the children, and setting the
+ # pointers in the array of GEOSStructs.
+ for i in xrange(n_child):
+ if self._child_cs:
+ geom_ptr, cs_ptr = ptr_list[i]
+ if cs_ptr is not None:
+ children[i].cs.contents = c_int(cs_ptr)
+ else:
+ geom_ptr = ptr_list[i]
+ children[i].geom.contents = c_int(geom_ptr)
+ children[i].parent = parent
+
+ # Casting the CHILD_ARR to the contents of the void pointer, and
+ # setting the number of children within the struct (used by __len__).
+ self._struct.child = cast(pointer(children), c_void_p)
+ self._struct.nchild = c_int(n_child)
+
+ def nullify(self):
+ """
+ Nullify the geometry and coordinate sequence pointers (sets the
+ pointers to NULL). This does not delete any memory (destroy()
+ handles that), rather, it sets the GEOS pointers to a NULL address,
+ preventing access to deleted objects.
+ """
+ # Nullifying both the geometry and coordinate sequence pointer.
+ self.set(None)
+ self.set_coordseq(None)
+
+ def summary(self):
+ """
+ Returns a summary string containing information about the pointer,
+ including the geometry address, any associated coordinate sequence
+ address, and child geometry addresses.
+ """
+ sum = '%s\n' % str(self)
+ for p1 in self:
+ sum += ' * %s\n' % p1
+ for p2 in p1: sum += ' - %s\n' % p2
+ if bool(self._struct.parent):
+ sum += 'Parent: %s\n' % self.parent
+ return sum
+
+class NullGEOSPointer(GEOSPointer):
+ "The NullGEOSPointer is always NULL, and cannot be set to anything else."
+ def __init__(self):
+ self._struct = GEOSStruct()
+
+ @property
+ def valid(self):
+ return False
+
+ @property
+ def coordseq_valid(self):
+ return False
+
+ def set(self, *args):
+ pass
+
+ def set_coordseq(self, *args):
+ pass
+
+ def set_children(self, *args):
+ pass
+
+NULL_GEOM = NullGEOSPointer()
diff --git a/django/contrib/gis/tests/test_geos.py b/django/contrib/gis/tests/test_geos.py
index 81525a868c..be0da42e5a 100644
--- a/django/contrib/gis/tests/test_geos.py
+++ b/django/contrib/gis/tests/test_geos.py
@@ -13,13 +13,13 @@ class GEOSTest(unittest.TestCase):
def test01a_wkt(self):
"Testing WKT output."
for g in wkt_out:
- geom = GEOSGeometry(g.wkt)
+ geom = fromstr(g.wkt)
self.assertEqual(g.ewkt, geom.wkt)
def test01b_hex(self):
"Testing HEX output."
for g in hex_wkt:
- geom = GEOSGeometry(g.wkt)
+ geom = fromstr(g.wkt)
self.assertEqual(g.hex, geom.hex)
def test01c_kml(self):
@@ -34,22 +34,22 @@ class GEOSTest(unittest.TestCase):
print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
for err in errors:
if err.hex:
- self.assertRaises(GEOSException, GEOSGeometry, err.wkt)
+ self.assertRaises(GEOSException, fromstr, err.wkt)
else:
- self.assertRaises(GEOSException, GEOSGeometry, err.wkt)
+ self.assertRaises(GEOSException, fromstr, err.wkt)
print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
def test02a_points(self):
"Testing Point objects."
- prev = GEOSGeometry('POINT(0 0)')
+ prev = fromstr('POINT(0 0)')
for p in points:
# Creating the point from the WKT
- pnt = GEOSGeometry(p.wkt)
+ pnt = fromstr(p.wkt)
self.assertEqual(pnt.geom_type, 'Point')
self.assertEqual(pnt.geom_typeid, 0)
self.assertEqual(p.x, pnt.x)
self.assertEqual(p.y, pnt.y)
- self.assertEqual(True, pnt == GEOSGeometry(p.wkt))
+ self.assertEqual(True, pnt == fromstr(p.wkt))
self.assertEqual(False, pnt == prev)
# Making sure that the point's X, Y components are what we expect
@@ -97,7 +97,7 @@ class GEOSTest(unittest.TestCase):
def test02b_multipoints(self):
"Testing MultiPoint objects."
for mp in multipoints:
- mpnt = GEOSGeometry(mp.wkt)
+ mpnt = fromstr(mp.wkt)
self.assertEqual(mpnt.geom_type, 'MultiPoint')
self.assertEqual(mpnt.geom_typeid, 4)
@@ -115,9 +115,9 @@ class GEOSTest(unittest.TestCase):
def test03a_linestring(self):
"Testing LineString objects."
- prev = GEOSGeometry('POINT(0 0)')
+ prev = fromstr('POINT(0 0)')
for l in linestrings:
- ls = GEOSGeometry(l.wkt)
+ ls = fromstr(l.wkt)
self.assertEqual(ls.geom_type, 'LineString')
self.assertEqual(ls.geom_typeid, 1)
self.assertEqual(ls.empty, False)
@@ -127,7 +127,7 @@ class GEOSTest(unittest.TestCase):
if hasattr(l, 'tup'):
self.assertEqual(l.tup, ls.tuple)
- self.assertEqual(True, ls == GEOSGeometry(l.wkt))
+ self.assertEqual(True, ls == fromstr(l.wkt))
self.assertEqual(False, ls == prev)
self.assertRaises(GEOSGeometryIndexError, ls.__getitem__, len(ls))
prev = ls
@@ -141,16 +141,16 @@ class GEOSTest(unittest.TestCase):
def test03b_multilinestring(self):
"Testing MultiLineString objects."
- prev = GEOSGeometry('POINT(0 0)')
+ prev = fromstr('POINT(0 0)')
for l in multilinestrings:
- ml = GEOSGeometry(l.wkt)
+ ml = fromstr(l.wkt)
self.assertEqual(ml.geom_type, 'MultiLineString')
self.assertEqual(ml.geom_typeid, 5)
self.assertAlmostEqual(l.centroid[0], ml.centroid.x, 9)
self.assertAlmostEqual(l.centroid[1], ml.centroid.y, 9)
- self.assertEqual(True, ml == GEOSGeometry(l.wkt))
+ self.assertEqual(True, ml == fromstr(l.wkt))
self.assertEqual(False, ml == prev)
prev = ml
@@ -166,7 +166,7 @@ class GEOSTest(unittest.TestCase):
def test04_linearring(self):
"Testing LinearRing objects."
for rr in linearrings:
- lr = GEOSGeometry(rr.wkt)
+ lr = fromstr(rr.wkt)
self.assertEqual(lr.geom_type, 'LinearRing')
self.assertEqual(lr.geom_typeid, 2)
self.assertEqual(rr.n_p, len(lr))
@@ -181,10 +181,10 @@ class GEOSTest(unittest.TestCase):
def test05a_polygons(self):
"Testing Polygon objects."
- prev = GEOSGeometry('POINT(0 0)')
+ prev = fromstr('POINT(0 0)')
for p in polygons:
# Creating the Polygon, testing its properties.
- poly = GEOSGeometry(p.wkt)
+ poly = fromstr(p.wkt)
self.assertEqual(poly.geom_type, 'Polygon')
self.assertEqual(poly.geom_typeid, 3)
self.assertEqual(poly.empty, False)
@@ -199,7 +199,7 @@ class GEOSTest(unittest.TestCase):
self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
# Testing the geometry equivalence
- self.assertEqual(True, poly == GEOSGeometry(p.wkt))
+ self.assertEqual(True, poly == fromstr(p.wkt))
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
self.assertEqual(True, poly != prev)
@@ -222,9 +222,8 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(r.geom_typeid, 2)
# Testing polygon construction.
- self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
- self.assertRaises(TypeError, Polygon, 'foo')
-
+ self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3])
+ self.assertRaises(TypeError, Polygon.__init__, '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)
@@ -246,9 +245,9 @@ class GEOSTest(unittest.TestCase):
def test05b_multipolygons(self):
"Testing MultiPolygon objects."
print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
- prev = GEOSGeometry('POINT (0 0)')
+ prev = fromstr('POINT (0 0)')
for mp in multipolygons:
- mpoly = GEOSGeometry(mp.wkt)
+ mpoly = fromstr(mp.wkt)
self.assertEqual(mpoly.geom_type, 'MultiPolygon')
self.assertEqual(mpoly.geom_typeid, 6)
self.assertEqual(mp.valid, mpoly.valid)
@@ -266,14 +265,14 @@ class GEOSTest(unittest.TestCase):
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
- def test06_memory_hijinks(self):
- "Testing Geometry __del__() in different scenarios"
+ def test06a_memory_hijinks(self):
+ "Testing Geometry __del__() on rings and polygons."
#### Memory issues with rings and polygons
# These tests are needed to ensure sanity with writable geometries.
# Getting a polygon with interior rings, and pulling out the interior rings
- poly = GEOSGeometry(polygons[1].wkt)
+ poly = fromstr(polygons[1].wkt)
ring1 = poly[0]
ring2 = poly[1]
@@ -292,6 +291,8 @@ class GEOSTest(unittest.TestCase):
self.assertRaises(GEOSException, str, ring1)
self.assertRaises(GEOSException, str, ring2)
+ def test06b_memory_hijinks(self):
+ "Testing Geometry __del__() on collections."
#### Memory issues with geometries from Geometry Collections
mp = fromstr('MULTIPOINT(85 715, 235 1400, 4620 1711)')
@@ -315,16 +316,17 @@ class GEOSTest(unittest.TestCase):
# Should raise GEOSException when trying to get geometries from the multipoint
# after it has been deleted.
+ parr1 = [ptr for ptr in mp._ptr]
+ parr2 = [p._ptr for p in mp]
del mp
+
for p in pts:
self.assertRaises(GEOSException, str, p) # tests p's geometry pointer
self.assertRaises(GEOSException, p.get_coords) # 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)
+ poly = fromstr(polygons[3].wkt) # a 'real life' polygon.
+ linring = fromstr(linearrings[0].wkt) # a 'real life' linear ring
# Pulling out the shell and cloning our initial geometries for later comparison.
shell = poly.shell
@@ -338,33 +340,42 @@ class GEOSTest(unittest.TestCase):
self.assertRaises(GEOSException, str, shell)
self.assertRaises(GEOSException, str, linring)
- r1 = gc[1] # pulling out the ring
+ # Deep-indexing delete should be 'harmless.'
+ tmpr1 = gc[0][0]
+ del tmpr1
+
+ r1 = gc[0][0] # pulling out shell from polygon -- deep indexing
+ r2 = 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)
+ self.assertEqual(linringc.wkt, mpoly[1][0].wkt) # deep-indexed ring
# 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.
+ # BUT, should still be able to access the Point we obtained earlier,
+ # however, 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)
+ for tmpr in [r1, r2]:
+ # __len__ is called on the coordinate sequence pointer --
+ # making sure its nullified as well
+ self.assertRaises(GEOSException, len, tmpr)
+ self.assertRaises(GEOSException, str, tmpr)
# Can't access point after deletion of parent geometry.
del gc
self.assertRaises(GEOSException, str, pnt)
# Cleaning up.
- del polyc
- del mpoly
+ del polyc, mpoly
+ def test06c_memory_hijinks(self):
+ "Testing __init__ using other Geometries as parameters."
#### Memory issues with creating geometries from coordinate sequences within other geometries
# Creating the initial polygon from the following tuples, and then pulling out
@@ -388,18 +399,42 @@ class GEOSTest(unittest.TestCase):
# 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
+ del poly1, poly2
self.assertRaises(GEOSException, str, hole2)
+
+ #### Testing creating Geometries w/"deep-indexed" rings ####
+ mpoly = MultiPolygon(Polygon(LinearRing(ext_tup), LinearRing(itup1), LinearRing(itup2)),
+ Polygon(LinearRing(itup1)))
+
+ r0 = mpoly[0][1] # itup1, left alone
+ r1 = mpoly[0][0] # ext_tup
+ r2 = mpoly[1][0] # itup1
+ r3 = mpoly[0][2] # itup2
+
+ # Using the rings of the multipolygon to create a new Polygon, should
+ # no longer be able to access the MultiPolygon or the ring objects
+ # used in initialization.
+ p1 = Polygon(r1, r2, r3)
+ for g in [mpoly, r1, r2, r3]:
+ self.assertRaises(GEOSException, len, g)
+ self.assertRaises(GEOSException, str, g)
+ # However, the middle ring of the first Polygon was not used.
+ self.assertEqual(r0.tuple, itup1)
+ self.assertEqual(r0, p1[1])
+
+ # This deletes the leftover ring (or whenever mpoly is garbage collected)
+ del mpoly
+
def test08_coord_seq(self):
"Testing Coordinate Sequence objects."
for p in polygons:
if p.ext_ring_cs:
# Constructing the polygon and getting the coordinate sequence
- poly = GEOSGeometry(p.wkt)
+ poly = fromstr(p.wkt)
cs = poly.exterior_ring.coord_seq
self.assertEqual(p.ext_ring_cs, cs.tuple) # done in the Polygon test too.
@@ -423,13 +458,13 @@ class GEOSTest(unittest.TestCase):
def test09_relate_pattern(self):
"Testing relate() and relate_pattern()."
- g = GEOSGeometry('POINT (0 0)')
+ g = fromstr('POINT (0 0)')
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
for i in xrange(len(relate_geoms)):
g_tup = relate_geoms[i]
- a = GEOSGeometry(g_tup[0].wkt)
- b = GEOSGeometry(g_tup[1].wkt)
+ a = fromstr(g_tup[0].wkt)
+ b = fromstr(g_tup[1].wkt)
pat = g_tup[2]
result = g_tup[3]
self.assertEqual(result, a.relate_pattern(b, pat))
@@ -439,9 +474,9 @@ class GEOSTest(unittest.TestCase):
"Testing intersects() and intersection()."
for i in xrange(len(topology_geoms)):
g_tup = topology_geoms[i]
- a = GEOSGeometry(g_tup[0].wkt)
- b = GEOSGeometry(g_tup[1].wkt)
- i1 = GEOSGeometry(intersect_geoms[i].wkt)
+ a = fromstr(g_tup[0].wkt)
+ b = fromstr(g_tup[1].wkt)
+ i1 = fromstr(intersect_geoms[i].wkt)
self.assertEqual(True, a.intersects(b))
i2 = a.intersection(b)
self.assertEqual(i1, i2)
@@ -453,9 +488,9 @@ class GEOSTest(unittest.TestCase):
"Testing union()."
for i in xrange(len(topology_geoms)):
g_tup = topology_geoms[i]
- a = GEOSGeometry(g_tup[0].wkt)
- b = GEOSGeometry(g_tup[1].wkt)
- u1 = GEOSGeometry(union_geoms[i].wkt)
+ a = fromstr(g_tup[0].wkt)
+ b = fromstr(g_tup[1].wkt)
+ u1 = fromstr(union_geoms[i].wkt)
u2 = a.union(b)
self.assertEqual(u1, u2)
self.assertEqual(u1, a | b) # __or__ is union operator
@@ -466,9 +501,9 @@ class GEOSTest(unittest.TestCase):
"Testing 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(diff_geoms[i].wkt)
+ a = fromstr(g_tup[0].wkt)
+ b = fromstr(g_tup[1].wkt)
+ d1 = fromstr(diff_geoms[i].wkt)
d2 = a.difference(b)
self.assertEqual(d1, d2)
self.assertEqual(d1, a - b) # __sub__ is difference operator
@@ -479,9 +514,9 @@ class GEOSTest(unittest.TestCase):
"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)
+ a = fromstr(g_tup[0].wkt)
+ b = fromstr(g_tup[1].wkt)
+ d1 = fromstr(sdiff_geoms[i].wkt)
d2 = a.sym_difference(b)
self.assertEqual(d1, d2)
self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
@@ -492,10 +527,10 @@ class GEOSTest(unittest.TestCase):
"Testing buffer()."
for i in xrange(len(buffer_geoms)):
g_tup = buffer_geoms[i]
- g = GEOSGeometry(g_tup[0].wkt)
+ g = fromstr(g_tup[0].wkt)
# The buffer we expect
- exp_buf = GEOSGeometry(g_tup[1].wkt)
+ exp_buf = fromstr(g_tup[1].wkt)
# Can't use a floating-point for the number of quadsegs.
self.assertRaises(TypeError, g.buffer, g_tup[2], float(g_tup[3]))
@@ -575,8 +610,8 @@ class GEOSTest(unittest.TestCase):
self.assertNotEqual(pnt, mp[i])
del mp
- # Multipolygons involve much more memory management because each
- # polygon w/in the collection has its own rings.
+ # MultiPolygons involve much more memory management because each
+ # Polygon w/in the collection has its own rings.
for tg in multipolygons:
mpoly = fromstr(tg.wkt)
for i in xrange(len(mpoly)):
@@ -592,12 +627,17 @@ class GEOSTest(unittest.TestCase):
self.assertRaises(GEOSException, str, tmp)
self.assertEqual(mpoly[i], new)
self.assertNotEqual(poly, mpoly[i])
- del mpoly
+
+ # Extreme (!!) __setitem__
+ mpoly[0][0][0] = (3.14, 2.71)
+ self.assertEqual((3.14, 2.71), mpoly[0][0][0])
+ # Doing it more slowly..
+ self.assertEqual((3.14, 2.71), mpoly[0].shell[0])
+ del mpoly
def test17_threed(self):
"Testing three-dimensional geometries."
-
# Testing a 3D Point
pnt = Point(2, 3, 8)
self.assertEqual((2.,3.,8.), pnt.coords)
@@ -632,7 +672,6 @@ class GEOSTest(unittest.TestCase):
def test19_length(self):
"Testing the length property."
-
# Points have 0 length.
pnt = Point(0, 0)
self.assertEqual(0.0, pnt.length)