1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

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
This commit is contained in:
Justin Bronn 2007-08-30 01:38:44 +00:00
parent cb64f5375c
commit f4203ef757
9 changed files with 671 additions and 373 deletions

View File

@ -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."

View File

@ -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,21 +143,30 @@ 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()
@ -178,7 +174,7 @@ class GEOSGeometry(object):
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

View File

@ -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):

View File

@ -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 ###

View File

@ -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

View File

@ -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.
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._parent = None
self._rings = {}
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.
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))
"""
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."

View File

@ -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)

View File

@ -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()

View File

@ -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
@ -391,15 +402,39 @@ class GEOSTest(unittest.TestCase):
# 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)