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:
parent
ffcc38ebe8
commit
056f87ab28
@ -12,7 +12,7 @@ from types import StringType, 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.libgeos import lgeos, GEOSPointer, HAS_NUMPY, ISQLQuote, GEOM_FUNC_PREFIX
|
||||
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
|
||||
@ -87,7 +87,7 @@ class GEOSGeometry(object):
|
||||
|
||||
def __del__(self):
|
||||
"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
|
||||
if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
|
||||
|
||||
@ -101,11 +101,11 @@ class GEOSGeometry(object):
|
||||
# Comparison operators
|
||||
def __eq__(self, other):
|
||||
"Equivalence testing."
|
||||
return self.equals(other)
|
||||
return self.equals_exact(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"The not equals operator."
|
||||
return not self.equals(other)
|
||||
return not self.equals_exact(other)
|
||||
|
||||
### Geometry set-like operations ###
|
||||
# Thanks to Sean Gillies for inspiration:
|
||||
@ -115,21 +115,76 @@ class GEOSGeometry(object):
|
||||
"Returns the union of this Geometry and the 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
|
||||
def __and__(self, other):
|
||||
"Returns the intersection of this Geometry and the 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
|
||||
def __sub__(self, other):
|
||||
"Return the difference this Geometry and the 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
|
||||
def __xor__(self, other):
|
||||
"Return the symmetric difference of this Geometry and the 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 ####
|
||||
def __conform__(self, proto):
|
||||
# Does the given protocol conform to what Psycopg2 expects?
|
||||
@ -140,7 +195,8 @@ class GEOSGeometry(object):
|
||||
|
||||
def getquoted(self):
|
||||
"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 ####
|
||||
@property
|
||||
@ -299,10 +355,8 @@ class GEOSGeometry(object):
|
||||
def get_srid(self):
|
||||
"Gets the SRID for the geometry, returns None if no SRID is set."
|
||||
s = lgeos.GEOSGetSRID(self._ptr())
|
||||
if s == 0:
|
||||
return None
|
||||
else:
|
||||
return s
|
||||
if s == 0: return None
|
||||
else: return s
|
||||
|
||||
def set_srid(self, srid):
|
||||
"Sets the SRID for the geometry."
|
||||
@ -404,7 +458,7 @@ class GEOSGeometry(object):
|
||||
|
||||
def clone(self):
|
||||
"Clones this Geometry."
|
||||
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()))
|
||||
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()), srid=self.srid)
|
||||
|
||||
# Class mapping dictionary
|
||||
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
|
||||
|
@ -4,37 +4,25 @@
|
||||
"""
|
||||
from ctypes import c_int, c_uint, byref, cast
|
||||
from types import TupleType, ListType
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, init_from_geom, get_pointer_arr, GEOM_PTR
|
||||
from django.contrib.gis.geos.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
|
||||
|
||||
def init_from_poly(poly):
|
||||
"Internal routine used for initializing Geometry Collections from Polygons."
|
||||
# Constructing a new Polygon to take control of the rings.
|
||||
p = Polygon(*tuple(ring for ring in poly))
|
||||
|
||||
# If this Polygon came from a GeometryCollection, it is a child
|
||||
# and the parent geometry pointer is nullified.
|
||||
if poly._parent: poly._parent.nullify()
|
||||
|
||||
# Nullifying the polygon pointer
|
||||
poly._ptr.nullify()
|
||||
|
||||
# Returning the address of the new Polygon.
|
||||
return p._ptr()
|
||||
|
||||
class GeometryCollection(GEOSGeometry):
|
||||
_allowed = (Point, LineString, LinearRing, Polygon)
|
||||
_typeid = 7
|
||||
|
||||
def __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 = False
|
||||
self._parent = None
|
||||
|
||||
# Checking the arguments
|
||||
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 isinstance(args[0], (TupleType, ListType)):
|
||||
@ -54,23 +42,18 @@ class GeometryCollection(GEOSGeometry):
|
||||
|
||||
# Incrementing through each input geometry.
|
||||
for i in xrange(ngeom):
|
||||
if isinstance(init_geoms[i], Polygon):
|
||||
# Special care is taken when importing from Polygons
|
||||
geoms[i] = cast(init_from_poly(init_geoms[i]), GEOM_PTR)
|
||||
else:
|
||||
geoms[i] = cast(init_from_geom(init_geoms[i]), GEOM_PTR)
|
||||
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)
|
||||
|
||||
def __del__(self):
|
||||
"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 self._ptr.valid:
|
||||
# Nullifying pointers to internal geometries, preventing any attempted future access.
|
||||
for k in self._geoms: self._geoms[k].nullify()
|
||||
super(GeometryCollection, self).__del__()
|
||||
else:
|
||||
# Internal memory has become part of other Geometry objects, must delete the
|
||||
# internal objects which are still valid individually, since calling destructor
|
||||
@ -80,20 +63,38 @@ class GeometryCollection(GEOSGeometry):
|
||||
if self._geoms[k].valid:
|
||||
lgeos.GEOSGeom_destroy(self._geoms[k].address)
|
||||
self._geoms[k].nullify()
|
||||
super(GeometryCollection, self).__del__()
|
||||
|
||||
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.
|
||||
self._checkindex(index)
|
||||
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):
|
||||
"For iteration on the multiple geometries."
|
||||
"Iterates over each Geometry in the Collection."
|
||||
for i in xrange(len(self)):
|
||||
yield self.__getitem__(i)
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of geometries in this collection."
|
||||
"Returns the number of geometries in this Collection."
|
||||
return self.num_geom
|
||||
|
||||
def _checkindex(self, index):
|
||||
@ -101,13 +102,18 @@ class GeometryCollection(GEOSGeometry):
|
||||
if index < 0 or index >= self.num_geom:
|
||||
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):
|
||||
"Populates the internal child geometry dictionary."
|
||||
self._geoms = {}
|
||||
for i in xrange(self.num_geom):
|
||||
self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i)))
|
||||
|
||||
|
||||
# MultiPoint, MultiLineString, and MultiPolygon class definitions.
|
||||
class MultiPoint(GeometryCollection):
|
||||
_allowed = Point
|
||||
|
@ -7,7 +7,7 @@
|
||||
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, 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.error import GEOSException, GEOSGeometryIndexError
|
||||
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
|
||||
"""
|
||||
|
||||
# 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.
|
||||
@ -132,7 +134,9 @@ 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 len(args) == 1: coords = args[0]
|
||||
@ -254,6 +258,7 @@ class Polygon(GEOSGeometry):
|
||||
poly = Polygon(shell, (hole1, hole2))
|
||||
"""
|
||||
self._ptr = GEOSPointer(0) # Initially NULL
|
||||
self._parent = None
|
||||
self._rings = {}
|
||||
if not args:
|
||||
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
|
||||
@ -277,44 +282,59 @@ class Polygon(GEOSGeometry):
|
||||
holes = get_pointer_arr(nholes)
|
||||
for i in xrange(nholes):
|
||||
# Casting to the Geometry Pointer type
|
||||
holes[i] = cast(init_from_geom(init_holes[i]), GEOM_PTR)
|
||||
holes[i] = cast(init_holes[i]._nullify(), GEOM_PTR)
|
||||
|
||||
# Getting the shell pointer address,
|
||||
shell = init_from_geom(ext_ring)
|
||||
shell = ext_ring._nullify()
|
||||
|
||||
# Calling with the GEOS createPolygon factory.
|
||||
super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)), **kwargs)
|
||||
|
||||
def __del__(self):
|
||||
"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 self._ptr.valid:
|
||||
# Nulling the pointers to internal rings, preventing any attempted future access
|
||||
for k in self._rings: self._rings[k].nullify()
|
||||
super(Polygon, self).__del__()
|
||||
else:
|
||||
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.
|
||||
# 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()
|
||||
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."""
|
||||
if index < 0 or index > self.num_interior_rings:
|
||||
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
|
||||
else:
|
||||
if index == 0:
|
||||
return self.exterior_ring
|
||||
else:
|
||||
# Getting the interior ring, have to subtract 1 from the index.
|
||||
return self.get_interior_ring(index-1)
|
||||
|
||||
def __setitem__(self, index, ring):
|
||||
"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):
|
||||
"Iterates over each ring in the polygon."
|
||||
for i in xrange(len(self)):
|
||||
@ -324,6 +344,17 @@ class Polygon(GEOSGeometry):
|
||||
"Returns the number of rings in this Polygon."
|
||||
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):
|
||||
"Populates the internal rings dictionary."
|
||||
# Getting the exterior ring first for the 0th index.
|
||||
@ -336,13 +367,9 @@ class Polygon(GEOSGeometry):
|
||||
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."""
|
||||
|
||||
# Making sure the ring index is within range
|
||||
if ring_i < 0 or ring_i >= self.num_interior_rings:
|
||||
raise IndexError, 'ring index out of range'
|
||||
|
||||
# Returning the ring from the internal ring dictionary (have to
|
||||
# add one to the index)
|
||||
# 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)
|
||||
|
||||
#### Polygon Properties ####
|
||||
@ -361,14 +388,13 @@ class Polygon(GEOSGeometry):
|
||||
"Gets the exterior ring of the Polygon."
|
||||
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
|
||||
raise NotImplementedError
|
||||
self[0] = ring
|
||||
|
||||
# properties for the exterior ring/shell
|
||||
exterior_ring = property(get_ext_ring)
|
||||
shell = property(get_ext_ring)
|
||||
exterior_ring = property(get_ext_ring, set_ext_ring)
|
||||
shell = property(get_ext_ring, set_ext_ring)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
|
@ -18,11 +18,13 @@ try:
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
# Psycopg2 supported?
|
||||
# Are psycopg2 and GeoDjango models being used?
|
||||
try:
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
except ImportError:
|
||||
from django.contrib.gis.db.backend.postgis import GEOM_FUNC_PREFIX
|
||||
except (ImportError, EnvironmentError):
|
||||
ISQLQuote = None
|
||||
GEOM_FUNC_PREFIX = None
|
||||
|
||||
# Setting the appropriate name for the GEOS-C library, depending on which
|
||||
# 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);"
|
||||
lgeos.initGEOS(notice_h, error_h)
|
||||
|
||||
#### GEOS Geometry Pointer utilities. ####
|
||||
#### GEOS Geometry Pointer object, related C data structures, and functions. ####
|
||||
|
||||
# Opaque GEOS geometry structure
|
||||
class GEOSGeom_t(Structure):
|
||||
"Opaque structure used when arrays of geometries are needed as parameters."
|
||||
pass
|
||||
|
||||
# Pointer to opaque geometry structure
|
||||
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||
|
||||
# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection GEOS routines
|
||||
def get_pointer_arr(n):
|
||||
"Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
|
||||
GeomArr = GEOM_PTR * n
|
||||
return GeomArr()
|
||||
|
||||
#### GEOS Pointer object and routines ####
|
||||
class GEOSPointer(object):
|
||||
"""The GEOSPointer provides a layer of abstraction in accessing the values returned by
|
||||
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
|
||||
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."
|
||||
return self.valid
|
||||
|
||||
def __str__(self):
|
||||
return str(self.address)
|
||||
|
||||
def __repr__(self):
|
||||
return 'GEOSPointer(%s)' % self.address
|
||||
|
||||
### GEOSPointer Properties ###
|
||||
@property
|
||||
def address(self):
|
||||
@ -161,21 +167,3 @@ class GEOSPointer(object):
|
||||
# Nullifying both the geometry and coordinate sequence pointer.
|
||||
self.set(0)
|
||||
self.set(0, coordseq=True)
|
||||
|
||||
def init_from_geom(geom):
|
||||
"""During initialization of geometries from other geometries, this routine is
|
||||
used to nullify any parent geometries (since they will now be missing memory
|
||||
components) and to nullify the geometry itself to prevent future access.
|
||||
Only the address (an integer) of the current geometry is returned for use in
|
||||
initializing the new geometry."""
|
||||
# First getting the memory address of the geometry.
|
||||
address = geom._ptr()
|
||||
|
||||
# If the geometry is a child geometry, then the parent geometry pointer is
|
||||
# nullified.
|
||||
if geom._parent: geom._parent.nullify()
|
||||
|
||||
# Nullifying the geometry pointer
|
||||
geom._ptr.nullify()
|
||||
|
||||
return address
|
||||
|
@ -56,9 +56,13 @@ def run_tests(module_list, verbosity=1):
|
||||
from django.conf import settings
|
||||
|
||||
# Getting initial values.
|
||||
old_debug = settings.DEBUG
|
||||
old_name = copy(settings.DATABASE_NAME)
|
||||
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
|
||||
# adding the model test suites to our suite package.
|
||||
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.
|
||||
destroy_test_db(old_name, verbosity)
|
||||
settings.DEBUG = old_debug
|
||||
settings.INSTALLED_APPS = old_installed
|
||||
|
||||
# Returning the total failures and errors
|
||||
|
@ -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))'),
|
||||
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))'),
|
||||
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))'),
|
||||
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)))'),
|
||||
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)'),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import unittest
|
||||
import random, unittest
|
||||
from django.contrib.gis.geos import \
|
||||
GEOSException, GEOSGeometryIndexError, \
|
||||
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([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):
|
||||
"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, poly[0].tuple) # Testing __getitem__
|
||||
|
||||
# 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:
|
||||
self.assertEqual(ring.geom_type, 'LinearRing')
|
||||
self.assertEqual(ring.geom_typeid, 2)
|
||||
self.assertEqual(r.geom_type, 'LinearRing')
|
||||
self.assertEqual(r.geom_typeid, 2)
|
||||
|
||||
# Testing polygon construction.
|
||||
self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
|
||||
@ -224,8 +229,8 @@ class GEOSTest(unittest.TestCase):
|
||||
except TypeError:
|
||||
pass
|
||||
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(False, poly == prev) # Even different from the clone we just made
|
||||
self.assertEqual(newval, poly[0][1]) # The point in the polygon should be the new value
|
||||
self.assertEqual(False, poly == prev) # Should be different from the clone we just made
|
||||
|
||||
def test05b_multipolygons(self):
|
||||
"Testing MultiPolygon objects."
|
||||
@ -426,11 +431,12 @@ class GEOSTest(unittest.TestCase):
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
i1 = GEOSGeometry(intersect_geoms[i].wkt)
|
||||
|
||||
self.assertEqual(True, a.intersects(b))
|
||||
i2 = a.intersection(b)
|
||||
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):
|
||||
"Testing union()."
|
||||
@ -441,7 +447,9 @@ class GEOSTest(unittest.TestCase):
|
||||
u1 = GEOSGeometry(union_geoms[i].wkt)
|
||||
u2 = a.union(b)
|
||||
self.assertEqual(u1, u2)
|
||||
self.assertEqual(u1, a | b) # Union ('|') operator
|
||||
self.assertEqual(u1, a | b) # __or__ is union operator
|
||||
a |= b # testing __ior__
|
||||
self.assertEqual(u1, a)
|
||||
|
||||
def test12_difference(self):
|
||||
"Testing difference()."
|
||||
@ -452,7 +460,9 @@ class GEOSTest(unittest.TestCase):
|
||||
d1 = GEOSGeometry(diff_geoms[i].wkt)
|
||||
d2 = a.difference(b)
|
||||
self.assertEqual(d1, d2)
|
||||
self.assertEqual(d1, a - b) # Difference ('-') operator
|
||||
self.assertEqual(d1, a - b) # __sub__ is difference operator
|
||||
a -= b # testing __isub__
|
||||
self.assertEqual(d1, a)
|
||||
|
||||
def test13_symdifference(self):
|
||||
"Testing sym_difference()."
|
||||
@ -463,7 +473,9 @@ class GEOSTest(unittest.TestCase):
|
||||
d1 = GEOSGeometry(sdiff_geoms[i].wkt)
|
||||
d2 = a.sym_difference(b)
|
||||
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):
|
||||
"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][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():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(GEOSTest))
|
||||
|
Loading…
x
Reference in New Issue
Block a user