1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +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.
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

View File

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

View File

@ -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,43 +282,58 @@ 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)
if index == 0:
return self.exterior_ring
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)
# 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."
@ -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):

View File

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

View File

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

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))'),
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)'),

View File

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