1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +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.geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon 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.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): def fromfile(file_name):
""" """

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
This module also houses GEOS Pointer utilities, including This module also houses GEOS Pointer utilities, including
get_pointer_arr(), and GEOM_PTR. 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 ctypes import c_char_p, string_at, Structure, CDLL, CFUNCTYPE, POINTER
from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.error import GEOSException
@ -21,7 +21,7 @@ except ImportError:
try: try:
from django.conf import settings from django.conf import settings
lib_name = settings.GEOS_LIBRARY_PATH lib_name = settings.GEOS_LIBRARY_PATH
except (AttributeError, EnvironmentError): except (AttributeError, EnvironmentError, ImportError):
lib_name = None lib_name = None
# Setting the appropriate name for the GEOS-C library, depending on which # Setting the appropriate name for the GEOS-C library, depending on which
@ -96,5 +96,20 @@ def geos_version():
"Returns the string version of GEOS." "Returns the string version of GEOS."
return string_at(lgeos.GEOSversion()) 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. # Calling the finishGEOS() upon exit of the interpreter.
atexit.register(lgeos.finishGEOS) atexit.register(lgeos.finishGEOS)

View File

@ -49,6 +49,12 @@ def string_from_geom(func):
### ctypes prototypes ### ### 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 # Creation routines from WKB, HEX, WKT
from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf) from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf) from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)

View File

@ -1,10 +1,6 @@
import random, unittest, sys import random, unittest, sys
from ctypes import ArgumentError from ctypes import ArgumentError
from django.contrib.gis.geos import \ 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.base import HAS_GDAL from django.contrib.gis.geos.base import HAS_GDAL
from django.contrib.gis.tests.geometries import * from django.contrib.gis.tests.geometries import *
@ -89,6 +85,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, poly.shell.srid)
self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export 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): def test02a_points(self):
"Testing Point objects." "Testing Point objects."
prev = fromstr('POINT(0 0)') prev = fromstr('POINT(0 0)')
@ -478,9 +483,17 @@ class GEOSTest(unittest.TestCase):
# However, when HEX is exported, the SRID information is lost # However, when HEX is exported, the SRID information is lost
# and set to -1. Essentially, the 'E' of the EWKB is not # 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) p2 = fromstr(p1.hex)
self.assertEqual(-1, p2.srid) self.assertEqual(exp_srid, p2.srid)
p3 = fromstr(p1.hex, srid=-1) # -1 is intended. p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
self.assertEqual(-1, p3.srid) self.assertEqual(-1, p3.srid)
@ -650,7 +663,16 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, isinstance(g2.srs, SpatialReference)) self.assertEqual(True, isinstance(g2.srs, SpatialReference))
self.assertEqual(g2.hex, g2.ogr.hex) self.assertEqual(g2.hex, g2.ogr.hex)
self.assertEqual('WGS 84', g2.srs.name) 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(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GEOSTest)) s.addTest(unittest.makeSuite(GEOSTest))