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

gis: big changes within in GEOS internals:

(1) All Geometry types and Collections now have their own constructor (e.g., Polygon(LinearRing(..)), MultiPoint(Point(..), Point(..)))
   (2) Memory management improved, laying the foundation for fully mutable geometries.
   (3) Added set-like operations (|, &, -, ^)
   (4) Added & improved tests.
   (5) docstring changes


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5742 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-07-21 17:39:09 +00:00
parent 8162e3543c
commit e922445186
9 changed files with 582 additions and 185 deletions

View File

@ -30,8 +30,13 @@
"""
from base import GEOSGeometry
from geometries import Point, LineString, LinearRing, HAS_NUMPY
from error import GEOSException
from geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
from collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
from error import GEOSException, GEOSGeometryIndexError
def fromstr(wkt_or_hex):
"Given a string value (wkt or hex), returns a GEOSGeometry object."
return GEOSGeometry(wkt_or_hex)
def hex_to_wkt(hex):
"Converts HEXEWKB into WKT."

View File

@ -1,35 +1,38 @@
# Trying not to pollute the namespace.
"""
This module contains the 'base' GEOSGeometry object -- all GEOS geometries
inherit from this object.
"""
# ctypes and types dependencies.
from ctypes import \
byref, string_at, create_string_buffer, pointer, \
c_char_p, c_double, c_int, c_size_t
from types import StringType, IntType, FloatType, TupleType, ListType
from types import StringType, IntType, FloatType
# Getting GEOS-related dependencies.
# Python and GEOS-related dependencies.
import re
from warnings import warn
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
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
if HAS_NUMPY:
from numpy import ndarray, array
# For recognizing HEXEWKB.
hex_regex = re.compile(r'^[0-9A-Fa-f]+')
# Regular expression for recognizing HEXEWKB.
hex_regex = re.compile(r'^[0-9A-Fa-f]+$')
class GEOSGeometry(object):
"A class that, generally, encapsulates a GEOS geometry."
#### Python 'magic' routines ####
def __init__(self, geo_input, input_type=False, child=False):
def __init__(self, geo_input, input_type=False, parent=False):
"""The constructor for GEOS geometry objects. May take the following
strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form).
The `input_type` keyword has been deprecated -- geometry type is now auto-detected.
The `child` keyword is for internal use only, and indicates to the garbage collector
not to delete this geometry if it was spawned from a parent (e.g., the exterior
ring from a polygon).
The `parent` keyword is for internal use only, and indicates to the garbage collector
not to delete this geometry because it was spawned from a parent (e.g., the exterior
ring from a polygon). Its value is the GEOSPointer of the parent geometry.
"""
# Initially, setting the pointer to NULL
@ -55,84 +58,126 @@ class GEOSGeometry(object):
raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input))
if bool(g):
# If we have a GEOSPointer object, just set the '_ptr' attribute with g
# If we have a GEOSPointer object, just set the '_ptr' attribute with input
if isinstance(g, GEOSPointer): self._ptr = g
else: self._ptr.set(g) # Otherwise, set the address
else: self._ptr.set(g) # Otherwise, set with the address
else:
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
# Setting the 'child' flag -- when the object is labeled with this flag
# it will not be destroyed by __del__(). This is used for child geometries from
# Setting the 'parent' flag -- when the object is labeled with this flag
# it will not be destroyed by __del__(). This is used for child geometries spawned from
# parent geometries (e.g., LinearRings from a Polygon, Points from a MultiPoint, etc.).
self._child = child
if isinstance(parent, GEOSPointer):
self._parent = parent
else:
self._parent = GEOSPointer(0)
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
self.__class__ = GEOS_CLASSES[self.geom_type]
# Getting the coordinate sequence for the geometry (will be None on geometries that
# do not have coordinate sequences)
self._get_cs()
# Extra setup needed for Geometries that may be parents.
if isinstance(self, GeometryCollection): self._geoms = {}
if isinstance(self, Polygon): self._rings = {}
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
def __del__(self):
"Destroys this geometry -- only if the pointer is valid and this is not a child geometry."
#print 'Deleting %s (child=%s, valid=%s)' % (self.geom_type, self._child, self._ptr.valid)
if self._ptr.valid and not self._child: lgeos.GEOSGeom_destroy(self._ptr())
"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)
# Only calling destroy on valid pointers not spawned from a parent
if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
def __str__(self):
"WKT is used for the string representation."
return self.wkt
def __repr__(self):
return '<%s object>' % self.geom_type
# Comparison operators
def __eq__(self, other):
"Equivalence testing."
return self.equals(other)
def __ne__(self, other):
"The not equals operator."
return not self.equals(other)
### Geometry set-like operations ###
# Thanks to Sean Gillies for inspiration:
# http://lists.gispython.org/pipermail/community/2007-July/001034.html
# g = g1 | g2
def __or__(self, other):
"Returns 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)
# g = g1 - g2
def __sub__(self, other):
"Return the difference 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)
#### Coordinate Sequence Routines ####
def _cache_cs(self):
"Caches the coordinate sequence for this Geometry."
if not hasattr(self, '_cs'):
# Only these geometries are allowed to have coordinate sequences.
if self.geom_type in ('LineString', 'LinearRing', 'Point'):
self._cs = GEOSCoordSeq(GEOSPointer(lgeos.GEOSGeom_getCoordSeq(self._ptr())), self.hasz)
else:
self._cs = None
@property
def has_cs(self):
"Returns True if this Geometry has a coordinate sequence, False if not."
# Only these geometries are allowed to have coordinate sequences.
if isinstance(self, (Point, LineString, LinearRing)):
return True
else:
return False
def _get_cs(self):
"Gets the coordinate sequence for this Geometry."
if self.has_cs:
self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True)
self._cs = GEOSCoordSeq(self._ptr, self.hasz)
else:
self._cs = None
@property
def coord_seq(self):
"Returns the coordinate sequence for the geometry."
# Getting the coordinate sequence for the geometry
self._cache_cs()
# Returning a GEOSCoordSeq wrapped around the pointer.
"Returns the coordinate sequence for this Geometry."
return self._cs
#### Geometry Info ####
@property
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 string_at(lgeos.GEOSGeomType(self._ptr()))
@property
def geom_typeid(self):
"Returns an integer representing the geometry type."
"Returns an integer representing the Geometry type."
return lgeos.GEOSGeomTypeId(self._ptr())
@property
def num_geom(self):
"Returns the number of geometries in the geometry."
"Returns the number of geometries in the Geometry."
n = lgeos.GEOSGetNumGeometries(self._ptr())
if n == -1: raise GEOSException, 'Error getting number of geometries.'
else: return n
@property
def num_coords(self):
"Returns the number of coordinates in the geometry."
"Returns the number of coordinates in the Geometry."
n = lgeos.GEOSGetNumCoordinates(self._ptr())
if n == -1: raise GEOSException, 'Error getting number of coordinates.'
else: return n
@property
def num_points(self):
"Returns the number points, or coordinates, in the geometry."
"Returns the number points, or coordinates, in the Geometry."
return self.num_coords
@property
@ -145,6 +190,7 @@ class GEOSGeometry(object):
status = lgeos.GEOSNormalize(self._ptr())
if status == -1: raise GEOSException, 'failed to normalize geometry'
## Internal for GEOS unary & binary predicate functions ##
def _unary_predicate(self, func):
"Returns the result, or raises an exception for the given unary predicate function."
val = func(self._ptr())
@ -164,12 +210,12 @@ class GEOSGeometry(object):
#### Unary predicates ####
@property
def empty(self):
"Returns a boolean indicating whether the set of points in this geometry are empty."
"Returns a boolean indicating whether the set of points in this Geometry are empty."
return self._unary_predicate(lgeos.GEOSisEmpty)
@property
def valid(self):
"This property tests the validity of this geometry."
"This property tests the validity of this Geometry."
return self._unary_predicate(lgeos.GEOSisValid)
@property
@ -190,17 +236,17 @@ class GEOSGeometry(object):
#### Binary predicates. ####
def relate_pattern(self, other, pattern):
"""Returns true if the elements in the DE-9IM intersection matrix for
the two Geometrys match the elements in pattern."""
the two Geometries match the elements in pattern."""
if len(pattern) > 9:
raise GEOSException, 'invalid intersection matrix pattern'
return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern))
def disjoint(self, other):
"Returns true if the DE-9IM intersection matrix for the two Geometrys is FF*FF****."
"Returns true if the DE-9IM intersection matrix for the two Geometries is FF*FF****."
return self._binary_predicate(lgeos.GEOSDisjoint, other)
def touches(self, other):
"Returns true if the DE-9IM intersection matrix for the two Geometrys is FT*******, F**T***** or F***T****."
"Returns true if the DE-9IM intersection matrix for the two Geometries is FT*******, F**T***** or F***T****."
return self._binary_predicate(lgeos.GEOSTouches, other)
def intersects(self, other):
@ -208,12 +254,12 @@ class GEOSGeometry(object):
return self._binary_predicate(lgeos.GEOSIntersects, other)
def crosses(self, other):
"""Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T****** (for a point and a curve,
"""Returns true if the DE-9IM intersection matrix for the two Geometries 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 self._binary_predicate(lgeos.GEOSCrosses, other)
def within(self, other):
"Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**F***."
"Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**F***."
return self._binary_predicate(lgeos.GEOSWithin, other)
def contains(self, other):
@ -221,16 +267,16 @@ class GEOSGeometry(object):
return self._binary_predicate(lgeos.GEOSContains, other)
def overlaps(self, other):
"""Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T***T** (for two points
"""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 self._binary_predicate(lgeos.GEOSOverlaps, other)
def equals(self, other):
"Returns true if the DE-9IM intersection matrix for the two Geometrys is T*F**FFF*."
"Returns true if the DE-9IM intersection matrix for the two Geometries is T*F**FFF*."
return self._binary_predicate(lgeos.GEOSEquals, other)
def equals_exact(self, other, tolerance=0):
"Returns true if the two Geometrys are exactly equal, up to a specified tolerance."
"Returns true if the two Geometries are exactly equal, up to a specified tolerance."
tol = c_double(tolerance)
return self._binary_predicate(lgeos.GEOSEqualsExact, other, tol)
@ -290,7 +336,7 @@ class GEOSGeometry(object):
@property
def centroid(self):
"""The centroid is equal to the centroid of the set of component Geometrys
"""The centroid is equal to the centroid of the set of component Geometries
of highest dimension (since the lower-dimension geometries contribute zero
"weight" to the centroid)."""
return self._unary_topology(lgeos.GEOSGetCentroid)
@ -344,7 +390,7 @@ class GEOSGeometry(object):
def clone(self):
"Clones this Geometry."
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()))
# Class mapping dictionary
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
@ -352,8 +398,8 @@ GEOS_CLASSES = {'Point' : Point,
'Polygon' : Polygon,
'LineString' : LineString,
'LinearRing' : LinearRing,
'GeometryCollection' : GeometryCollection,
'MultiPoint' : MultiPoint,
'MultiLineString' : MultiLineString,
'MultiPolygon' : MultiPolygon,
'GeometryCollection' : GeometryCollection,
}

View File

@ -2,33 +2,94 @@
This module houses the Geometry Collection objects:
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
"""
from ctypes import c_int
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer
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.base import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException
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):
self._ptr = GEOSPointer(0) # Initially NULL
self._geoms = {}
self._parent = False
if not args:
raise TypeError, 'Must provide at least one LinearRing to initialize Polygon.'
if len(args) == 1: # If only one geometry provided or a list of geometries is provided
if isinstance(args[0], (TupleType, ListType)):
init_geoms = args[0]
else:
init_geoms = args
else:
init_geoms = args
# Ensuring that only the permitted geometries are allowed in this collection
if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
raise TypeError, 'Invalid Geometry type encountered in the arguments.'
# Creating the geometry pointer array
ngeom = len(init_geoms)
geoms = get_pointer_arr(ngeom)
# 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)
# 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)))
def __del__(self):
"Override the GEOSGeometry delete routine to safely take care of any spawned geometries."
# Nullifying the pointers to internal geometries, preventing any attempted future access
for k in self._geoms: self._geoms[k].nullify()
super(GeometryCollection, self).__del__() # Calling the parent __del__() method.
"Overloaded deletion method for Geometry Collections."
#print '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
# on entire geometry will result in an attempted deletion of NULL pointers for
# the missing components.
for k in self._geoms:
if self._geoms[k].valid:
lgeos.GEOSGeom_destroy(self._geoms[k].address)
self._geoms[k].nullify()
def __getitem__(self, index):
"For indexing on the multiple geometries."
# Checking the index and returning the corresponding GEOS geometry.
self._checkindex(index)
# Setting an entry in the _geoms dictionary for the requested geometry.
if not index in self._geoms:
self._geoms[index] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(index)))
# Cloning the GEOS Geometry first, before returning it.
return GEOSGeometry(self._geoms[index], child=True)
return GEOSGeometry(self._geoms[index], parent=self._ptr)
def __iter__(self):
"For iteration on the multiple geometries."
for i in xrange(self.__len__()):
for i in xrange(len(self)):
yield self.__getitem__(i)
def __len__(self):
@ -40,7 +101,20 @@ class GeometryCollection(GEOSGeometry):
if index < 0 or index >= self.num_geom:
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
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): pass
class MultiLineString(GeometryCollection): pass
class MultiPolygon(GeometryCollection): pass
class MultiPoint(GeometryCollection):
_allowed = Point
_typeid = 4
class MultiLineString(GeometryCollection):
_allowed = (LineString, LinearRing)
_typeid = 5
class MultiPolygon(GeometryCollection):
_allowed = Polygon
_typeid = 6

View File

@ -1,6 +1,8 @@
from django.contrib.gis.geos.libgeos import lgeos
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
from ctypes import c_double, c_int, c_uint, byref
from types import ListType, TupleType
if HAS_NUMPY: from numpy import ndarray
"""
This module houses the GEOSCoordSeq object, and is used internally
@ -14,6 +16,8 @@ class GEOSCoordSeq(object):
#### Python 'magic' routines ####
def __init__(self, ptr, z=False):
"Initializes from a GEOS pointer."
if not isinstance(ptr, GEOSPointer):
raise TypeError, 'Coordinate sequence should initialize with a GEOSPointer.'
self._ptr = ptr
self._z = z
@ -39,6 +43,14 @@ class GEOSCoordSeq(object):
def __setitem__(self, index, value):
"Can use the index [] operator to set coordinate sequence at an index."
# Checking the input value
if isinstance(value, (ListType, TupleType)):
pass
elif HAS_NUMPY and isinstance(value, ndarray):
pass
else:
raise TypeError, 'Must set coordinate with a sequence (list, tuple, or numpy array).'
# Checking the dims of the input
if self.dims == 3 and self._z:
n_args = 3
set_3d = True
@ -47,6 +59,7 @@ class GEOSCoordSeq(object):
set_3d = False
if len(value) != n_args:
raise TypeError, 'Dimension of value does not match.'
# Setting the X, Y, Z
self.setX(index, value[0])
self.setY(index, value[1])
if set_3d: self.setZ(index, value[2])
@ -73,11 +86,11 @@ class GEOSCoordSeq(object):
dim = c_uint(dimension)
idx = c_uint(index)
# 'd' is the value of the point, passed in by reference
# 'd' is the value of the ordinate, passed in by reference
d = c_double()
status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr(), idx, dim, byref(d))
status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr.coordseq(), idx, dim, byref(d))
if status == 0:
raise GEOSException, 'could not retrieve %th ordinate at index: %s' % (str(dimension), str(index))
raise GEOSException, 'could not retrieve %sth ordinate at index: %s' % (dimension, index)
return d.value
def setOrdinate(self, dimension, index, value):
@ -90,7 +103,7 @@ class GEOSCoordSeq(object):
idx = c_uint(index)
# Setting the ordinate
status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr(), idx, dim, c_double(value))
status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr.coordseq(), idx, dim, c_double(value))
if status == 0:
raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index)
@ -123,7 +136,7 @@ class GEOSCoordSeq(object):
def size(self):
"Returns the size of this coordinate sequence."
n = c_uint(0)
status = lgeos.GEOSCoordSeq_getSize(self._ptr(), byref(n))
status = lgeos.GEOSCoordSeq_getSize(self._ptr.coordseq(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq size.'
return n.value
@ -132,7 +145,7 @@ class GEOSCoordSeq(object):
def dims(self):
"Returns the dimensions of this coordinate sequence."
n = c_uint(0)
status = lgeos.GEOSCoordSeq_getDimensions(self._ptr(), byref(n))
status = lgeos.GEOSCoordSeq_getDimensions(self._ptr.coordseq(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq dimensions.'
return n.value
@ -146,7 +159,7 @@ class GEOSCoordSeq(object):
@property
def clone(self):
"Clones this coordinate sequence."
pass
return GEOSCoordSeq(GEOSPointer(0, lgeos.GEOSCoordSeq_clone(self._ptr.coordseq())), self.hasz)
@property
def tuple(self):

View File

@ -1,12 +1,16 @@
from ctypes import c_double, c_int, c_uint
"""
This module houses the Point, LineString, LinearRing, and Polygon OGC
geometry classes. All geometry classes in this module inherit from
GEOSGeometry.
"""
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, HAS_NUMPY
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, init_from_geom, GEOM_PTR, HAS_NUMPY
from django.contrib.gis.geos.base import GEOSGeometry
from django.contrib.gis.geos.error import GEOSException
if HAS_NUMPY:
from numpy import ndarray, array
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
if HAS_NUMPY: from numpy import ndarray, array
class Point(GEOSGeometry):
@ -17,6 +21,8 @@ class Point(GEOSGeometry):
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
"""
self._ptr = GEOSPointer(0) # Initially NULL
if isinstance(x, (TupleType, ListType)):
# Here a tuple or list was passed in under the ``x`` parameter.
ndim = len(x)
@ -52,14 +58,17 @@ class Point(GEOSGeometry):
# Initializing from the geometry, and getting a Python object
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs))
def __len__(self):
"Returns the number of dimensions for this Point (either 2 or 3)."
if self.hasz: return 3
else: return 2
def _getOrdinate(self, dim, idx):
"The coordinate sequence getOrdinate() wrapper."
self._cache_cs()
return self._cs.getOrdinate(dim, idx)
def _setOrdinate(self, dim, idx, value):
"The coordinate sequence setOrdinate() wrapper."
self._cache_cs()
self._cs.setOrdinate(dim, idx, value)
def get_x(self):
@ -100,38 +109,50 @@ class Point(GEOSGeometry):
### Tuple setting and retrieval routines. ###
def get_tuple(self):
"Returns a tuple of the point."
self._cache_cs()
return self._cs.tuple
def set_tuple(self):
"Sets the tuple for this point object."
pass
def set_tuple(self, tup):
"Sets the coordinates of the point with the given tuple."
self._cs[0] = tup
# The tuple property
tuple = property(get_tuple, set_tuple)
class LineString(GEOSGeometry):
#### Python 'magic' routines ####
def __init__(self, coords, ring=False):
"""Initializes on the given sequence, may take lists, tuples, or NumPy arrays
of X,Y pairs."""
def __init__(self, *args, **kwargs):
"""Initializes on the given sequence -- may take lists, tuples, NumPy arrays
of X,Y pairs, or Point objects. If Point objects are used, ownership is
_not_ transferred to the LineString object.
Examples:
ls = LineString((1, 1), (2, 2))
ls = LineString([(1, 1), (2, 2)])
ls = LineString(array([(1, 1), (2, 2)]))
ls = LineString(Point(1, 1), Point(2, 2))
"""
self._ptr = GEOSPointer(0) # Initially NULL
# If only one argument was provided, then set the coords array appropriately
if len(args) == 1: coords = args[0]
else: coords = args
if isinstance(coords, (TupleType, ListType)):
# Getting the number of coords and the number of dimensions -- which
# must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
ncoords = len(coords)
first = True
for coord in coords:
if not isinstance(coord, (TupleType, ListType)):
if coords: ndim = len(coords[0])
else: raise TypeError, 'Cannot initialize on empty sequence.'
self._checkdim(ndim)
# Incrementing through each of the coordinates and verifying
for i in xrange(1, ncoords):
if not isinstance(coords[i], (TupleType, ListType, Point)):
raise TypeError, 'each coordinate should be a sequence (list or tuple)'
if first:
ndim = len(coord)
self._checkdim(ndim)
first = False
else:
if len(coord) != ndim: raise TypeError, 'Dimension mismatch.'
if len(coords[i]) != ndim: raise TypeError, 'Dimension mismatch.'
numpy_coords = False
elif HAS_NUMPY and isinstance(coords, ndarray):
shape = coords.shape
shape = coords.shape # Using numpy's shape.
if len(shape) != 2: raise TypeError, 'Too many dimensions.'
self._checkdim(shape[1])
ncoords = shape[0]
@ -141,40 +162,38 @@ class LineString(GEOSGeometry):
raise TypeError, 'Invalid initialization input for LineStrings.'
# Creating the coordinate sequence
cs = GEOSCoordSeq(GEOSPointer(create_cs(c_uint(ncoords), c_uint(ndim))))
cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim))))
# Setting each point in the coordinate sequence
for i in xrange(ncoords):
if numpy_coords: cs[i] = coords[i,:]
elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
else: cs[i] = coords[i]
# Getting the initialization function
if ring:
if kwargs.get('ring', False):
func = lgeos.GEOSGeom_createLinearRing
else:
func = lgeos.GEOSGeom_createLineString
# Calling the base geometry initialization with the returned pointer from the function.
super(LineString, self).__init__(func(cs._ptr()))
super(LineString, self).__init__(func(cs._ptr.coordseq()))
def __getitem__(self, index):
"Gets the point at the specified index."
self._cache_cs()
return self._cs[index]
def __setitem__(self, index, value):
"Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
self._cache_cs()
self._cs[index] = value
def __iter__(self):
"Allows iteration over this LineString."
for i in xrange(self.__len__()):
yield self.__getitem__(index)
yield self.__getitem__(i)
def __len__(self):
"Returns the number of points in this LineString."
self._cache_cs()
return len(self._cs)
def _checkdim(self, dim):
@ -184,55 +203,103 @@ class LineString(GEOSGeometry):
@property
def tuple(self):
"Returns a tuple version of the geometry from the coordinate sequence."
self._cache_cs()
return self._cs.tuple
def _listarr(self, func):
"""Internal routine that returns a sequence (list) corresponding with
the given function. Will return a numpy array if possible."""
lst = [func(i) for i in xrange(self.__len__())] # constructing the list, using the function
lst = [func(i) for i in xrange(len(self))] # constructing the list, using the function
if HAS_NUMPY: return array(lst) # ARRRR!
else: return lst
@property
def array(self):
"Returns a numpy array for the LineString."
self._cache_cs()
return self._listarr(self._cs.__getitem__)
@property
def x(self):
"Returns a list or numpy array of the X variable."
self._cache_cs()
return self._listarr(self._cs.getX)
@property
def y(self):
"Returns a list or numpy array of the Y variable."
self._cache_cs()
return self._listarr(self._cs.getY)
@property
def z(self):
"Returns a list or numpy array of the Z variable."
self._cache_cs()
if not self.hasz: return None
else: return self._listarr(self._cs.getZ)
# LinearRings are LineStrings used within Polygons.
class LinearRing(LineString):
def __init__(self, coords):
def __init__(self, *args):
"Overriding the initialization function to set the ring keyword."
super(LinearRing, self).__init__(coords, ring=True)
kwargs = {'ring' : True}
super(LinearRing, self).__init__(*args, **kwargs)
class Polygon(GEOSGeometry):
def __init__(self, *args):
"""Initializes on an exterior ring and a sequence of holes (both instances of LinearRings.
All LinearRing instances used for creation will become owned by this Polygon.
Examples, where shell, hole1, and hole2 are valid LinearRing geometries:
poly = Polygon(shell, hole1, hole2)
poly = Polygon(shell, (hole1, hole2))
"""
self._ptr = GEOSPointer(0) # Initially NULL
self._rings = {}
if not args:
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
# Getting the ext_ring and init_holes parameters from the argument list
ext_ring = args[0]
init_holes = args[1:]
if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)):
init_holes = init_holes[0]
# Ensuring the exterior ring parameter is a LinearRing object
if not isinstance(ext_ring, LinearRing):
raise TypeError, 'First argument for Polygon initialization must be a LinearRing.'
# Making sure all of the holes are LinearRing objects
if False in [isinstance(hole, LinearRing) for hole in init_holes]:
raise TypeError, 'Holes parameter must be a sequence of LinearRings.'
# Getting the holes
nholes = len(init_holes)
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)
# Getting the shell pointer address,
shell = init_from_geom(ext_ring)
# Calling with the GEOS createPolygon factory.
super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)))
def __del__(self):
"Override the GEOSGeometry delete routine to safely take care of any spawned rings."
# Nullifying the pointers to internal rings, preventing any attempted future access
for k in self._rings: self._rings[k].nullify()
super(Polygon, self).__del__() # Calling the parent __del__() method.
"Overloaded deletion method for Polygons."
#print '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:
# 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.
for k in self._rings:
if self._rings[k].valid:
lgeos.GEOSGeom_destroy(self._rings[k].address)
self._rings[k].nullify()
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."""
@ -247,13 +314,22 @@ class Polygon(GEOSGeometry):
def __iter__(self):
"Iterates over each ring in the polygon."
for i in xrange(self.__len__()):
for i in xrange(len(self)):
yield self.__getitem__(i)
def __len__(self):
"Returns the number of rings in this Polygon."
return self.num_interior_rings + 1
def _populate(self):
"Populates the internal rings dictionary."
# Getting the exterior ring first for the 0th index.
self._rings = {0 : GEOSPointer(lgeos.GEOSGetExteriorRing(self._ptr()))}
# Getting the interior rings.
for i in xrange(self.num_interior_rings):
self._rings[i+1] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i)))
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."""
@ -262,13 +338,9 @@ class Polygon(GEOSGeometry):
if ring_i < 0 or ring_i >= self.num_interior_rings:
raise IndexError, 'ring index out of range'
# Placing the ring in internal rings dictionary.
idx = ring_i+1 # the index for the polygon is +1 because of the exterior ring
if not idx in self._rings:
self._rings[idx] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(ring_i)))
# Returning the ring at the given index.
return GEOSGeometry(self._rings[idx], child=True)
# Returning the ring from the internal ring dictionary (have to
# add one to the index)
return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr)
#### Polygon Properties ####
@property
@ -282,17 +354,18 @@ class Polygon(GEOSGeometry):
if n == -1: raise GEOSException, 'Error getting the number of interior rings.'
else: return n
@property
def exterior_ring(self):
def get_ext_ring(self):
"Gets the exterior ring of the Polygon."
# Returns exterior ring
self._rings[0] = GEOSPointer(lgeos.GEOSGetExteriorRing((self._ptr())))
return GEOSGeometry(self._rings[0], child=True)
return GEOSGeometry(self._rings[0], parent=self._ptr)
@property
def shell(self):
"Gets the shell (exterior ring) of the Polygon."
return self.exterior_ring
def set_ext_ring(self):
"Sets the exterior ring of the Polygon."
# Sets the exterior ring
raise NotImplementedError
# properties for the exterior ring/shell
exterior_ring = property(get_ext_ring)
shell = property(get_ext_ring)
@property
def tuple(self):

View File

@ -5,9 +5,7 @@
"""
from django.contrib.gis.geos.error import GEOSException
from ctypes import \
c_char_p, c_int, c_size_t, c_ubyte, pointer, addressof, \
CDLL, CFUNCTYPE, POINTER, Structure
from ctypes import c_char_p, c_int, pointer, CDLL, CFUNCTYPE, POINTER, Structure
import os, sys
# NumPy supported?
@ -64,29 +62,55 @@ 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. ####
# 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 to accessing the values returned by
GEOS geometry creation routines."""
"""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,
which allows parent geometries to be 'nullified' if their children's memory is used
in construction of another geometry. Related coordinate sequence pointers are kept
in this object for the same reason."""
### Python 'magic' routines ###
def __init__(self, ptr):
"Given a ctypes pointer(c_int)"
if isinstance(ptr, int):
self._ptr = pointer(c_int(ptr))
def __init__(self, address, coordseq=0):
"Initializes on an address (an integer)."
if isinstance(address, int):
self._geom = pointer(c_int(address))
self._coordseq = pointer(c_int(coordseq))
else:
raise TypeError, 'GEOSPointer object must initialize with an integer.'
def __call__(self):
"""If the pointer is NULL, then an exception will be raised, otherwise the
address value (a GEOSGeom_ptr) will be returned."""
address value (an integer) will be returned."""
if self.valid: return self.address
else: raise GEOSException, 'GEOS pointer no longer valid (was the parent geometry deleted?)'
else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
def __bool__(self):
"Returns True when the GEOSPointer is valid."
return self.valid
def __str__(self):
return str(self.address)
### GEOSPointer Properties ###
@property
def address(self):
"Returns the address of the GEOSPointer (represented as an integer)."
return self._ptr.contents.value
return self._geom.contents.value
@property
def valid(self):
@ -94,13 +118,55 @@ class GEOSPointer(object):
if bool(self.address): return True
else: return False
### Coordinate Sequence properties ###
def coordseq(self):
"If the coordinate sequence pointer is NULL (0), an exception will be raised."
if self.coordseq_valid: return self.coordseq_address
else: raise GEOSException, 'GEOS coordinate sequence pointer invalid (was this geometry or the parent geometry deleted or modified?)'
@property
def coordseq_address(self):
"Returns the address of the related coordinate sequence."
return self._coordseq.contents.value
@property
def coordseq_valid(self):
"Returns True if the coordinate sequence address is valid, False otherwise."
if bool(self.coordseq_address): return True
else: return False
### GEOSPointer Methods ###
def set(self, address):
def set(self, address, coordseq=False):
"Sets this pointer with the new address (represented as an integer)"
if not isinstance(address, int):
raise TypeError, 'GEOSPointer must be set with an address (an integer).'
self._ptr.contents = c_int(address)
if coordseq:
self._coordseq.contents = c_int(address)
else:
self._geom.contents = c_int(address)
def nullify(self):
"Nullify this geometry pointer (set the address to 0)."
"""Nullify this geometry pointer (set the address to 0). This does not delete
any memory, rather, it sets the GEOS pointer to a NULL address, to prevent
access to addressses of deleted objects."""
# 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

@ -109,17 +109,26 @@ multilinestrings = (TestGeom('MULTILINESTRING ((0 0, 0 100), (100 0, 100 100))',
topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0, -5.0 0.0))'),
TestGeom('POLYGON ((0.0 -5.0, 0.0 5.0, 10.0 5.0, 10.0 -5.0, 0.0 -5.0))')
),
(TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'),
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))'),
),
)
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))')
)
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))'),
)
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))'),
)
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))'),
)
relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),
TestGeom('MULTIPOINT(80 170, 140 120, 200 80, 80 70)'),

View File

@ -1,9 +1,12 @@
import unittest
from django.contrib.gis.geos import GEOSGeometry, GEOSException, Point, LineString, LinearRing, HAS_NUMPY
from django.contrib.gis.geos import \
GEOSException, GEOSGeometryIndexError, \
GEOSGeometry, Point, LineString, LinearRing, Polygon, \
MultiPoint, MultiLineString, MultiPolygon, GeometryCollection, \
fromstr, HAS_NUMPY
from geometries import *
if HAS_NUMPY:
from numpy import array
if HAS_NUMPY: from numpy import array
class GEOSTest(unittest.TestCase):
@ -52,10 +55,12 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(p.z, pnt.z)
self.assertEqual(p.z, pnt.tuple[2], 9)
tup_args = (p.x, p.y, p.z)
set_tup = (2.71, 3.14, 5.23)
else:
self.assertEqual(False, pnt.hasz)
self.assertEqual(None, pnt.z)
tup_args = (p.x, p.y)
set_tup = (2.71, 3.14)
# Centroid operation on point should be point itself
self.assertEqual(p.centroid, pnt.centroid.tuple)
@ -71,6 +76,11 @@ class GEOSTest(unittest.TestCase):
pnt.x = 2.71
self.assertEqual(3.14, pnt.y)
self.assertEqual(2.71, pnt.x)
# Setting via the tuple property
pnt.tuple = set_tup
self.assertEqual(set_tup, pnt.tuple)
prev = pnt # setting the previous geometry
def test02b_multipoints(self):
@ -83,6 +93,7 @@ class GEOSTest(unittest.TestCase):
self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
self.assertRaises(GEOSGeometryIndexError, mpnt.__getitem__, len(mpnt))
self.assertEqual(mp.centroid, mpnt.centroid.tuple)
self.assertEqual(mp.points, tuple(m.tuple for m in mpnt))
for p in mpnt:
@ -107,17 +118,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
self.assertEqual(False, ls == prev)
self.assertRaises(GEOSGeometryIndexError, ls.__getitem__, len(ls))
prev = ls
# Creating a LineString from a tuple, list, and numpy array
ls2 = LineString(ls.tuple)
self.assertEqual(ls, ls2)
ls3 = LineString([list(tup) for tup in ls.tuple])
self.assertEqual(ls, ls3)
if HAS_NUMPY:
ls4 = LineString(array(ls.tuple))
self.assertEqual(ls, ls4)
self.assertEqual(ls, LineString(ls.tuple)) # tuple
self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
if HAS_NUMPY: self.assertEqual(ls, LineString(array(ls.tuple))) # as numpy array
def test03b_multilinestring(self):
"Testing MultiLineString objects."
@ -139,7 +148,11 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(ls.geom_typeid, 1)
self.assertEqual(ls.empty, False)
def test04a_linearring(self):
self.assertRaises(GEOSGeometryIndexError, ml.__getitem__, len(ml))
self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
def test04_linearring(self):
"Testing LinearRing objects."
for rr in linearrings:
lr = GEOSGeometry(rr.wkt)
@ -150,13 +163,10 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(False, lr.empty)
# Creating a LinearRing from a tuple, list, and numpy array
lr2 = LinearRing(lr.tuple)
self.assertEqual(lr, lr2)
lr3 = LinearRing([list(tup) for tup in lr.tuple])
self.assertEqual(lr, lr3)
if HAS_NUMPY:
lr4 = LineString(array(lr.tuple))
self.assertEqual(lr, lr4)
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)))
def test05a_polygons(self):
"Testing Polygon objects."
@ -180,6 +190,7 @@ class GEOSTest(unittest.TestCase):
# Testing the geometry equivalence
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
self.assertEqual(True, poly != prev)
# Testing the exterior ring
ring = poly.exterior_ring
@ -194,6 +205,15 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(ring.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2)
# Testing polygon construction.
self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
self.assertRaises(TypeError, Polygon, 'foo')
rings = tuple(r.clone() for r in poly)
self.assertEqual(poly, Polygon(rings[0], rings[1:]))
self.assertEqual(poly.wkt, Polygon(*tuple(r.clone() for r in poly)).wkt)
self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
# Setting the second point of the first ring (which should set the
# first point of the polygon).
prev = poly.clone() # Using clone() to get a copy of the current polygon
@ -221,10 +241,13 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(mp.num_geom, mpoly.num_geom)
self.assertEqual(mp.n_p, mpoly.num_coords)
self.assertEqual(mp.num_geom, len(mpoly))
self.assertRaises(GEOSGeometryIndexError, mpoly.__getitem__, len(mpoly))
for p in mpoly:
self.assertEqual(p.geom_type, 'Polygon')
self.assertEqual(p.geom_typeid, 3)
self.assertEqual(p.valid, True)
self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
def test06_memory_hijinks(self):
@ -254,7 +277,7 @@ class GEOSTest(unittest.TestCase):
self.assertRaises(GEOSException, str, ring2)
#### Memory issues with geometries from Geometry Collections
mp = GEOSGeometry('MULTIPOINT(85 715, 235 1400, 4620 1711)')
mp = fromstr('MULTIPOINT(85 715, 235 1400, 4620 1711)')
# Getting the points
pts = [p for p in mp]
@ -278,7 +301,82 @@ class GEOSTest(unittest.TestCase):
# after it has been deleted.
del mp
for p in pts:
self.assertRaises(GEOSException, str, p)
self.assertRaises(GEOSException, str, p) # tests p's geometry pointer
self.assertRaises(GEOSException, p.get_tuple) # tests p's coordseq pointer
# Now doing this with a GeometryCollection
polywkt = polygons[3].wkt # a 'real life' polygon.
lrwkt = linearrings[0].wkt # a 'real life' linear ring
poly = fromstr(polywkt)
linring = fromstr(lrwkt)
# Pulling out the shell and cloning our initial geometries for later comparison.
shell = poly.shell
polyc = poly.clone()
linringc = linring.clone()
gc = GeometryCollection(poly, linring, Point(5, 23))
# Should no longer be able to access these variables
self.assertRaises(GEOSException, str, poly)
self.assertRaises(GEOSException, str, shell)
self.assertRaises(GEOSException, str, linring)
r1 = gc[1] # pulling out the ring
pnt = gc[2] # pulling the point from the geometry collection
# Now lets create a MultiPolygon from the geometry collection components
mpoly = MultiPolygon(gc[0], Polygon(gc[1]))
self.assertEqual(polyc.wkt, mpoly[0].wkt)
# Should no longer be able to access the geometry collection directly
self.assertRaises(GEOSException, len, gc)
# BUT, should still be able to access the Point we obtained earlier, but
# not the linear ring (since it is now part of the MultiPolygon.
self.assertEqual(5, pnt.x)
self.assertEqual(23, pnt.y)
# __len__ is called on the coordinate sequence pointer -- make sure its nullified as well
self.assertRaises(GEOSException, len, r1)
self.assertRaises(GEOSException, str, r1)
# Can't access point after deletion of parent geometry.
del gc
self.assertRaises(GEOSException, str, pnt)
# Cleaning up.
del polyc
del mpoly
#### Memory issues with creating geometries from coordinate sequences within other geometries
# Creating the initial polygon from the following tuples, and then pulling out
# the individual rings.
ext_tup = ((0, 0), (0, 7), (7, 7), (7, 0), (0, 0))
itup1 = ((1, 1), (1, 2), (2, 2), (2, 1), (1, 1))
itup2 = ((4, 4), (4, 5), (5, 5), (5, 4), (4, 4))
poly1 = Polygon(LinearRing(ext_tup), LinearRing(itup1), LinearRing(itup2))
shell = poly1.shell
hole1 = poly1[1]
hole2 = poly1[2]
# Creating a Polygon from the shell and one of the holes
poly2 = Polygon(shell, hole1)
# We should no longer be able to access the original Polygon, its
# shell or its first internal ring.
self.assertRaises(GEOSException, str, poly1)
self.assertRaises(GEOSException, str, shell)
self.assertRaises(GEOSException, str, hole1)
# BUT, the second hole is still accessible.
self.assertEqual(itup2, hole2.tuple)
# Deleting the first polygon, and ensuring that
# the second hole is now gone for good.
del poly1
self.assertRaises(GEOSException, str, hole2)
def test08_coord_seq(self):
"Testing Coordinate Sequence objects."
@ -309,7 +407,6 @@ class GEOSTest(unittest.TestCase):
def test09_relate_pattern(self):
"Testing relate() and relate_pattern()."
g = GEOSGeometry('POINT (0 0)')
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
@ -333,6 +430,7 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(True, a.intersects(b))
i2 = a.intersection(b)
self.assertEqual(i1, i2)
self.assertEqual(i1, a & b)
def test11_union(self):
"Testing union()."
@ -343,6 +441,7 @@ 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
def test12_difference(self):
"Testing difference()."
@ -353,8 +452,20 @@ 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
def test13_buffer(self):
def test13_symdifference(self):
"Testing sym_difference()."
for i in xrange(len(topology_geoms)):
g_tup = topology_geoms[i]
a = GEOSGeometry(g_tup[0].wkt)
b = GEOSGeometry(g_tup[1].wkt)
d1 = GEOSGeometry(sdiff_geoms[i].wkt)
d2 = a.sym_difference(b)
self.assertEqual(d1, d2)
self.assertEqual(d1, a ^ b) # Symmetric difference ('^') operator
def test14_buffer(self):
"Testing buffer()."
for i in xrange(len(buffer_geoms)):
g_tup = buffer_geoms[i]

View File

@ -228,7 +228,7 @@ class LayerMapping:
"A class that maps OGR Layers to Django Models."
def __init__(self, model, data, mapping, layer=0, source_srs=None):
"Takes the Django model, the mapping (dictionary), and the SHP file."
"Takes the Django model, the data source, and the mapping (dictionary)"
# Getting the field names and types from the model
fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)