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

gis: geos: fully-mutable geometries have landed; fixed GEOSPointer boolean value; equivalence now uses equals_exact (vertex-by-vertex matching); added in-place set operations; improved tests; getquoted() will return ST_* for PostGIS 1.2.2+.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5786 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-08-02 05:31:10 +00:00
parent ffcc38ebe8
commit 056f87ab28
7 changed files with 275 additions and 105 deletions

View File

@ -12,7 +12,7 @@ from types import StringType, IntType, FloatType
# Python and GEOS-related dependencies. # Python and GEOS-related dependencies.
import re import re
from warnings import warn from warnings import warn
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY, ISQLQuote from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY, ISQLQuote, GEOM_FUNC_PREFIX
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
if HAS_NUMPY: from numpy import ndarray, array if HAS_NUMPY: from numpy import ndarray, array
@ -87,7 +87,7 @@ class GEOSGeometry(object):
def __del__(self): def __del__(self):
"Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent." "Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent."
#print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid) #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 # Only calling destroy on valid pointers not spawned from a parent
if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr()) if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
@ -101,11 +101,11 @@ class GEOSGeometry(object):
# Comparison operators # Comparison operators
def __eq__(self, other): def __eq__(self, other):
"Equivalence testing." "Equivalence testing."
return self.equals(other) return self.equals_exact(other)
def __ne__(self, other): def __ne__(self, other):
"The not equals operator." "The not equals operator."
return not self.equals(other) return not self.equals_exact(other)
### Geometry set-like operations ### ### Geometry set-like operations ###
# Thanks to Sean Gillies for inspiration: # Thanks to Sean Gillies for inspiration:
@ -115,21 +115,76 @@ class GEOSGeometry(object):
"Returns the union of this Geometry and the other." "Returns the union of this Geometry and the other."
return self.union(other) return self.union(other)
# g1 |= g2
def __ior__(self, other):
"Reassigns this Geometry to the union of this Geometry and the other."
return self.union(other)
# g = g1 & g2 # g = g1 & g2
def __and__(self, other): def __and__(self, other):
"Returns the intersection of this Geometry and the other." "Returns the intersection of this Geometry and the other."
return self.intersection(other) return self.intersection(other)
# g1 &= g2
def __iand__(self, other):
"Reassigns this Geometry to the intersection of this Geometry and the other."
return self.intersection(other)
# g = g1 - g2 # g = g1 - g2
def __sub__(self, other): def __sub__(self, other):
"Return the difference this Geometry and the other." "Return the difference this Geometry and the other."
return self.difference(other) return self.difference(other)
# g1 -= g2
def __isub__(self, other):
"Reassigns this Geometry to the difference of this Geometry and the other."
return self.difference(other)
# g = g1 ^ g2 # g = g1 ^ g2
def __xor__(self, other): def __xor__(self, other):
"Return the symmetric difference of this Geometry and the other." "Return the symmetric difference of this Geometry and the other."
return self.sym_difference(other) return self.sym_difference(other)
# g1 ^= g2
def __ixor__(self, other):
"Reassigns this Geometry to the symmetric difference of this Geometry and the other."
return self.sym_difference(other)
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."""
# 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()
# Nullifying the geometry pointer
self._ptr.nullify()
return address
def _reassign(self, new_geom):
"Internal routine for reassigning internal pointer to a 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)
gtype = new_geom.geom_type
# 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
# The new geometry class may be different from the original, so setting
# the __class__ and populating the internal geometry or ring dictionary.
self.__class__ = GEOS_CLASSES[gtype]
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
#### Psycopg2 database adaptor routines #### #### Psycopg2 database adaptor routines ####
def __conform__(self, proto): def __conform__(self, proto):
# Does the given protocol conform to what Psycopg2 expects? # Does the given protocol conform to what Psycopg2 expects?
@ -140,7 +195,8 @@ class GEOSGeometry(object):
def getquoted(self): def getquoted(self):
"Returns a properly quoted string for use in PostgresSQL/PostGIS." "Returns a properly quoted string for use in PostgresSQL/PostGIS."
return "GeometryFromText('%s', %s)" % (self.wkt, self.srid or -1) # GeometryFromText() is ST_GeometryFromText() in PostGIS >= 1.2.2
return "%sGeometryFromText('%s', %s)" % (GEOM_FUNC_PREFIX, self.wkt, self.srid or -1)
#### Coordinate Sequence Routines #### #### Coordinate Sequence Routines ####
@property @property
@ -299,10 +355,8 @@ class GEOSGeometry(object):
def get_srid(self): def get_srid(self):
"Gets the SRID for the geometry, returns None if no SRID is set." "Gets the SRID for the geometry, returns None if no SRID is set."
s = lgeos.GEOSGetSRID(self._ptr()) s = lgeos.GEOSGetSRID(self._ptr())
if s == 0: if s == 0: return None
return None else: return s
else:
return s
def set_srid(self, srid): def set_srid(self, srid):
"Sets the SRID for the geometry." "Sets the SRID for the geometry."
@ -404,7 +458,7 @@ class GEOSGeometry(object):
def clone(self): def clone(self):
"Clones this Geometry." "Clones this Geometry."
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr())) return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()), srid=self.srid)
# Class mapping dictionary # Class mapping dictionary
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing

View File

@ -4,37 +4,25 @@
""" """
from ctypes import c_int, c_uint, byref, cast from ctypes import c_int, c_uint, byref, cast
from types import TupleType, ListType from types import TupleType, ListType
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, init_from_geom, get_pointer_arr, GEOM_PTR from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, GEOM_PTR
from django.contrib.gis.geos.base import GEOSGeometry from django.contrib.gis.geos.base import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError 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.geometries import Point, LineString, LinearRing, Polygon
def init_from_poly(poly):
"Internal routine used for initializing Geometry Collections from Polygons."
# Constructing a new Polygon to take control of the rings.
p = Polygon(*tuple(ring for ring in poly))
# If this Polygon came from a GeometryCollection, it is a child
# and the parent geometry pointer is nullified.
if poly._parent: poly._parent.nullify()
# Nullifying the polygon pointer
poly._ptr.nullify()
# Returning the address of the new Polygon.
return p._ptr()
class GeometryCollection(GEOSGeometry): class GeometryCollection(GEOSGeometry):
_allowed = (Point, LineString, LinearRing, Polygon) _allowed = (Point, LineString, LinearRing, Polygon)
_typeid = 7 _typeid = 7
def __init__(self, *args, **kwargs): 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._ptr = GEOSPointer(0) # Initially NULL
self._geoms = {} self._geoms = {}
self._parent = False self._parent = None
# Checking the arguments
if not args: if not args:
raise TypeError, 'Must provide at least one LinearRing to initialize Polygon.' 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
if isinstance(args[0], (TupleType, ListType)): if isinstance(args[0], (TupleType, ListType)):
@ -54,23 +42,18 @@ class GeometryCollection(GEOSGeometry):
# Incrementing through each input geometry. # Incrementing through each input geometry.
for i in xrange(ngeom): for i in xrange(ngeom):
if isinstance(init_geoms[i], Polygon): geoms[i] = cast(init_geoms[i]._nullify(), GEOM_PTR)
# Special care is taken when importing from Polygons
geoms[i] = cast(init_from_poly(init_geoms[i]), GEOM_PTR)
else:
geoms[i] = cast(init_from_geom(init_geoms[i]), GEOM_PTR)
# Calling the parent class, using the pointer returned from GEOS createCollection() # 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) super(GeometryCollection, self).__init__(lgeos.GEOSGeom_createCollection(c_int(self._typeid), byref(geoms), c_uint(ngeom)), **kwargs)
def __del__(self): def __del__(self):
"Overloaded deletion method for Geometry Collections." "Overloaded deletion method for Geometry Collections."
#print '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._parent, self._ptr.valid)
# If this geometry is still valid, it hasn't been modified by others. # If this geometry is still valid, it hasn't been modified by others.
if self._ptr.valid: if self._ptr.valid:
# Nullifying pointers to internal geometries, preventing any attempted future access. # Nullifying pointers to internal geometries, preventing any attempted future access.
for k in self._geoms: self._geoms[k].nullify() for k in self._geoms: self._geoms[k].nullify()
super(GeometryCollection, self).__del__()
else: else:
# Internal memory has become part of other Geometry objects, must delete the # Internal memory has become part of other Geometry objects, must delete the
# internal objects which are still valid individually, since calling destructor # internal objects which are still valid individually, since calling destructor
@ -80,20 +63,38 @@ class GeometryCollection(GEOSGeometry):
if self._geoms[k].valid: if self._geoms[k].valid:
lgeos.GEOSGeom_destroy(self._geoms[k].address) lgeos.GEOSGeom_destroy(self._geoms[k].address)
self._geoms[k].nullify() self._geoms[k].nullify()
super(GeometryCollection, self).__del__()
def __getitem__(self, index): def __getitem__(self, index):
"For indexing on the multiple geometries." "Returns the Geometry from this Collection at the given index (0-based)."
# Checking the index and returning the corresponding GEOS geometry. # Checking the index and returning the corresponding GEOS geometry.
self._checkindex(index) self._checkindex(index)
return GEOSGeometry(self._geoms[index], parent=self._ptr, srid=self.srid) return GEOSGeometry(self._geoms[index], parent=self._ptr, srid=self.srid)
def __setitem__(self, index, geom):
"Sets the Geometry at the specified index."
self._checkindex(index)
if not isinstance(geom, self._allowed):
raise TypeError, 'Incompatible Geometry for collection.'
# Constructing the list of geometries that will go in the collection.
new_geoms = []
for i in xrange(len(self)):
if i == index: new_geoms.append(geom)
else: new_geoms.append(self[i])
# Creating a new geometry collection from the list, and
# re-assigning the pointers.
new_collection = self.__class__(*new_geoms)
self._reassign(new_collection)
def __iter__(self): def __iter__(self):
"For iteration on the multiple geometries." "Iterates over each Geometry in the Collection."
for i in xrange(len(self)): for i in xrange(len(self)):
yield self.__getitem__(i) yield self.__getitem__(i)
def __len__(self): def __len__(self):
"Returns the number of geometries in this collection." "Returns the number of geometries in this Collection."
return self.num_geom return self.num_geom
def _checkindex(self, index): def _checkindex(self, index):
@ -101,13 +102,18 @@ class GeometryCollection(GEOSGeometry):
if index < 0 or index >= self.num_geom: if index < 0 or index >= self.num_geom:
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
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()
return super(GeometryCollection, self)._nullify()
def _populate(self): def _populate(self):
"Populates the internal child geometry dictionary." "Populates the internal child geometry dictionary."
self._geoms = {} self._geoms = {}
for i in xrange(self.num_geom): for i in xrange(self.num_geom):
self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i))) self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i)))
# MultiPoint, MultiLineString, and MultiPolygon class definitions. # MultiPoint, MultiLineString, and MultiPolygon class definitions.
class MultiPoint(GeometryCollection): class MultiPoint(GeometryCollection):
_allowed = Point _allowed = Point

View File

@ -7,7 +7,7 @@
from ctypes import c_double, c_int, c_uint, byref, cast from ctypes import c_double, c_int, c_uint, byref, cast
from types import FloatType, IntType, ListType, TupleType from types import FloatType, IntType, ListType, TupleType
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, init_from_geom, GEOM_PTR, HAS_NUMPY 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.base import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
if HAS_NUMPY: from numpy import ndarray, array if HAS_NUMPY: from numpy import ndarray, array
@ -21,7 +21,9 @@ class Point(GEOSGeometry):
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
""" """
# Setting-up for Point Creation
self._ptr = GEOSPointer(0) # Initially NULL self._ptr = GEOSPointer(0) # Initially NULL
self._parent = None
if isinstance(x, (TupleType, ListType)): 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.
@ -132,7 +134,9 @@ class LineString(GEOSGeometry):
ls = LineString(array([(1, 1), (2, 2)])) ls = LineString(array([(1, 1), (2, 2)]))
ls = LineString(Point(1, 1), Point(2, 2)) ls = LineString(Point(1, 1), Point(2, 2))
""" """
# Setting up for LineString creation
self._ptr = GEOSPointer(0) # Initially NULL 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 was provided, then set the coords array appropriately
if len(args) == 1: coords = args[0] if len(args) == 1: coords = args[0]
@ -254,6 +258,7 @@ class Polygon(GEOSGeometry):
poly = Polygon(shell, (hole1, hole2)) poly = Polygon(shell, (hole1, hole2))
""" """
self._ptr = GEOSPointer(0) # Initially NULL self._ptr = GEOSPointer(0) # Initially NULL
self._parent = None
self._rings = {} self._rings = {}
if not args: if not args:
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.' raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
@ -277,43 +282,58 @@ class Polygon(GEOSGeometry):
holes = get_pointer_arr(nholes) holes = get_pointer_arr(nholes)
for i in xrange(nholes): for i in xrange(nholes):
# Casting to the Geometry Pointer type # Casting to the Geometry Pointer type
holes[i] = cast(init_from_geom(init_holes[i]), GEOM_PTR) holes[i] = cast(init_holes[i]._nullify(), GEOM_PTR)
# Getting the shell pointer address, # Getting the shell pointer address,
shell = init_from_geom(ext_ring) shell = ext_ring._nullify()
# Calling with the GEOS createPolygon factory. # Calling with the GEOS createPolygon factory.
super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)), **kwargs) super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)), **kwargs)
def __del__(self): def __del__(self):
"Overloaded deletion method for Polygons." "Overloaded deletion method for Polygons."
#print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid) #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 this geometry is still valid, it hasn't been modified by others.
if self._ptr.valid: if self._ptr.valid:
# Nulling the pointers to internal rings, preventing any attempted future access # Nulling the pointers to internal rings, preventing any attempted future access
for k in self._rings: self._rings[k].nullify() for k in self._rings: self._rings[k].nullify()
super(Polygon, self).__del__() elif not self._parent:
else:
# Internal memory has become part of other objects; must delete the # Internal memory has become part of other objects; must delete the
# internal objects which are still valid individually, since calling # internal objects which are still valid individually, since calling
# destructor on entire geometry will result in an attempted deletion # destructor on entire geometry will result in an attempted deletion
# of NULL pointers for the missing components. # of NULL pointers for the missing components. Not performed on
# children Polygons from MultiPolygon or GeometryCollection objects.
for k in self._rings: for k in self._rings:
if self._rings[k].valid: if self._rings[k].valid:
lgeos.GEOSGeom_destroy(self._rings[k].address) lgeos.GEOSGeom_destroy(self._rings[k].address)
self._rings[k].nullify() self._rings[k].nullify()
super(Polygon, self).__del__()
def __getitem__(self, index): def __getitem__(self, index):
"""Returns the ring at the specified index. The first index, 0, will always """Returns the ring at the specified index. The first index, 0, will always
return the exterior ring. Indices > 0 will return the interior ring.""" return the exterior ring. Indices > 0 will return the interior ring."""
if index < 0 or index > self.num_interior_rings: if index == 0:
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) return self.exterior_ring
else: else:
if index == 0: # Getting the interior ring, have to subtract 1 from the index.
return self.exterior_ring return self.get_interior_ring(index-1)
else:
# Getting the interior ring, have to subtract 1 from the index. def __setitem__(self, index, ring):
return self.get_interior_ring(index-1) "Sets the ring at the specified index with the given ring."
# Checking the index and ring parameters.
self._checkindex(index)
if not isinstance(ring, LinearRing):
raise TypeError, 'must set Polygon index with a LinearRing object'
# Constructing the ring parameters
new_rings = []
for i in xrange(len(self)):
if index == i: new_rings.append(ring)
else: new_rings.append(self[i])
# Constructing the new Polygon with the ring parameters, and reassigning the internals.
new_poly = Polygon(*new_rings)
self._reassign(new_poly)
def __iter__(self): def __iter__(self):
"Iterates over each ring in the polygon." "Iterates over each ring in the polygon."
@ -324,6 +344,17 @@ class Polygon(GEOSGeometry):
"Returns the number of rings in this Polygon." "Returns the number of rings in this Polygon."
return self.num_interior_rings + 1 return self.num_interior_rings + 1
def _checkindex(self, index):
"Internal routine for checking the given ring index."
if index < 0 or index >= len(self):
raise GEOSGeometryIndexError, 'invalid Polygon ring index: %s' % index
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()
return super(Polygon, self)._nullify()
def _populate(self): def _populate(self):
"Populates the internal rings dictionary." "Populates the internal rings dictionary."
# Getting the exterior ring first for the 0th index. # Getting the exterior ring first for the 0th index.
@ -336,13 +367,9 @@ class Polygon(GEOSGeometry):
def get_interior_ring(self, ring_i): def get_interior_ring(self, ring_i):
"""Gets the interior ring at the specified index, """Gets the interior ring at the specified index,
0 is for the first interior ring, not the exterior ring.""" 0 is for the first interior ring, not the exterior ring."""
# Returning the ring from the internal ring dictionary (have to add one
# Making sure the ring index is within range # to index since all internal rings come after the exterior ring)
if ring_i < 0 or ring_i >= self.num_interior_rings: self._checkindex(ring_i+1)
raise IndexError, 'ring index out of range'
# Returning the ring from the internal ring dictionary (have to
# add one to the index)
return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr, srid=self.srid) return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr, srid=self.srid)
#### Polygon Properties #### #### Polygon Properties ####
@ -361,14 +388,13 @@ class Polygon(GEOSGeometry):
"Gets the exterior ring of the Polygon." "Gets the exterior ring of the Polygon."
return GEOSGeometry(self._rings[0], parent=self._ptr, srid=self.srid) return GEOSGeometry(self._rings[0], parent=self._ptr, srid=self.srid)
def set_ext_ring(self): def set_ext_ring(self, ring):
"Sets the exterior ring of the Polygon." "Sets the exterior ring of the Polygon."
# Sets the exterior ring self[0] = ring
raise NotImplementedError
# properties for the exterior ring/shell # properties for the exterior ring/shell
exterior_ring = property(get_ext_ring) exterior_ring = property(get_ext_ring, set_ext_ring)
shell = property(get_ext_ring) shell = property(get_ext_ring, set_ext_ring)
@property @property
def tuple(self): def tuple(self):

View File

@ -18,11 +18,13 @@ try:
except ImportError: except ImportError:
HAS_NUMPY = False HAS_NUMPY = False
# Psycopg2 supported? # Are psycopg2 and GeoDjango models being used?
try: try:
from psycopg2.extensions import ISQLQuote from psycopg2.extensions import ISQLQuote
except ImportError: from django.contrib.gis.db.backend.postgis import GEOM_FUNC_PREFIX
except (ImportError, EnvironmentError):
ISQLQuote = None ISQLQuote = None
GEOM_FUNC_PREFIX = None
# Setting the appropriate name for the GEOS-C library, depending on which # Setting the appropriate name for the GEOS-C library, depending on which
# OS and POSIX platform we're running. # OS and POSIX platform we're running.
@ -71,21 +73,22 @@ error_h = ERRORFUNC(error_h)
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);" # "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
lgeos.initGEOS(notice_h, error_h) lgeos.initGEOS(notice_h, error_h)
#### GEOS Geometry Pointer utilities. #### #### GEOS Geometry Pointer object, related C data structures, and functions. ####
# Opaque GEOS geometry structure # Opaque GEOS geometry structure
class GEOSGeom_t(Structure): class GEOSGeom_t(Structure):
"Opaque structure used when arrays of geometries are needed as parameters." "Opaque structure used when arrays of geometries are needed as parameters."
pass pass
# Pointer to opaque geometry structure # Pointer to opaque geometry structure
GEOM_PTR = POINTER(GEOSGeom_t) 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): def get_pointer_arr(n):
"Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer." "Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
GeomArr = GEOM_PTR * n GeomArr = GEOM_PTR * n
return GeomArr() return GeomArr()
#### GEOS Pointer object and routines ####
class GEOSPointer(object): class GEOSPointer(object):
"""The GEOSPointer provides a layer of abstraction in accessing the values returned by """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, GEOS geometry creation routines. Memory addresses (integers) are kept in a C pointer,
@ -108,13 +111,16 @@ class GEOSPointer(object):
if self.valid: return self.address if self.valid: return self.address
else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)' else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
def __bool__(self): def __nonzero__(self):
"Returns True when the GEOSPointer is valid." "Returns True when the GEOSPointer is valid."
return self.valid return self.valid
def __str__(self): def __str__(self):
return str(self.address) return str(self.address)
def __repr__(self):
return 'GEOSPointer(%s)' % self.address
### GEOSPointer Properties ### ### GEOSPointer Properties ###
@property @property
def address(self): def address(self):
@ -161,21 +167,3 @@ class GEOSPointer(object):
# Nullifying both the geometry and coordinate sequence pointer. # Nullifying both the geometry and coordinate sequence pointer.
self.set(0) self.set(0)
self.set(0, coordseq=True) self.set(0, coordseq=True)
def init_from_geom(geom):
"""During initialization of geometries from other geometries, this routine is
used to nullify any parent geometries (since they will now be missing memory
components) and to nullify the geometry itself to prevent future access.
Only the address (an integer) of the current geometry is returned for use in
initializing the new geometry."""
# First getting the memory address of the geometry.
address = geom._ptr()
# If the geometry is a child geometry, then the parent geometry pointer is
# nullified.
if geom._parent: geom._parent.nullify()
# Nullifying the geometry pointer
geom._ptr.nullify()
return address

View File

@ -56,9 +56,13 @@ def run_tests(module_list, verbosity=1):
from django.conf import settings from django.conf import settings
# Getting initial values. # Getting initial values.
old_debug = settings.DEBUG
old_name = copy(settings.DATABASE_NAME) old_name = copy(settings.DATABASE_NAME)
old_installed = copy(settings.INSTALLED_APPS) old_installed = copy(settings.INSTALLED_APPS)
# Want DEBUG to be set to False.
settings.DEBUG = False
# Creating the test suite, adding the test models to INSTALLED_APPS, and # Creating the test suite, adding the test models to INSTALLED_APPS, and
# adding the model test suites to our suite package. # adding the model test suites to our suite package.
test_suite = suite() test_suite = suite()
@ -82,6 +86,7 @@ def run_tests(module_list, verbosity=1):
# Cleaning up, destroying the test spatial database and resetting the INSTALLED_APPS. # Cleaning up, destroying the test spatial database and resetting the INSTALLED_APPS.
destroy_test_db(old_name, verbosity) destroy_test_db(old_name, verbosity)
settings.DEBUG = old_debug
settings.INSTALLED_APPS = old_installed settings.INSTALLED_APPS = old_installed
# Returning the total failures and errors # Returning the total failures and errors

View File

@ -115,19 +115,19 @@ topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0,
) )
intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'), intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'),
TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))') TestGeom('POLYGON ((10 1, 9 3, 7 4, 5 6, 4 8, 4 10, 5 12, 7 13, 9 12, 10 10, 11 12, 13 13, 15 12, 16 10, 16 8, 15 6, 13 4, 11 3, 10 1))'),
) )
union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'), union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'),
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'), TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0))'),
) )
diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'), diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'),
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'), TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
) )
sdiff_geoms = ( TestGeom('MULTIPOLYGON (((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0)),((0 0,5 0,5 5,10 5,10 -5,0 -5,0 0)))'), sdiff_geoms = ( TestGeom('MULTIPOLYGON (((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0)),((0 0,5 0,5 5,10 5,10 -5,0 -5,0 0)))'),
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'), TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
) )
relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'), relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),

View File

@ -1,4 +1,4 @@
import unittest import random, unittest
from django.contrib.gis.geos import \ from django.contrib.gis.geos import \
GEOSException, GEOSGeometryIndexError, \ GEOSException, GEOSGeometryIndexError, \
GEOSGeometry, Point, LineString, LinearRing, Polygon, \ GEOSGeometry, Point, LineString, LinearRing, Polygon, \
@ -166,7 +166,7 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(lr, LinearRing(lr.tuple)) self.assertEqual(lr, LinearRing(lr.tuple))
self.assertEqual(lr, LinearRing(*lr.tuple)) self.assertEqual(lr, LinearRing(*lr.tuple))
self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple])) self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
if HAS_NUMPY: self.assertEqual(lr, LineString(array(lr.tuple))) if HAS_NUMPY: self.assertEqual(lr, LinearRing(array(lr.tuple)))
def test05a_polygons(self): def test05a_polygons(self):
"Testing Polygon objects." "Testing Polygon objects."
@ -200,10 +200,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(p.ext_ring_cs, ring.tuple) self.assertEqual(p.ext_ring_cs, ring.tuple)
self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__ self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__
# Testing __iter__ # Testing __getitem__ and __setitem__ on invalid indices
self.assertRaises(GEOSGeometryIndexError, poly.__getitem__, len(poly))
self.assertRaises(GEOSGeometryIndexError, poly.__setitem__, len(poly), False)
self.assertRaises(GEOSGeometryIndexError, poly.__getitem__, -1)
# Testing __iter__
for r in poly: for r in poly:
self.assertEqual(ring.geom_type, 'LinearRing') self.assertEqual(r.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2) self.assertEqual(r.geom_typeid, 2)
# Testing polygon construction. # Testing polygon construction.
self.assertRaises(TypeError, Polygon, 0, [1, 2, 3]) self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
@ -224,8 +229,8 @@ class GEOSTest(unittest.TestCase):
except TypeError: except TypeError:
pass pass
poly[0][1] = newval # setting the second point in the polygon with the newvalue (based on the old) poly[0][1] = newval # setting the second point in the polygon with the newvalue (based on the old)
self.assertEqual(newval, poly[0][1]) # The point in the polygon should be the self.assertEqual(newval, poly[0][1]) # The point in the polygon should be the new value
self.assertEqual(False, poly == prev) # Even different from the clone we just made self.assertEqual(False, poly == prev) # Should be different from the clone we just made
def test05b_multipolygons(self): def test05b_multipolygons(self):
"Testing MultiPolygon objects." "Testing MultiPolygon objects."
@ -426,11 +431,12 @@ class GEOSTest(unittest.TestCase):
a = GEOSGeometry(g_tup[0].wkt) a = GEOSGeometry(g_tup[0].wkt)
b = GEOSGeometry(g_tup[1].wkt) b = GEOSGeometry(g_tup[1].wkt)
i1 = GEOSGeometry(intersect_geoms[i].wkt) i1 = GEOSGeometry(intersect_geoms[i].wkt)
self.assertEqual(True, a.intersects(b)) self.assertEqual(True, a.intersects(b))
i2 = a.intersection(b) i2 = a.intersection(b)
self.assertEqual(i1, i2) self.assertEqual(i1, i2)
self.assertEqual(i1, a & b) self.assertEqual(i1, a & b) # __and__ is intersection operator
a &= b # testing __iand__
self.assertEqual(i1, a)
def test11_union(self): def test11_union(self):
"Testing union()." "Testing union()."
@ -441,7 +447,9 @@ class GEOSTest(unittest.TestCase):
u1 = GEOSGeometry(union_geoms[i].wkt) u1 = GEOSGeometry(union_geoms[i].wkt)
u2 = a.union(b) u2 = a.union(b)
self.assertEqual(u1, u2) self.assertEqual(u1, u2)
self.assertEqual(u1, a | b) # Union ('|') operator self.assertEqual(u1, a | b) # __or__ is union operator
a |= b # testing __ior__
self.assertEqual(u1, a)
def test12_difference(self): def test12_difference(self):
"Testing difference()." "Testing difference()."
@ -452,7 +460,9 @@ class GEOSTest(unittest.TestCase):
d1 = GEOSGeometry(diff_geoms[i].wkt) d1 = GEOSGeometry(diff_geoms[i].wkt)
d2 = a.difference(b) d2 = a.difference(b)
self.assertEqual(d1, d2) self.assertEqual(d1, d2)
self.assertEqual(d1, a - b) # Difference ('-') operator self.assertEqual(d1, a - b) # __sub__ is difference operator
a -= b # testing __isub__
self.assertEqual(d1, a)
def test13_symdifference(self): def test13_symdifference(self):
"Testing sym_difference()." "Testing sym_difference()."
@ -463,7 +473,9 @@ class GEOSTest(unittest.TestCase):
d1 = GEOSGeometry(sdiff_geoms[i].wkt) d1 = GEOSGeometry(sdiff_geoms[i].wkt)
d2 = a.sym_difference(b) d2 = a.sym_difference(b)
self.assertEqual(d1, d2) self.assertEqual(d1, d2)
self.assertEqual(d1, a ^ b) # Symmetric difference ('^') operator self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
a ^= b # testing __ixor__
self.assertEqual(d1, a)
def test14_buffer(self): def test14_buffer(self):
"Testing buffer()." "Testing buffer()."
@ -492,6 +504,85 @@ class GEOSTest(unittest.TestCase):
self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9)
self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9)
def test15_srid(self):
"Testing the SRID property and keyword."
# Testing SRID keyword on Point
pnt = Point(5, 23, srid=4326)
self.assertEqual(4326, pnt.srid)
pnt.srid = 3084
self.assertEqual(3084, pnt.srid)
self.assertRaises(TypeError, pnt.set_srid, '4326')
# Testing SRID keyword on fromstr(), and on Polygon rings.
poly = fromstr(polygons[1].wkt, srid=4269)
self.assertEqual(4269, poly.srid)
for ring in poly: self.assertEqual(4269, ring.srid)
poly.srid = 4326
self.assertEqual(4326, poly.shell.srid)
# Testing SRID keyword on GeometryCollection
gc = GeometryCollection(Point(5, 23), LineString((0, 0), (1.5, 1.5), (3, 3)), srid=32021)
self.assertEqual(32021, gc.srid)
for i in range(len(gc)): self.assertEqual(32021, gc[i].srid)
def test16_mutable_geometries(self):
"Testing the mutability of Polygons and Geometry Collections."
### Testing the mutability of Polygons ###
for p in polygons:
poly = fromstr(p.wkt)
# Should only be able to use __setitem__ with LinearRing geometries.
self.assertRaises(TypeError, poly.__setitem__, 0, LineString((1, 1), (2, 2)))
# Constructing the new shell by adding 500 to every point in the old shell.
shell_tup = poly.shell.tuple
new_coords = []
for point in shell_tup: new_coords.append((point[0] + 500., point[1] + 500.))
shell1 = LinearRing(*tuple(new_coords))
shell2 = shell1.clone()
# Assigning polygon's exterior ring w/the new shell
poly.exterior_ring = shell1
self.assertRaises(GEOSException, str, shell1) # shell1 should no longer be accessible
self.assertEqual(poly.exterior_ring, shell2)
self.assertEqual(poly[0], shell2)
del poly, shell1, shell_tup # cleaning up
### Testing the mutability of Geometry Collections
for tg in multipoints:
mp = fromstr(tg.wkt)
for i in range(len(mp)):
# Creating a random point.
pnt = mp[i].clone()
new = Point(random.randint(1, 100), random.randint(1, 100))
tmp = new.clone()
# Testing the assignmen
mp[i] = tmp
self.assertRaises(GEOSException, len, tmp)
self.assertEqual(mp[i], new)
self.assertEqual(mp[i].wkt, new.wkt)
self.assertNotEqual(pnt, mp[i])
del mp
# 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)):
poly = mpoly[i].clone()
# Offsetting the each ring in the polygon by 500.
tmp = poly.clone()
for r in tmp:
for j in xrange(len(r)): r[j] = (r[j][0] + 500., r[j][1] + 500.)
self.assertNotEqual(poly, tmp)
new = tmp.clone() # a 'reference' copy of the geometry used in assignment
# Testing the assignment
mpoly[i] = tmp
self.assertRaises(GEOSException, str, tmp)
self.assertEqual(mpoly[i], new)
self.assertNotEqual(poly, mpoly[i])
del mpoly
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GEOSTest)) s.addTest(unittest.makeSuite(GEOSTest))