diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py index 7f26690aec..90e2605240 100644 --- a/django/contrib/gis/geos/__init__.py +++ b/django/contrib/gis/geos/__init__.py @@ -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): """ diff --git a/django/contrib/gis/geos/base.py b/django/contrib/gis/geos/base.py index 51a3e366db..1f16a259ca 100644 --- a/django/contrib/gis/geos/base.py +++ b/django/contrib/gis/geos/base.py @@ -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 diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py index 5ca7e57909..7f27bf57fc 100644 --- a/django/contrib/gis/geos/collections.py +++ b/django/contrib/gis/geos/collections.py @@ -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 diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py index 8cbf25c0f2..5d47985192 100644 --- a/django/contrib/gis/geos/coordseq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -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): diff --git a/django/contrib/gis/geos/geometries.py b/django/contrib/gis/geos/geometries.py index 47aa206687..5a7ef5eabd 100644 --- a/django/contrib/gis/geos/geometries.py +++ b/django/contrib/gis/geos/geometries.py @@ -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." diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index ed02357540..f05cbd2905 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -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\d+\.\d+\.\d+)(rc(?P\d+))?-CAPI-(?P\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) diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py index 83cdc4657b..e5942a5957 100644 --- a/django/contrib/gis/geos/prototypes/geom.py +++ b/django/contrib/gis/geos/prototypes/geom.py @@ -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) diff --git a/django/contrib/gis/tests/test_geos.py b/django/contrib/gis/tests/test_geos.py index 6a255c7c4f..8dc8a3b78f 100644 --- a/django/contrib/gis/tests/test_geos.py +++ b/django/contrib/gis/tests/test_geos.py @@ -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))