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

gis: geos: all pointer access is now done via the ptr property to prevent calling GEOS routines on NULL pointers; added the geos_version_info routine; added __copy__ and __deepcopy__ interfaces that return clones (for compatibility w/queryset-refactor); __eq__ may now compare WKT strings (for compatibility w/newforms-admin); made tests compatible w/GEOS 3.0.0 release.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6978 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-12-25 21:27:56 +00:00
parent 24617b1667
commit 31111857cb
8 changed files with 166 additions and 81 deletions

View File

@ -32,7 +32,7 @@ from django.contrib.gis.geos.base import GEOSGeometry, wkt_regex, hex_regex
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
from django.contrib.gis.geos.libgeos import geos_version
from django.contrib.gis.geos.libgeos import geos_version, geos_version_info
def fromfile(file_name):
"""

View File

@ -90,6 +90,18 @@ class GEOSGeometry(object):
# Setting the coordinate sequence for the geometry (will be None on
# geometries that do not have coordinate sequences)
self._set_cs()
@property
def ptr(self):
"""
Property for controlling access to the GEOS geometry pointer. Using
this raises an exception when the pointer is NULL, thus preventing
the C library from attempting to access an invalid memory location.
"""
if self._ptr:
return self._ptr
else:
raise GEOSException('NULL GEOS pointer encountered; was this geometry modified?')
def __del__(self):
"""
@ -98,22 +110,43 @@ class GEOSGeometry(object):
"""
if self._ptr: destroy_geom(self._ptr)
def __copy__(self):
"""
Returns a clone because the copy of a GEOSGeometry may contain an
invalid pointer location if the original is garbage collected.
"""
return self.clone()
def __deepcopy__(self, memodict):
"""
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
thus, the protocol routine needs to be implemented to return correct
copies (clones) of these GEOS objects, which use C pointers.
"""
return self.clone()
def __str__(self):
"WKT is used for the string representation."
return self.wkt
def __repr__(self):
"Short-hand representation because WKT may be very large."
return '<%s object at %s>' % (self.geom_type, hex(addressof(self._ptr)))
return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
# Comparison operators
def __eq__(self, other):
"Equivalence testing."
return self.equals_exact(other)
"""
Equivalence testing, a Geometry may be compared with another Geometry
or a WKT representation.
"""
if isinstance(other, basestring):
return self.wkt == other
else:
return self.equals_exact(other)
def __ne__(self, other):
"The not equals operator."
return not self.equals_exact(other)
return not (self == other)
### Geometry set-like operations ###
# Thanks to Sean Gillies for inspiration:
@ -151,7 +184,7 @@ class GEOSGeometry(object):
def _set_cs(self):
"Sets the coordinate sequence for this Geometry."
if self.has_cs:
self._cs = GEOSCoordSeq(get_cs(self._ptr), self.hasz)
self._cs = GEOSCoordSeq(get_cs(self.ptr), self.hasz)
else:
self._cs = None
@ -164,22 +197,22 @@ class GEOSGeometry(object):
@property
def geom_type(self):
"Returns a string representing the Geometry type, e.g. 'Polygon'"
return geos_type(self._ptr)
return geos_type(self.ptr)
@property
def geom_typeid(self):
"Returns an integer representing the Geometry type."
return geos_typeid(self._ptr)
return geos_typeid(self.ptr)
@property
def num_geom(self):
"Returns the number of geometries in the Geometry."
return get_num_geoms(self._ptr)
return get_num_geoms(self.ptr)
@property
def num_coords(self):
"Returns the number of coordinates in the Geometry."
return get_num_coords(self._ptr)
return get_num_coords(self.ptr)
@property
def num_points(self):
@ -189,11 +222,11 @@ class GEOSGeometry(object):
@property
def dims(self):
"Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
return get_dims(self._ptr)
return get_dims(self.ptr)
def normalize(self):
"Converts this Geometry to normal form (or canonical form)."
return geos_normalize(self._ptr)
return geos_normalize(self.ptr)
#### Unary predicates ####
@property
@ -202,32 +235,32 @@ class GEOSGeometry(object):
Returns a boolean indicating whether the set of points in this Geometry
are empty.
"""
return geos_isempty(self._ptr)
return geos_isempty(self.ptr)
@property
def hasz(self):
"Returns whether the geometry has a 3D dimension."
return geos_hasz(self._ptr)
return geos_hasz(self.ptr)
@property
def ring(self):
"Returns whether or not the geometry is a ring."
return geos_isring(self._ptr)
return geos_isring(self.ptr)
@property
def simple(self):
"Returns false if the Geometry not simple."
return geos_issimple(self._ptr)
return geos_issimple(self.ptr)
@property
def valid(self):
"This property tests the validity of this Geometry."
return geos_isvalid(self._ptr)
return geos_isvalid(self.ptr)
#### Binary predicates. ####
def contains(self, other):
"Returns true if other.within(this) returns true."
return geos_contains(self._ptr, other._ptr)
return geos_contains(self.ptr, other.ptr)
def crosses(self, other):
"""
@ -235,39 +268,39 @@ class GEOSGeometry(object):
is T*T****** (for a point and a curve,a point and an area or a line and
an area) 0******** (for two curves).
"""
return geos_crosses(self._ptr, other._ptr)
return geos_crosses(self.ptr, other.ptr)
def disjoint(self, other):
"""
Returns true if the DE-9IM intersection matrix for the two Geometries
is FF*FF****.
"""
return geos_disjoint(self._ptr, other._ptr)
return geos_disjoint(self.ptr, other.ptr)
def equals(self, other):
"""
Returns true if the DE-9IM intersection matrix for the two Geometries
is T*F**FFF*.
"""
return geos_equals(self._ptr, other._ptr)
return geos_equals(self.ptr, other.ptr)
def equals_exact(self, other, tolerance=0):
"""
Returns true if the two Geometries are exactly equal, up to a
specified tolerance.
"""
return geos_equalsexact(self._ptr, other._ptr, float(tolerance))
return geos_equalsexact(self.ptr, other.ptr, float(tolerance))
def intersects(self, other):
"Returns true if disjoint returns false."
return geos_intersects(self._ptr, other._ptr)
return geos_intersects(self.ptr, other.ptr)
def overlaps(self, other):
"""
Returns true if the DE-9IM intersection matrix for the two Geometries
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
"""
return geos_overlaps(self._ptr, other._ptr)
return geos_overlaps(self.ptr, other.ptr)
def relate_pattern(self, other, pattern):
"""
@ -276,32 +309,32 @@ class GEOSGeometry(object):
"""
if not isinstance(pattern, StringType) or len(pattern) > 9:
raise GEOSException('invalid intersection matrix pattern')
return geos_relatepattern(self._ptr, other._ptr, pattern)
return geos_relatepattern(self.ptr, other.ptr, pattern)
def touches(self, other):
"""
Returns true if the DE-9IM intersection matrix for the two Geometries
is FT*******, F**T***** or F***T****.
"""
return geos_touches(self._ptr, other._ptr)
return geos_touches(self.ptr, other.ptr)
def within(self, other):
"""
Returns true if the DE-9IM intersection matrix for the two Geometries
is T*F**F***.
"""
return geos_within(self._ptr, other._ptr)
return geos_within(self.ptr, other.ptr)
#### SRID Routines ####
def get_srid(self):
"Gets the SRID for the geometry, returns None if no SRID is set."
s = geos_get_srid(self._ptr)
s = geos_get_srid(self.ptr)
if s == 0: return None
else: return s
def set_srid(self, srid):
"Sets the SRID for the geometry."
geos_set_srid(self._ptr, srid)
geos_set_srid(self.ptr, srid)
srid = property(get_srid, set_srid)
#### Output Routines ####
@ -314,7 +347,7 @@ class GEOSGeometry(object):
@property
def wkt(self):
"Returns the WKT (Well-Known Text) of the Geometry."
return to_wkt(self._ptr)
return to_wkt(self.ptr)
@property
def hex(self):
@ -325,12 +358,12 @@ class GEOSGeometry(object):
"""
# A possible faster, all-python, implementation:
# str(self.wkb).encode('hex')
return to_hex(self._ptr, byref(c_size_t()))
return to_hex(self.ptr, byref(c_size_t()))
@property
def wkb(self):
"Returns the WKB of the Geometry as a buffer."
bin = to_wkb(self._ptr, byref(c_size_t()))
bin = to_wkb(self.ptr, byref(c_size_t()))
return buffer(bin)
@property
@ -374,7 +407,7 @@ class GEOSGeometry(object):
ptr = from_wkb(wkb, len(wkb))
if ptr:
# Reassigning pointer, and resetting the SRID.
destroy_geom(self._ptr)
destroy_geom(self.ptr)
self._ptr = ptr
self.srid = g.srid
else:
@ -388,7 +421,7 @@ class GEOSGeometry(object):
@property
def boundary(self):
"Returns the boundary as a newly allocated Geometry object."
return self._topology(geos_boundary(self._ptr))
return self._topology(geos_boundary(self.ptr))
def buffer(self, width, quadsegs=8):
"""
@ -398,7 +431,7 @@ class GEOSGeometry(object):
the number of segment used to approximate a quarter circle (defaults to 8).
(Text from PostGIS documentation at ch. 6.1.3)
"""
return self._topology(geos_buffer(self._ptr, width, quadsegs))
return self._topology(geos_buffer(self.ptr, width, quadsegs))
@property
def centroid(self):
@ -407,7 +440,7 @@ class GEOSGeometry(object):
of highest dimension (since the lower-dimension geometries contribute zero
"weight" to the centroid).
"""
return self._topology(geos_centroid(self._ptr))
return self._topology(geos_centroid(self.ptr))
@property
def convex_hull(self):
@ -415,32 +448,32 @@ class GEOSGeometry(object):
Returns the smallest convex Polygon that contains all the points
in the Geometry.
"""
return self._topology(geos_convexhull(self._ptr))
return self._topology(geos_convexhull(self.ptr))
def difference(self, other):
"""
Returns a Geometry representing the points making up this Geometry
that do not make up other.
"""
return self._topology(geos_difference(self._ptr, other._ptr))
return self._topology(geos_difference(self.ptr, other.ptr))
@property
def envelope(self):
"Return the envelope for this geometry (a polygon)."
return self._topology(geos_envelope(self._ptr))
return self._topology(geos_envelope(self.ptr))
def intersection(self, other):
"Returns a Geometry representing the points shared by this Geometry and other."
return self._topology(geos_intersection(self._ptr, other._ptr))
return self._topology(geos_intersection(self.ptr, other.ptr))
@property
def point_on_surface(self):
"Computes an interior point of this Geometry."
return self._topology(geos_pointonsurface(self._ptr))
return self._topology(geos_pointonsurface(self.ptr))
def relate(self, other):
"Returns the DE-9IM intersection matrix for this Geometry and the other."
return geos_relate(self._ptr, other._ptr)
return geos_relate(self.ptr, other.ptr)
def simplify(self, tolerance=0.0, preserve_topology=False):
"""
@ -455,26 +488,26 @@ class GEOSGeometry(object):
input. This is significantly slower.
"""
if preserve_topology:
return self._topology(geos_preservesimplify(self._ptr, tolerance))
return self._topology(geos_preservesimplify(self.ptr, tolerance))
else:
return self._topology(geos_simplify(self._ptr, tolerance))
return self._topology(geos_simplify(self.ptr, tolerance))
def sym_difference(self, other):
"""
Returns a set combining the points in this Geometry not in other,
and the points in other not in this Geometry.
"""
return self._topology(geos_symdifference(self._ptr, other._ptr))
return self._topology(geos_symdifference(self.ptr, other.ptr))
def union(self, other):
"Returns a Geometry representing all the points in this Geometry and other."
return self._topology(geos_union(self._ptr, other._ptr))
return self._topology(geos_union(self.ptr, other.ptr))
#### Other Routines ####
@property
def area(self):
"Returns the area of the Geometry."
return geos_area(self._ptr, byref(c_double()))
return geos_area(self.ptr, byref(c_double()))
def distance(self, other):
"""
@ -484,7 +517,7 @@ class GEOSGeometry(object):
"""
if not isinstance(other, GEOSGeometry):
raise TypeError('distance() works only on other GEOS Geometries.')
return geos_distance(self._ptr, other._ptr, byref(c_double()))
return geos_distance(self.ptr, other.ptr, byref(c_double()))
@property
def length(self):
@ -492,11 +525,11 @@ class GEOSGeometry(object):
Returns the length of this Geometry (e.g., 0 for point, or the
circumfrence of a Polygon).
"""
return geos_length(self._ptr, byref(c_double()))
return geos_length(self.ptr, byref(c_double()))
def clone(self):
"Clones this Geometry."
return GEOSGeometry(geom_clone(self._ptr), srid=self.srid)
return GEOSGeometry(geom_clone(self.ptr), srid=self.srid)
# Class mapping dictionary
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing

View File

@ -38,14 +38,14 @@ class GeometryCollection(GEOSGeometry):
# Creating the geometry pointer array.
ngeoms = len(init_geoms)
geoms = get_pointer_arr(ngeoms)
for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i]._ptr)
for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i].ptr)
super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs)
def __getitem__(self, index):
"Returns the Geometry from this Collection at the given index (0-based)."
# Checking the index and returning the corresponding GEOS geometry.
self._checkindex(index)
return GEOSGeometry(geom_clone(get_geomn(self._ptr, index)), srid=self.srid)
return GEOSGeometry(geom_clone(get_geomn(self.ptr, index)), srid=self.srid)
def __setitem__(self, index, geom):
"Sets the Geometry at the specified index."
@ -57,12 +57,12 @@ class GeometryCollection(GEOSGeometry):
geoms = get_pointer_arr(ngeoms)
for i in xrange(ngeoms):
if i == index:
geoms[i] = geom_clone(geom._ptr)
geoms[i] = geom_clone(geom.ptr)
else:
geoms[i] = geom_clone(get_geomn(self._ptr, i))
geoms[i] = geom_clone(get_geomn(self.ptr, i))
# Creating a new collection, and destroying the contents of the previous poiner.
prev_ptr = self._ptr
prev_ptr = self.ptr
srid = self.srid
self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms))
if srid: self.srid = srid

View File

@ -76,18 +76,27 @@ class GEOSCoordSeq(object):
if dim < 0 or dim > 2:
raise GEOSException('invalid ordinate dimension "%d"' % dim)
@property
def ptr(self):
"""
Property for controlling access to coordinate sequence pointer,
preventing attempted access to a NULL memory location.
"""
if self._ptr: return self._ptr
else: raise GEOSException('NULL coordinate sequence pointer encountered.')
#### Ordinate getting and setting routines ####
def getOrdinate(self, dimension, index):
"Returns the value for the given dimension and index."
self._checkindex(index)
self._checkdim(dimension)
return cs_getordinate(self._ptr, index, dimension, byref(c_double()))
return cs_getordinate(self.ptr, index, dimension, byref(c_double()))
def setOrdinate(self, dimension, index, value):
"Sets the value for the given dimension and index."
self._checkindex(index)
self._checkdim(dimension)
cs_setordinate(self._ptr, index, dimension, value)
cs_setordinate(self.ptr, index, dimension, value)
def getX(self, index):
"Get the X value at the index."
@ -117,12 +126,12 @@ class GEOSCoordSeq(object):
@property
def size(self):
"Returns the size of this coordinate sequence."
return cs_getsize(self._ptr, byref(c_uint()))
return cs_getsize(self.ptr, byref(c_uint()))
@property
def dims(self):
"Returns the dimensions of this coordinate sequence."
return cs_getdims(self._ptr, byref(c_uint()))
return cs_getdims(self.ptr, byref(c_uint()))
@property
def hasz(self):
@ -135,7 +144,7 @@ class GEOSCoordSeq(object):
### Other Methods ###
def clone(self):
"Clones this coordinate sequence."
return GEOSCoordSeq(cs_clone(self._ptr), self.hasz)
return GEOSCoordSeq(cs_clone(self.ptr), self.hasz)
@property
def kml(self):

View File

@ -166,7 +166,7 @@ class LineString(GEOSGeometry):
# Calling the base geometry initialization with the returned pointer
# from the function.
super(LineString, self).__init__(func(cs._ptr), srid=srid)
super(LineString, self).__init__(func(cs.ptr), srid=srid)
def __getitem__(self, index):
"Gets the point at the specified index."
@ -263,10 +263,10 @@ class Polygon(GEOSGeometry):
# Getting the holes array.
nholes = len(init_holes)
holes = get_pointer_arr(nholes)
for i in xrange(nholes): holes[i] = geom_clone(init_holes[i]._ptr)
for i in xrange(nholes): holes[i] = geom_clone(init_holes[i].ptr)
# Getting the shell pointer address,
shell = geom_clone(ext_ring._ptr)
shell = geom_clone(ext_ring.ptr)
# Calling with the GEOS createPolygon factory.
super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(nholes)), **kwargs)
@ -291,9 +291,9 @@ class Polygon(GEOSGeometry):
# Getting the shell
if index == 0:
shell = geom_clone(ring._ptr)
shell = geom_clone(ring.ptr)
else:
shell = geom_clone(get_extring(self._ptr))
shell = geom_clone(get_extring(self.ptr))
# Getting the interior rings (holes)
nholes = len(self)-1
@ -301,16 +301,16 @@ class Polygon(GEOSGeometry):
holes = get_pointer_arr(nholes)
for i in xrange(nholes):
if i == (index-1):
holes[i] = geom_clone(ring._ptr)
holes[i] = geom_clone(ring.ptr)
else:
holes[i] = geom_clone(get_intring(self._ptr, i))
holes[i] = geom_clone(get_intring(self.ptr, i))
holes_param = byref(holes)
else:
holes_param = None
# Getting the current pointer, replacing with the newly constructed
# geometry, and destroying the old geometry.
prev_ptr = self._ptr
prev_ptr = self.ptr
srid = self.srid
self._ptr = create_polygon(shell, holes_param, c_uint(nholes))
if srid: self.srid = srid
@ -336,18 +336,18 @@ class Polygon(GEOSGeometry):
interior ring, not the exterior ring.
"""
self._checkindex(ring_i+1)
return GEOSGeometry(geom_clone(get_intring(self._ptr, ring_i)), srid=self.srid)
return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid)
#### Polygon Properties ####
@property
def num_interior_rings(self):
"Returns the number of interior rings."
# Getting the number of rings
return get_nrings(self._ptr)
return get_nrings(self.ptr)
def get_ext_ring(self):
"Gets the exterior ring of the Polygon."
return GEOSGeometry(geom_clone(get_extring(self._ptr)), srid=self.srid)
return GEOSGeometry(geom_clone(get_extring(self.ptr)), srid=self.srid)
def set_ext_ring(self, ring):
"Sets the exterior ring of the Polygon."

View File

@ -6,7 +6,7 @@
This module also houses GEOS Pointer utilities, including
get_pointer_arr(), and GEOM_PTR.
"""
import atexit, os, sys
import atexit, os, re, sys
from ctypes import c_char_p, string_at, Structure, CDLL, CFUNCTYPE, POINTER
from django.contrib.gis.geos.error import GEOSException
@ -21,7 +21,7 @@ except ImportError:
try:
from django.conf import settings
lib_name = settings.GEOS_LIBRARY_PATH
except (AttributeError, EnvironmentError):
except (AttributeError, EnvironmentError, ImportError):
lib_name = None
# Setting the appropriate name for the GEOS-C library, depending on which
@ -96,5 +96,20 @@ def geos_version():
"Returns the string version of GEOS."
return string_at(lgeos.GEOSversion())
# Regular expression should be able to parse version strings such as
# '3.0.0rc4-CAPI-1.3.3', or '3.0.0-CAPI-1.4.1'
version_regex = re.compile(r'^(?P<version>\d+\.\d+\.\d+)(rc(?P<release_candidate>\d+))?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)$')
def geos_version_info():
"""
Returns a dictionary containing the various version metadata parsed from
the GEOS version string, including the version number, whether the version
is a release candidate (and what number release candidate), and the C API
version.
"""
ver = geos_version()
m = version_regex.match(ver)
if not m: raise GEOSException('Could not parse version info string "%s"' % ver)
return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version'))
# Calling the finishGEOS() upon exit of the interpreter.
atexit.register(lgeos.finishGEOS)

View File

@ -49,6 +49,12 @@ def string_from_geom(func):
### ctypes prototypes ###
# TODO: Tell all users to use GEOS 3.0.0, instead of the release
# candidates, and use the new Reader and Writer APIs (e.g.,
# GEOSWKT[Reader|Writer], GEOSWKB[Reader|Writer]). A good time
# to do this will be when Refractions releases a Windows PostGIS
# installer using GEOS 3.0.0.
# Creation routines from WKB, HEX, WKT
from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)

View File

@ -1,10 +1,6 @@
import random, unittest, sys
from ctypes import ArgumentError
from django.contrib.gis.geos import \
GEOSException, GEOSIndexError, \
GEOSGeometry, Point, LineString, LinearRing, Polygon, \
MultiPoint, MultiLineString, MultiPolygon, GeometryCollection, \
fromstr, geos_version, HAS_NUMPY
from django.contrib.gis.geos import *
from django.contrib.gis.geos.base import HAS_GDAL
from django.contrib.gis.tests.geometries import *
@ -89,6 +85,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(srid, poly.shell.srid)
self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export
def test01i_eq(self):
"Testing equivalence with WKT."
p = fromstr('POINT(5 23)')
self.assertEqual(p, p.wkt)
self.assertNotEqual(p, 'foo')
ls = fromstr('LINESTRING(0 0, 1 1, 5 5)')
self.assertEqual(ls, ls.wkt)
self.assertNotEqual(p, 'bar')
def test02a_points(self):
"Testing Point objects."
prev = fromstr('POINT(0 0)')
@ -478,9 +483,17 @@ class GEOSTest(unittest.TestCase):
# However, when HEX is exported, the SRID information is lost
# and set to -1. Essentially, the 'E' of the EWKB is not
# encoded in HEX by the GEOS C library for some reason.
# encoded in HEX by the GEOS C library unless the GEOSWKBWriter
# method is used. GEOS 3.0.0 will not encode -1 in the HEX
# as is done in the release candidates.
info = geos_version_info()
if info['version'] == '3.0.0' and info['release_candidate']:
exp_srid = -1
else:
exp_srid = None
p2 = fromstr(p1.hex)
self.assertEqual(-1, p2.srid)
self.assertEqual(exp_srid, p2.srid)
p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
self.assertEqual(-1, p3.srid)
@ -650,7 +663,16 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, isinstance(g2.srs, SpatialReference))
self.assertEqual(g2.hex, g2.ogr.hex)
self.assertEqual('WGS 84', g2.srs.name)
def test22_copy(self):
"Testing use with the Python `copy` module."
import copy
poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))')
cpy1 = copy.copy(poly)
cpy2 = copy.deepcopy(poly)
self.assertNotEqual(poly._ptr, cpy1._ptr)
self.assertNotEqual(poly._ptr, cpy2._ptr)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GEOSTest))