Merged in patch from Aryeh Leib Taurog for #9877, adapting as necessary.
1.1 --- a/django/contrib/gis/geos/__init__.py Thu Jan 29 21:08:19 2009 -0600
1.2 +++ b/django/contrib/gis/geos/__init__.py Sat Jan 31 15:18:50 2009 -0600
1.3 @@ -1,40 +1,10 @@
1.4 -"""
1.5 - The goal of this module is to be a ctypes wrapper around the GEOS library
1.6 - that will work on both *NIX and Windows systems. Specifically, this uses
1.7 - the GEOS C api.
1.8 -
1.9 - I have several motivations for doing this:
1.10 - (1) The GEOS SWIG wrapper is no longer maintained, and requires the
1.11 - installation of SWIG.
1.12 - (2) The PCL implementation is over 2K+ lines of C and would make
1.13 - PCL a requisite package for the GeoDjango application stack.
1.14 - (3) Windows and Mac compatibility becomes substantially easier, and does not
1.15 - require the additional compilation of PCL or GEOS and SWIG -- all that
1.16 - is needed is a Win32 or Mac compiled GEOS C library (dll or dylib)
1.17 - in a location that Python can read (e.g. 'C:\Python25').
1.18 -
1.19 - In summary, I wanted to wrap GEOS in a more maintainable and portable way using
1.20 - only Python and the excellent ctypes library (now standard in Python 2.5).
1.21 -
1.22 - In the spirit of loose coupling, this library does not require Django or
1.23 - GeoDjango. Only the GEOS C library and ctypes are needed for the platform
1.24 - of your choice.
1.25 -
1.26 - For more information about GEOS:
1.27 - http://geos.refractions.net
1.28 -
1.29 - For more info about PCL and the discontinuation of the Python GEOS
1.30 - library see Sean Gillies' writeup (and subsequent update) at:
1.31 - http://zcologia.com/news/150/geometries-for-python/
1.32 - http://zcologia.com/news/429/geometries-for-python-update/
1.33 -"""
1.34 from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex
1.35 from django.contrib.gis.geos.point import Point
1.36 from django.contrib.gis.geos.linestring import LineString, LinearRing
1.37 from django.contrib.gis.geos.polygon import Polygon
1.38 from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
1.39 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
1.40 -from django.contrib.gis.geos.libgeos import geos_version, geos_version_info
1.41 +from django.contrib.gis.geos.libgeos import geos_version, geos_version_info, GEOS_PREPARE
1.42
1.43 def fromfile(file_name):
1.44 """
2.1 --- a/django/contrib/gis/geos/base.py Thu Jan 29 21:08:19 2009 -0600
2.2 +++ b/django/contrib/gis/geos/base.py Sat Jan 31 15:18:50 2009 -0600
2.3 @@ -1,6 +1,6 @@
2.4 from ctypes import c_void_p
2.5 from types import NoneType
2.6 -from django.contrib.gis.geos.error import GEOSException
2.7 +from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
2.8
2.9 # Trying to import GDAL libraries, if available. Have to place in
2.10 # try/except since this package may be used outside GeoDjango.
2.11 @@ -52,3 +52,167 @@
2.12 # this raises an exception when the pointer is NULL, thus preventing
2.13 # the C library from attempting to access an invalid memory location.
2.14 ptr = property(_get_ptr, _set_ptr)
2.15 +
2.16 +class ListMixin(object):
2.17 +
2.18 + _canSetSingle = False
2.19 +
2.20 + def _setSingle_rebuild(self, index, value):
2.21 + self._checkindex(index)
2.22 + self._setSlice(slice(index, index + 1, 1), [value])
2.23 + _setSingle = _setSingle_rebuild
2.24 +
2.25 + def _checkindex(self, index):
2.26 + if index < 0 or index >= len(self):
2.27 + raise GEOSIndexError('invalid index: %s' % str(index))
2.28 +
2.29 + def _checkAllowedTypes(self, items):
2.30 + # Ensuring that only the permitted geometries are allowed in this collection
2.31 + if hasattr(self, '_allowed'):
2.32 + if False in [isinstance(geom, self._allowed) for geom in items]:
2.33 + raise TypeError('Invalid Geometry type encountered in the arguments.')
2.34 +
2.35 + def __getitem__(self, index):
2.36 + "Gets the coordinates of the point(s) at the specified index/slice."
2.37 + if isinstance(index, slice):
2.38 + return [self._getItemExternal(i) for i in xrange(*index.indices(len(self)))]
2.39 + else:
2.40 + if index < 0:
2.41 + index += len(self)
2.42 + return self._getItemExternal(index)
2.43 +
2.44 + def __delitem__(self, index):
2.45 + "Delete the point(s) at the specified index/slice."
2.46 + if not isinstance(index, (int, long, slice)):
2.47 + raise TypeError("%s is not a legal index" % index)
2.48 +
2.49 + # calculate new length and dimensions
2.50 + origLen = len(self)
2.51 + if isinstance(index, (int, long)):
2.52 + if index < 0: index += origLen
2.53 + if not 0 <= index < origLen:
2.54 + raise GEOSIndexError('invalid index: %d' % index)
2.55 + indexRange = [index]
2.56 + else:
2.57 + indexRange = range(*index.indices(origLen))
2.58 +
2.59 + newLen = origLen - len(indexRange)
2.60 + newItems = ( self._getItemInternal(i)
2.61 + for i in xrange(origLen)
2.62 + if i not in indexRange )
2.63 +
2.64 + self._setCollection(newLen, newItems)
2.65 +
2.66 + def __setitem__(self, index, geom):
2.67 + "Sets the Geometry at the specified index."
2.68 + if isinstance(index, slice):
2.69 + self._setSlice(index, geom)
2.70 + else:
2.71 + if index < 0: index += len(self)
2.72 + self._setSingle(index, geom)
2.73 +
2.74 + def _setSlice(self, index, values):
2.75 + "Assign values to a slice of the object"
2.76 + try:
2.77 + iter(values)
2.78 + except TypeError:
2.79 + raise TypeError('can only assign an iterable to a slice')
2.80 +
2.81 + self._checkAllowedTypes(values)
2.82 +
2.83 + origLen = len(self)
2.84 + valueList = list(values)
2.85 + start, stop, step = index.indices(origLen)
2.86 + stop = max(0, stop) # stop will be -1 if out-of-bounds
2.87 + # negative index is given
2.88 +
2.89 + # CAREFUL: index.step and step are not the same!
2.90 + # step will never be None
2.91 + #
2.92 + if index.step is None:
2.93 + self._assignSimpleSlice(start, stop, valueList)
2.94 + else:
2.95 + self._assignExtendedSlice(start, stop, step, valueList)
2.96 +
2.97 + def _assignExtendedSlice(self, start, stop, step, valueList):
2.98 + 'Assign an extended slice by rebuilding entire list'
2.99 + indexList = range(start, stop, step)
2.100 + # extended slice, only allow assigning slice of same size
2.101 + if len(valueList) != len(indexList):
2.102 + raise ValueError('attempt to assign sequence of size %d '
2.103 + 'to extended slice of size %d'
2.104 + % (len(valueList), len(indexList)))
2.105 +
2.106 + # we're not changing the length of the sequence
2.107 + newLen = len(self)
2.108 + newVals = dict(zip(indexList, valueList))
2.109 + def newItems():
2.110 + for i in xrange(newLen):
2.111 + if i in newVals:
2.112 + yield newVals[i]
2.113 + else:
2.114 + yield self._getItemInternal(i)
2.115 +
2.116 + self._setCollection(newLen, newItems())
2.117 +
2.118 + def _assignExtendedSlice_no_rebuild(self, start, stop, step, valueList):
2.119 + 'Assign an extended slice by re-assigning individual items'
2.120 + indexList = range(start, stop, step)
2.121 + # extended slice, only allow assigning slice of same size
2.122 + if len(valueList) != len(indexList):
2.123 + raise ValueError('attempt to assign sequence of size %d '
2.124 + 'to extended slice of size %d'
2.125 + % (len(valueList), len(indexList)))
2.126 +
2.127 + for i, val in zip(indexList, valueList):
2.128 + self._setSingle(i, val)
2.129 +
2.130 + def _assignSimpleSlice(self, start, stop, valueList):
2.131 + 'Assign a simple slice; Can assign slice of any length'
2.132 + origLen = len(self)
2.133 + newLen = origLen - stop + start + len(valueList)
2.134 + def newItems():
2.135 + for i in xrange(origLen + 1):
2.136 + if i == start:
2.137 + for val in valueList:
2.138 + yield val
2.139 +
2.140 + if i < origLen:
2.141 + if i < start or i >= stop:
2.142 + yield self._getItemInternal(i)
2.143 +
2.144 + self._setCollection(newLen, newItems())
2.145 +
2.146 + def append(self, val):
2.147 + "Standard list append method"
2.148 + self[len(self):] = [val]
2.149 +
2.150 + def extend(self, vals):
2.151 + "Standard list extend method"
2.152 + self[len(self):] = vals
2.153 +
2.154 + def insert(self, index, val):
2.155 + "Standard list insert method"
2.156 + if not isinstance(index, (int, long)):
2.157 + raise TypeError("%s is not a legal index" % index)
2.158 + self[index:index] = [val]
2.159 +
2.160 + def pop(self, index=-1):
2.161 + "Standard list pop method"
2.162 + result = self[index]
2.163 + del self[index]
2.164 + return result
2.165 +
2.166 + def index(self, val):
2.167 + "Standard list index method"
2.168 + for i in xrange(0, len(self)):
2.169 + if self[i] == val: return i
2.170 + raise ValueError('%s not in geometry' % str(val))
2.171 +
2.172 + def remove(self, val):
2.173 + "Standard list remove method"
2.174 + del self[self.index(val)]
2.175 +
2.176 + def count(self):
2.177 + "Standard list count method"
2.178 + return len(self)
3.1 --- a/django/contrib/gis/geos/collections.py Thu Jan 29 21:08:19 2009 -0600
3.2 +++ b/django/contrib/gis/geos/collections.py Sat Jan 31 15:18:50 2009 -0600
3.3 @@ -32,39 +32,40 @@
3.4 init_geoms = args
3.5
3.6 # Ensuring that only the permitted geometries are allowed in this collection
3.7 - if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
3.8 - raise TypeError('Invalid Geometry type encountered in the arguments.')
3.9 + self._checkAllowedTypes(init_geoms)
3.10
3.11 # Creating the geometry pointer array.
3.12 - ngeoms = len(init_geoms)
3.13 - geoms = get_pointer_arr(ngeoms)
3.14 - for i in xrange(ngeoms): geoms[i] = capi.geom_clone(init_geoms[i].ptr)
3.15 - super(GeometryCollection, self).__init__(capi.create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs)
3.16 + collection = self._createCollection(len(init_geoms), iter(init_geoms))
3.17 + super(GeometryCollection, self).__init__(collection, **kwargs)
3.18
3.19 - def __getitem__(self, index):
3.20 + @classmethod
3.21 + def _createCollection(cls, length, items):
3.22 + # Creating the geometry pointer array.
3.23 + geoms = get_pointer_arr(length)
3.24 + for i, g in enumerate(items):
3.25 + # this is a little sloppy, but makes life easier
3.26 + # allow GEOSGeometry types (python wrappers) or pointer types
3.27 + if hasattr(g, 'ptr'):
3.28 + geoms[i] = capi.geom_clone(g.ptr)
3.29 + else:
3.30 + geoms[i] = capi.geom_clone(g)
3.31 +
3.32 + return capi.create_collection(c_int(cls._typeid), byref(geoms), c_uint(length))
3.33 +
3.34 + def _getItemInternal(self, index):
3.35 + return capi.get_geomn(self.ptr, index)
3.36 +
3.37 + def _getItemExternal(self, index):
3.38 "Returns the Geometry from this Collection at the given index (0-based)."
3.39 # Checking the index and returning the corresponding GEOS geometry.
3.40 self._checkindex(index)
3.41 - return GEOSGeometry(capi.geom_clone(capi.get_geomn(self.ptr, index)), srid=self.srid)
3.42 + return GEOSGeometry(capi.geom_clone(self._getItemInternal(index)), srid=self.srid)
3.43
3.44 - def __setitem__(self, index, geom):
3.45 - "Sets the Geometry at the specified index."
3.46 - self._checkindex(index)
3.47 - if not isinstance(geom, self._allowed):
3.48 - raise TypeError('Incompatible Geometry for collection.')
3.49 -
3.50 - ngeoms = len(self)
3.51 - geoms = get_pointer_arr(ngeoms)
3.52 - for i in xrange(ngeoms):
3.53 - if i == index:
3.54 - geoms[i] = capi.geom_clone(geom.ptr)
3.55 - else:
3.56 - geoms[i] = capi.geom_clone(capi.get_geomn(self.ptr, i))
3.57 -
3.58 - # Creating a new collection, and destroying the contents of the previous poiner.
3.59 + def _setCollection(self, length, items):
3.60 + "Create a new collection, and destroy the contents of the previous pointer."
3.61 prev_ptr = self.ptr
3.62 srid = self.srid
3.63 - self.ptr = capi.create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms))
3.64 + self.ptr = self._createCollection(length, items)
3.65 if srid: self.srid = srid
3.66 capi.destroy_geom(prev_ptr)
3.67
3.68 @@ -77,11 +78,6 @@
3.69 "Returns the number of geometries in this Collection."
3.70 return self.num_geom
3.71
3.72 - def _checkindex(self, index):
3.73 - "Checks the given geometry index."
3.74 - if index < 0 or index >= self.num_geom:
3.75 - raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index))
3.76 -
3.77 @property
3.78 def kml(self):
3.79 "Returns the KML for this Geometry Collection."
4.1 --- a/django/contrib/gis/geos/geometry.py Thu Jan 29 21:08:19 2009 -0600
4.2 +++ b/django/contrib/gis/geos/geometry.py Sat Jan 31 15:18:50 2009 -0600
4.3 @@ -7,7 +7,7 @@
4.4 from ctypes import addressof, byref, c_double, c_size_t
4.5
4.6 # GEOS-related dependencies.
4.7 -from django.contrib.gis.geos.base import GEOSBase, gdal
4.8 +from django.contrib.gis.geos.base import GEOSBase, ListMixin, gdal
4.9 from django.contrib.gis.geos.coordseq import GEOSCoordSeq
4.10 from django.contrib.gis.geos.error import GEOSException
4.11 from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
4.12 @@ -24,7 +24,7 @@
4.13 hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
4.14 wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I)
4.15
4.16 -class GEOSGeometry(GEOSBase):
4.17 +class GEOSGeometry(GEOSBase, ListMixin):
4.18 "A class that, generally, encapsulates a GEOS geometry."
4.19
4.20 ptr_type = GEOM_PTR
5.1 --- a/django/contrib/gis/geos/linestring.py Thu Jan 29 21:08:19 2009 -0600
5.2 +++ b/django/contrib/gis/geos/linestring.py Sat Jan 31 15:18:50 2009 -0600
5.3 @@ -5,6 +5,9 @@
5.4 from django.contrib.gis.geos import prototypes as capi
5.5
5.6 class LineString(GEOSGeometry):
5.7 + _init_func = capi.create_linestring
5.8 + _canSetSingle = True
5.9 + _assignExtendedSlice = GEOSGeometry._assignExtendedSlice_no_rebuild
5.10
5.11 #### Python 'magic' routines ####
5.12 def __init__(self, *args, **kwargs):
5.13 @@ -55,25 +58,38 @@
5.14 elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
5.15 else: cs[i] = coords[i]
5.16
5.17 - # Getting the correct initialization function
5.18 - if kwargs.get('ring', False):
5.19 - func = capi.create_linearring
5.20 - else:
5.21 - func = capi.create_linestring
5.22 -
5.23 # If SRID was passed in with the keyword arguments
5.24 srid = kwargs.get('srid', None)
5.25
5.26 # Calling the base geometry initialization with the returned pointer
5.27 # from the function.
5.28 - super(LineString, self).__init__(func(cs.ptr), srid=srid)
5.29 + super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)
5.30
5.31 - def __getitem__(self, index):
5.32 - "Gets the point at the specified index."
5.33 + def _getItemExternal(self, index):
5.34 + self._checkindex(index)
5.35 return self._cs[index]
5.36 + _getItemInternal = _getItemExternal
5.37
5.38 - def __setitem__(self, index, value):
5.39 - "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
5.40 + def _setCollection(self, length, items):
5.41 + ndim = self._cs.dims #
5.42 + hasz = self._cs.hasz # I don't understand why these are different
5.43 +
5.44 + # create a new coordinate sequence and populate accordingly
5.45 + cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
5.46 + for i, c in enumerate(items):
5.47 + cs[i] = c
5.48 +
5.49 + ptr = self._init_func(cs.ptr)
5.50 + if ptr:
5.51 + capi.destroy_geom(self.ptr)
5.52 + self._ptr = ptr
5.53 + self._post_init(self.srid)
5.54 + else:
5.55 + # can this happen?
5.56 + raise GEOSException('Geometry resulting from slice deletion was invalid.')
5.57 +
5.58 + def _setSingle(self, index, value):
5.59 + self._checkindex(index)
5.60 self._cs[index] = value
5.61
5.62 def __iter__(self):
5.63 @@ -127,7 +143,4 @@
5.64
5.65 # LinearRings are LineStrings used within Polygons.
5.66 class LinearRing(LineString):
5.67 - def __init__(self, *args, **kwargs):
5.68 - "Overriding the initialization function to set the ring keyword."
5.69 - kwargs['ring'] = True # Setting the ring keyword argument to True
5.70 - super(LinearRing, self).__init__(*args, **kwargs)
5.71 + _init_func = capi.create_linearring
6.1 --- a/django/contrib/gis/geos/polygon.py Thu Jan 29 21:08:19 2009 -0600
6.2 +++ b/django/contrib/gis/geos/polygon.py Sat Jan 31 15:18:50 2009 -0600
6.3 @@ -97,7 +97,7 @@
6.4 # geometry, and destroying the old geometry.
6.5 prev_ptr = self.ptr
6.6 srid = self.srid
6.7 - self._ptr = capi.create_polygon(shell, holes_param, c_uint(nholes))
6.8 + self.ptr = capi.create_polygon(shell, holes_param, c_uint(nholes))
6.9 if srid: self.srid = srid
6.10 capi.destroy_geom(prev_ptr)
6.11
6.12 @@ -110,11 +110,6 @@
6.13 "Returns the number of rings in this Polygon."
6.14 return self.num_interior_rings + 1
6.15
6.16 - def _checkindex(self, index):
6.17 - "Internal routine for checking the given ring index."
6.18 - if index < 0 or index >= len(self):
6.19 - raise GEOSIndexError('invalid Polygon ring index: %s' % index)
6.20 -
6.21 def _construct_ring(self, param, msg=''):
6.22 "Helper routine for trying to construct a ring from the given parameter."
6.23 if isinstance(param, LinearRing): return param
7.1 --- a/django/contrib/gis/geos/tests/__init__.py Thu Jan 29 21:08:19 2009 -0600
7.2 +++ b/django/contrib/gis/geos/tests/__init__.py Sat Jan 31 15:18:50 2009 -0600
7.3 @@ -1,4 +1,18 @@
7.4 """
7.5 GEOS Testing module.
7.6 """
7.7 -from django.contrib.gis.geos.tests.test_geos import GEOSTest, suite, run
7.8 +from unittest import TestSuite, TextTestRunner
7.9 +
7.10 +import test_geos, test_mutability
7.11 +
7.12 +test_suites = [test_geos.suite(),
7.13 + test_mutability.suite(),
7.14 + ]
7.15 +
7.16 +def suite():
7.17 + s = TestSuite()
7.18 + map(s.addTest, test_suites)
7.19 + return s
7.20 +
7.21 +def run(verbosity=1):
7.22 + TextTestRunner(verbosity=verbosity).run(suite())
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/django/contrib/gis/geos/tests/pymutable_geometries.py Sat Jan 31 15:18:50 2009 -0600
8.3 @@ -0,0 +1,184 @@
8.4 +from django.contrib.gis.geos import *
8.5 +from random import random
8.6 +
8.7 +SEQ_LENGTH = 10
8.8 +SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
8.9 +SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
8.10 +SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
8.11 +
8.12 +def seqrange(): return xrange(*SEQ_RANGE)
8.13 +
8.14 +def random_coord(dim = 2, # coordinate dimensions
8.15 + rng = (-50,50), # coordinate range
8.16 + num_type = float,
8.17 + round_coords = True):
8.18 +
8.19 + if round_coords:
8.20 + num = lambda: num_type(round(random() * (rng[1]-rng[0]) + rng[0]))
8.21 + else:
8.22 + num = lambda: num_type(random() * (rng[1]-rng[0]) + rng[0])
8.23 +
8.24 + return tuple( num() for axis in xrange(dim) )
8.25 +
8.26 +def random_list(length = SEQ_LENGTH, ring = False, **kwargs):
8.27 + result = [ random_coord(**kwargs) for index in xrange(length) ]
8.28 + if ring:
8.29 + result[-1] = result[0]
8.30 +
8.31 + return result
8.32 +
8.33 +random_list.single = random_coord
8.34 +
8.35 +def random_coll(count = SEQ_LENGTH, **kwargs):
8.36 + return [ tuple(random_list(**kwargs)) for i in xrange(count) ]
8.37 +
8.38 +random_coll.single = random_list
8.39 +
8.40 +class PyMutTestGeom:
8.41 + "The Test Geometry class container."
8.42 + def __init__(self, geom_type, coords_fcn=random_list, subtype=tuple, **kwargs):
8.43 + self.geom_type = geom_type
8.44 + self.subtype = subtype
8.45 + self.coords_fcn = coords_fcn
8.46 + self.fcn_args = kwargs
8.47 + self.coords = self.coords_fcn(**kwargs)
8.48 + self.geom = self.make_geom()
8.49 +
8.50 + def newitem(self, **kwargs):
8.51 + a = self.coords_fcn.single(**kwargs)
8.52 + return self.subtype(a), tuple(a)
8.53 +
8.54 + @property
8.55 + def tuple_coords(self):
8.56 + return tuple(self.coords)
8.57 +
8.58 + def make_geom(self):
8.59 + return self.geom_type(map(self.subtype,self.coords))
8.60 +
8.61 +
8.62 +def slice_geometries(ring=True):
8.63 + testgeoms = [
8.64 + PyMutTestGeom(LineString),
8.65 + PyMutTestGeom(MultiPoint, subtype=Point),
8.66 + PyMutTestGeom(MultiLineString, coords_fcn=random_coll, subtype=LineString),
8.67 + ]
8.68 + if ring:
8.69 + testgeoms.append(PyMutTestGeom(LinearRing, ring=True))
8.70 +
8.71 + return testgeoms
8.72 +
8.73 +def getslice_functions():
8.74 + def gs_01(x): x[0:4],
8.75 + def gs_02(x): x[5:-1],
8.76 + def gs_03(x): x[6:2:-1],
8.77 + def gs_04(x): x[:],
8.78 + def gs_05(x): x[:3],
8.79 + def gs_06(x): x[::2],
8.80 + def gs_07(x): x[::-4],
8.81 + def gs_08(x): x[7:7],
8.82 + def gs_09(x): x[20:],
8.83 +
8.84 + # don't really care about ringy-ness here
8.85 + return mark_ring(vars(), 'gs_')
8.86 +
8.87 +def delslice_functions():
8.88 + def ds_01(x): del x[0:4]
8.89 + def ds_02(x): del x[5:-1]
8.90 + def ds_03(x): del x[6:2:-1]
8.91 + def ds_04(x): del x[:] # should this be allowed?
8.92 + def ds_05(x): del x[:3]
8.93 + def ds_06(x): del x[1:9:2]
8.94 + def ds_07(x): del x[::-4]
8.95 + def ds_08(x): del x[7:7]
8.96 + def ds_09(x): del x[-7:-2]
8.97 +
8.98 + return mark_ring(vars(), 'ds_')
8.99 +
8.100 +def setslice_extended_functions(g):
8.101 + a = g.coords_fcn(3, rng=(100,150))
8.102 + def maptype(x,a):
8.103 + if isinstance(x, list): return a
8.104 + else: return map(g.subtype, a)
8.105 +
8.106 + def sse_00(x): x[:3:1] = maptype(x, a)
8.107 + def sse_01(x): x[0:3:1] = maptype(x, a)
8.108 + def sse_02(x): x[2:5:1] = maptype(x, a)
8.109 + def sse_03(x): x[-3::1] = maptype(x, a)
8.110 + def sse_04(x): x[-4:-1:1] = maptype(x, a)
8.111 + def sse_05(x): x[8:5:-1] = maptype(x, a)
8.112 + def sse_06(x): x[-6:-9:-1] = maptype(x, a)
8.113 + def sse_07(x): x[:8:3] = maptype(x, a)
8.114 + def sse_08(x): x[1::3] = maptype(x, a)
8.115 + def sse_09(x): x[-2::-3] = maptype(x, a)
8.116 + def sse_10(x): x[7:1:-2] = maptype(x, a)
8.117 + def sse_11(x): x[2:8:2] = maptype(x, a)
8.118 +
8.119 + return mark_ring(vars(), 'sse_')
8.120 +
8.121 +def setslice_simple_functions(g):
8.122 + a = g.coords_fcn(3, rng=(100,150))
8.123 + def maptype(x,a):
8.124 + if isinstance(x, list): return a
8.125 + else: return map(g.subtype, a)
8.126 +
8.127 + def ss_00(x): x[:0] = maptype(x, a)
8.128 + def ss_01(x): x[:1] = maptype(x, a)
8.129 + def ss_02(x): x[:2] = maptype(x, a)
8.130 + def ss_03(x): x[:3] = maptype(x, a)
8.131 + def ss_04(x): x[-4:] = maptype(x, a)
8.132 + def ss_05(x): x[-3:] = maptype(x, a)
8.133 + def ss_06(x): x[-2:] = maptype(x, a)
8.134 + def ss_07(x): x[-1:] = maptype(x, a)
8.135 + def ss_08(x): x[5:] = maptype(x, a)
8.136 + def ss_09(x): x[:] = maptype(x, a)
8.137 + def ss_10(x): x[4:4] = maptype(x, a)
8.138 + def ss_11(x): x[4:5] = maptype(x, a)
8.139 + def ss_12(x): x[4:7] = maptype(x, a)
8.140 + def ss_13(x): x[4:8] = maptype(x, a)
8.141 + def ss_14(x): x[10:] = maptype(x, a)
8.142 + def ss_15(x): x[20:30] = maptype(x, a)
8.143 + def ss_16(x): x[-13:-8] = maptype(x, a)
8.144 + def ss_17(x): x[-13:-9] = maptype(x, a)
8.145 + def ss_18(x): x[-13:-10] = maptype(x, a)
8.146 + def ss_19(x): x[-13:-11] = maptype(x, a)
8.147 +
8.148 + return mark_ring(vars(), 'ss_')
8.149 +
8.150 +def test_geos_functions():
8.151 +
8.152 + return (
8.153 + lambda x: x.num_coords,
8.154 + lambda x: x.empty,
8.155 + lambda x: x.valid,
8.156 + lambda x: x.simple,
8.157 + lambda x: x.ring,
8.158 + lambda x: x.boundary,
8.159 + lambda x: x.convex_hull,
8.160 + lambda x: x.extend,
8.161 + lambda x: x.area,
8.162 + lambda x: x.length,
8.163 + )
8.164 +
8.165 +def mark_ring(locals, name_pat, length=SEQ_LENGTH):
8.166 + '''
8.167 + Accepts an array of functions which perform slice modifications
8.168 + and labels each function as to whether or not it preserves ring-ness
8.169 + '''
8.170 + func_array = [ val for name, val in locals.items()
8.171 + if hasattr(val, '__call__')
8.172 + and name.startswith(name_pat) ]
8.173 +
8.174 + for i in xrange(len(func_array)):
8.175 + a = range(length)
8.176 + a[-1] = a[0]
8.177 + func_array[i](a)
8.178 + ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
8.179 + func_array[i].ring = ring
8.180 +
8.181 + return func_array
8.182 +
8.183 +def getcoords(o):
8.184 + if hasattr(o, 'coords'):
8.185 + return o.coords
8.186 + else:
8.187 + return o
9.1 --- a/django/contrib/gis/geos/tests/test_geos.py Thu Jan 29 21:08:19 2009 -0600
9.2 +++ b/django/contrib/gis/geos/tests/test_geos.py Sat Jan 31 15:18:50 2009 -0600
9.3 @@ -793,6 +793,7 @@
9.4
9.5 def test26_prepared(self):
9.6 "Testing PreparedGeometry support."
9.7 + if GEOS_PREPARE: return
9.8 # Creating a simple multipolygon and getting a prepared version.
9.9 mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))')
9.10 prep = mpoly.prepared
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/django/contrib/gis/geos/tests/test_mutability.py Sat Jan 31 15:18:50 2009 -0600
10.3 @@ -0,0 +1,206 @@
10.4 +import unittest
10.5 +from pymutable_geometries import *
10.6 +from django.contrib.gis.geos.error import GEOSIndexError
10.7 +
10.8 +class GEOSPyMutableTest(unittest.TestCase):
10.9 + '''
10.10 + Tests Pythonic Mutability of Python GEOS geometry wrappers
10.11 + get/set/delitem on a slice, normal list methods
10.12 + '''
10.13 +
10.14 + def test01_getitem(self):
10.15 + 'Test getting a single item from a geometry'
10.16 + for g in slice_geometries():
10.17 + for i in seqrange():
10.18 + self.assertEqual(g.coords[i], getcoords(g.geom[i]))
10.19 +
10.20 + def test02_getslice(self):
10.21 + 'Test getting a slice from a geometry'
10.22 + for f in getslice_functions():
10.23 + for g in slice_geometries():
10.24 + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
10.25 + self.assertEqual(f(g.coords), getcoords(f(g.geom)), msg)
10.26 +
10.27 + def test03_getitem_indexException(self):
10.28 + 'Test get single item with out-of-bounds index'
10.29 + for g in slice_geometries():
10.30 + for i in SEQ_OUT_OF_BOUNDS:
10.31 + self.assertRaises(GEOSIndexError, lambda: g.geom[i])
10.32 +
10.33 + def test04_delitem_single(self):
10.34 + 'Test delete single item from a geometry'
10.35 + for i in seqrange():
10.36 + for g in slice_geometries():
10.37 + if g.geom.ring and i in SEQ_BOUNDS: continue
10.38 + del g.coords[i]
10.39 + del g.geom[i]
10.40 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.41 +
10.42 + def test05_delitem_slice(self):
10.43 + 'Test delete slice from a geometry'
10.44 + for f in delslice_functions():
10.45 + for g in slice_geometries():
10.46 + if g.geom.ring and not f.ring: continue
10.47 + f(g.coords)
10.48 + f(g.geom)
10.49 + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
10.50 + self.assertEqual(g.tuple_coords, g.geom.coords, msg)
10.51 +
10.52 + def test06_delitem_single_indexException(self):
10.53 + 'Test delete single item with out-of-bounds index'
10.54 + def func(x, i): del x[i]
10.55 + for g in slice_geometries():
10.56 + for i in SEQ_OUT_OF_BOUNDS:
10.57 + self.assertRaises(GEOSIndexError, func, g.geom, i)
10.58 +
10.59 + def test07_setitem_single(self):
10.60 + "Test set single item (make sure we didn't break this)"
10.61 + for i in seqrange():
10.62 + for g in slice_geometries():
10.63 + if g.geom.ring and i in SEQ_BOUNDS: continue
10.64 + ag, ac = g.newitem(rng=(400,410))
10.65 + g.coords[i] = ac
10.66 + g.geom[i] = ag
10.67 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.68 +
10.69 + def test08_setslice_simple(self):
10.70 + 'Test setting a simple slice of a geometry'
10.71 + for g in slice_geometries():
10.72 + for f in setslice_simple_functions(g):
10.73 + if g.geom.ring and not f.ring: continue
10.74 + f(g.coords)
10.75 + f(g.geom)
10.76 + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
10.77 + self.assertEqual(g.tuple_coords, g.geom.coords, msg)
10.78 +
10.79 + def test09_setslice_extended(self):
10.80 + 'Test setting an extended slice of a geometry'
10.81 + for g in slice_geometries():
10.82 + for f in setslice_extended_functions(g):
10.83 + if g.geom.ring and not f.ring: continue
10.84 + f(g.coords)
10.85 + f(g.geom)
10.86 + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
10.87 + self.assertEqual(g.tuple_coords, g.geom.coords, msg)
10.88 +
10.89 + def test10_setslice_extended_mismatched(self):
10.90 + 'Test setting extended slice with array of mismatched length'
10.91 + for g in slice_geometries():
10.92 + ag, ac = g.newitem(rng=(400,410))
10.93 + def func(): g.geom[2:8:2] = [ ag, ]
10.94 + self.assertRaises(ValueError, func)
10.95 +
10.96 + def test11_setitem_single_indexException(self):
10.97 + 'Test set single item with out-of-bounds index'
10.98 + for g in slice_geometries():
10.99 + ag, ac = g.newitem(rng=(400,410))
10.100 + def func(i): g.geom[i] = ag
10.101 + for i in SEQ_OUT_OF_BOUNDS:
10.102 + self.assertRaises(GEOSIndexError, func, i)
10.103 +
10.104 + def test12_append(self):
10.105 + 'Test list method append'
10.106 + for g in slice_geometries(ring=False):
10.107 + ag, ac = g.newitem(rng=(400,410))
10.108 + g.geom.append(ag)
10.109 + g.coords.append(ac)
10.110 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.111 +
10.112 + def test13_extend(self):
10.113 + 'Test list method extend'
10.114 + for g in slice_geometries():
10.115 + items = g.coords_fcn(5)
10.116 + if g.geom.ring: items[-1] = g.coords[0]
10.117 + g.geom.extend(map(g.subtype,items))
10.118 + g.coords.extend(items)
10.119 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.120 +
10.121 + def test14_insert(self):
10.122 + 'Test list method insert'
10.123 + for i in xrange(*SEQ_OUT_OF_BOUNDS):
10.124 + for g in slice_geometries():
10.125 + if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
10.126 + continue
10.127 + ag, ac = g.newitem(rng=(200,250))
10.128 + g.geom.insert(i, ag)
10.129 + g.coords.insert(i, ac)
10.130 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.131 +
10.132 + def test15_insert_typeError(self):
10.133 + 'Test list method insert raises error on invalid index'
10.134 + for g in slice_geometries():
10.135 + ag, ac = g.newitem(rng=(200,250))
10.136 + self.assertRaises(TypeError, g.geom.insert, 'hi', ag)
10.137 +
10.138 + def test16_pop(self):
10.139 + 'Test list method pop'
10.140 + for i in seqrange():
10.141 + for g in slice_geometries():
10.142 + if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
10.143 + continue
10.144 + self.assertEqual(g.coords.pop(i), getcoords(g.geom.pop(i)))
10.145 +
10.146 + def test16_index(self):
10.147 + 'Test list method index'
10.148 + for i in xrange(0, SEQ_LENGTH):
10.149 + for g in slice_geometries():
10.150 + if g.geom.ring and i in SEQ_BOUNDS: continue
10.151 + ag, ac = g.newitem(rng=(400,410))
10.152 + g.geom[i] = ag
10.153 + self.assertEqual(i, g.geom.index(ag))
10.154 +
10.155 + def test17_index_ValueError(self):
10.156 + 'Test list method raises ValueError if value not found'
10.157 + for g in slice_geometries():
10.158 + ag, ac = g.newitem(rng=(400,410))
10.159 + self.assertRaises(ValueError, g.geom.index, ag)
10.160 +
10.161 + def test18_remove(self):
10.162 + 'Test list method remove'
10.163 + for i in xrange(0, SEQ_LENGTH):
10.164 + for g in slice_geometries():
10.165 + if g.geom.ring and i in SEQ_BOUNDS: continue
10.166 + ag, ac = g.newitem(rng=(400,410))
10.167 + g.geom[i] = ag
10.168 + g.coords[i] = ac
10.169 + g.geom.remove(ag)
10.170 + g.coords.remove(ac)
10.171 + self.assertEqual(g.tuple_coords, g.geom.coords)
10.172 +
10.173 + def test19_count(self):
10.174 + 'Test list method count'
10.175 + for g in slice_geometries():
10.176 + self.assertEqual(SEQ_LENGTH, g.geom.count())
10.177 +
10.178 + def test20_setslice_geos_fcns(self):
10.179 + 'Test geos properties after setting a simple slice of a geometry'
10.180 + for g in slice_geometries(ring=False):
10.181 + for f in setslice_simple_functions(g):
10.182 + f(g.coords)
10.183 + f(g.geom)
10.184 + if not len(g.coords): continue
10.185 + for tf in test_geos_functions():
10.186 + cg = g.make_geom()
10.187 + self.assertEqual(tf(cg) , tf(g.geom))
10.188 +
10.189 + def test21_delslice_geos_fcns(self):
10.190 + 'Test geos properties after deleting a slice of a geometry'
10.191 + for f in delslice_functions():
10.192 + for g in slice_geometries(ring=False):
10.193 + f(g.coords)
10.194 + f(g.geom)
10.195 + if not len(g.coords): continue
10.196 + for tf in test_geos_functions():
10.197 + cg = g.make_geom()
10.198 + self.assertEqual(tf(cg) , tf(g.geom))
10.199 +
10.200 +def suite():
10.201 + s = unittest.TestSuite()
10.202 + s.addTest(unittest.makeSuite(GEOSPyMutableTest))
10.203 + return s
10.204 +
10.205 +def run(verbosity=2):
10.206 + unittest.TextTestRunner(verbosity=verbosity).run(suite())
10.207 +
10.208 +if __name__ == '__main__':
10.209 + run()