Index: geos/base.py =================================================================== --- geos/base.py (revision 2) +++ geos/base.py (revision 13) @@ -594,6 +594,180 @@ "Clones this Geometry." return GEOSGeometry(geom_clone(self.ptr), srid=self.srid) +class ListMixin(object): + + def __init__(self, *args, **kwargs): + if not hasattr(self, '_getItemInternal'): + self._getItemInternal = self._getItemExternal + + if hasattr(self, '_setSingle'): + self._canSetSingle = True + self._assignExtendedSlice = self._assignExtendedSlice_no_rebuild + else: + self._canSetSingle = False + self._setSingle = self._setSingle_rebuild + + super(ListMixin, self).__init__(*args, **kwargs) + + def _setSingle_rebuild(self, index, value): + self._checkindex(index) + self._setSlice(slice(index, index + 1, 1), [value]) + + def _checkindex(self, index): + if index < 0 or index >= len(self): + raise IndexError('invalid index: %s' % str(index)) + + def _checkAllowedTypes(self, items): + # Ensuring that only the permitted geometries are allowed in this collection + if hasattr(self, '_allowed'): + if False in [isinstance(geom, self._allowed) for geom in items]: + raise TypeError('Invalid Geometry type encountered in the arguments.') + + def __getitem__(self, index): + "Gets the coordinates of the point(s) at the specified index/slice." + if isinstance(index, slice): + return [self._getItemExternal(i) for i in xrange(*index.indices(len(self)))] + else: + if index < 0: + index += len(self) + return self._getItemExternal(index) + + def __delitem__(self, index): + "Delete the point(s) at the specified index/slice." + if not isinstance(index, (int, long, slice)): + raise TypeError("%s is not a legal index" % index) + + # calculate new length and dimensions + origLen = len(self) + if isinstance(index, (int, long)): + if index < 0: index += origLen + if not 0 <= index < origLen: + raise IndexError('invalid index: %d' % index) + indexRange = [index] + else: + indexRange = range(*index.indices(origLen)) + + newLen = origLen - len(indexRange) + newItems = ( self._getItemInternal(i) + for i in xrange(origLen) + if i not in indexRange ) + + self._setCollection(newLen, newItems) + + def __setitem__(self, index, geom): + "Sets the Geometry at the specified index." + if isinstance(index, slice): + self._setSlice(index, geom) + else: + if index < 0: index += len(self) + self._setSingle(index, geom) + + def _setSlice(self, index, values): + "Assign values to a slice of the object" + try: + iter(values) + except TypeError: + raise TypeError('can only assign an iterable to a slice') + + self._checkAllowedTypes(values) + + origLen = len(self) + valueList = list(values) + start, stop, step = index.indices(origLen) + stop = max(0, stop) # stop will be -1 if out-of-bounds + # negative index is given + + # CAREFUL: index.step and step are not the same! + # step will never be None + # + if index.step is None: + self._assignSimpleSlice(start, stop, valueList) + else: + self._assignExtendedSlice(start, stop, step, valueList) + + def _assignExtendedSlice(self, start, stop, step, valueList): + 'Assign an extended slice by rebuilding entire list' + indexList = range(start, stop, step) + # extended slice, only allow assigning slice of same size + if len(valueList) != len(indexList): + raise ValueError('attempt to assign sequence of size %d ' + 'to extended slice of size %d' + % (len(valueList), len(indexList))) + + # we're not changing the length of the sequence + newLen = len(self) + newVals = dict(zip(indexList, valueList)) + def newItems(): + for i in xrange(newLen): + if i in newVals: + yield newVals[i] + else: + yield self._getItemInternal(i) + + self._setCollection(newLen, newItems()) + + def _assignExtendedSlice_no_rebuild(self, start, stop, step, valueList): + 'Assign an extended slice by re-assigning individual items' + indexList = range(start, stop, step) + # extended slice, only allow assigning slice of same size + if len(valueList) != len(indexList): + raise ValueError('attempt to assign sequence of size %d ' + 'to extended slice of size %d' + % (len(valueList), len(indexList))) + + for i, val in zip(indexList, valueList): + self._setSingle(i, val) + + def _assignSimpleSlice(self, start, stop, valueList): + 'Assign a simple slice; Can assign slice of any length' + origLen = len(self) + newLen = origLen - stop + start + len(valueList) + def newItems(): + for i in xrange(origLen + 1): + if i == start: + for val in valueList: + yield val + + if i < origLen: + if i < start or i >= stop: + yield self._getItemInternal(i) + + self._setCollection(newLen, newItems()) + + def append(self, val): + "Standard list append method" + self[len(self):] = [val] + + def extend(self, vals): + "Standard list extend method" + self[len(self):] = vals + + def insert(self, index, val): + "Standard list insert method" + if not isinstance(index, (int, long)): + raise TypeError("%s is not a legal index" % index) + self[index:index] = [val] + + def pop(self, index=-1): + "Standard list pop method" + result = self[index] + del self[index] + return result + + def index(self, val): + "Standard list index method" + for i in xrange(0, len(self)): + if self[i] == val: return i + raise ValueError('%s not in geometry' % str(val)) + + def remove(self, val): + "Standard list remove method" + del self[self.index(val)] + + def count(self): + "Standard list count method" + return len(self) + # 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 @@ -606,3 +780,4 @@ 6 : MultiPolygon, 7 : GeometryCollection, } + Index: geos/collections.py =================================================================== --- geos/collections.py (revision 2) +++ geos/collections.py (revision 13) @@ -4,16 +4,30 @@ """ from ctypes import c_int, c_uint, byref from types import TupleType, ListType -from django.contrib.gis.geos.base import GEOSGeometry +from django.contrib.gis.geos.base import GEOSGeometry, ListMixin from django.contrib.gis.geos.error import GEOSException, GEOSIndexError from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR from django.contrib.gis.geos.prototypes import create_collection, destroy_geom, geom_clone, geos_typeid, get_cs, get_geomn -class GeometryCollection(GEOSGeometry): +class GeometryCollection(ListMixin, GEOSGeometry): _allowed = (Point, LineString, LinearRing, Polygon) _typeid = 7 + @classmethod + def _createCollection(cls, length, items): + # Creating the geometry pointer array. + geoms = get_pointer_arr(length) + for i, g in enumerate(items): + # this is a little sloppy, but makes life easier + # allow GEOSGeometry types (python wrappers) or pointer types + if hasattr(g, 'ptr'): + geoms[i] = geom_clone(g.ptr) + else: + geoms[i] = geom_clone(g) + + return create_collection(c_int(cls._typeid), byref(geoms), c_uint(length)) + def __init__(self, *args, **kwargs): "Initializes a Geometry Collection from a sequence of Geometry objects." @@ -32,39 +46,26 @@ 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.') + self._checkAllowedTypes(init_geoms) # Creating the geometry pointer array. - ngeoms = len(init_geoms) - geoms = get_pointer_arr(ngeoms) - for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i].ptr) - super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs) + collection = self._createCollection(len(init_geoms), iter(init_geoms)) + super(GeometryCollection, self).__init__(collection, **kwargs) + + def _getItemInternal(self, index): + return get_geomn(self.ptr, index) - def __getitem__(self, index): + def _getItemExternal(self, index): "Returns the Geometry from this Collection at the given index (0-based)." # Checking the index and returning the corresponding GEOS geometry. self._checkindex(index) - return GEOSGeometry(geom_clone(get_geomn(self.ptr, index)), srid=self.srid) + return GEOSGeometry(geom_clone(self._getItemInternal(index)), srid=self.srid) - def __setitem__(self, index, geom): - "Sets the Geometry at the specified index." - self._checkindex(index) - if not isinstance(geom, self._allowed): - raise TypeError('Incompatible Geometry for collection.') - - ngeoms = len(self) - geoms = get_pointer_arr(ngeoms) - for i in xrange(ngeoms): - if i == index: - geoms[i] = geom_clone(geom.ptr) - else: - geoms[i] = geom_clone(get_geomn(self.ptr, i)) - - # Creating a new collection, and destroying the contents of the previous poiner. + def _setCollection(self, length, items): + "Create a new collection, and destroy the contents of the previous pointer." prev_ptr = self.ptr srid = self.srid - self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)) + self._ptr = self._createCollection(length, items) if srid: self.srid = srid destroy_geom(prev_ptr) @@ -77,11 +78,6 @@ "Returns the number of geometries in this Collection." return self.num_geom - def _checkindex(self, index): - "Checks the given geometry index." - if index < 0 or index >= self.num_geom: - raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index)) - @property def kml(self): "Returns the KML for this Geometry Collection." Index: geos/geometries.py =================================================================== --- geos/geometries.py (revision 2) +++ geos/geometries.py (revision 13) @@ -4,7 +4,7 @@ GEOSGeometry. """ from ctypes import c_uint, byref -from django.contrib.gis.geos.base import GEOSGeometry +from django.contrib.gis.geos.base import GEOSGeometry, ListMixin from django.contrib.gis.geos.coordseq import GEOSCoordSeq from django.contrib.gis.geos.error import GEOSException, GEOSIndexError from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY @@ -104,7 +104,7 @@ tuple = property(get_coords, set_coords) coords = tuple -class LineString(GEOSGeometry): +class LineString(ListMixin, GEOSGeometry): #### Python 'magic' routines #### def __init__(self, *args, **kwargs): @@ -156,10 +156,12 @@ # Getting the correct initialization function if kwargs.get('ring', False): - func = create_linearring + self._init_func = create_linearring else: - func = create_linestring + self._init_func = create_linestring + func = self._init_func + # If SRID was passed in with the keyword arguments srid = kwargs.get('srid', None) @@ -167,12 +169,30 @@ # from the function. super(LineString, self).__init__(func(cs.ptr), srid=srid) - def __getitem__(self, index): - "Gets the point at the specified index." + def _getItemExternal(self, index): + self._checkindex(index) return self._cs[index] - def __setitem__(self, index, value): - "Sets the point at the specified index, e.g., line_str[0] = (1, 2)." + def _setCollection(self, length, items): + ndim = self._cs.dims # + hasz = self._cs.hasz # I don't understand why these are different + + # create a new coordinate sequence and populate accordingly + cs = GEOSCoordSeq(create_cs(length, ndim), z=hasz) + for i, c in enumerate(items): + cs[i] = c + + ptr = self._init_func(cs.ptr) + if ptr: + destroy_geom(self.ptr) + self._ptr = ptr + self._post_init(self.srid) + else: + # can this happen? + raise GEOSException('Geometry resulting from slice deletion was invalid.') + + def _setSingle(self, index, value): + self._checkindex(index) self._cs[index] = value def __iter__(self): Index: tests/__init__.py =================================================================== --- tests/__init__.py (revision 2) +++ tests/__init__.py (revision 13) @@ -18,6 +18,7 @@ # Tests that do not require setting up and tearing down a spatial database. test_suite_names = [ 'test_geos', + 'test_geos_pymutable', 'test_measure', ] if HAS_GDAL: Index: tests/test_geos_pymutable.py =================================================================== --- tests/test_geos_pymutable.py (revision 0) +++ tests/test_geos_pymutable.py (revision 13) @@ -0,0 +1,206 @@ +import unittest +from pymutable_geometries import * +from django.contrib.gis.geos.error import GEOSIndexError + +class GEOSPyMutableTest(unittest.TestCase): + ''' + Tests Pythonic Mutability of Python GEOS geometry wrappers + get/set/delitem on a slice, normal list methods + ''' + + def test01_getitem(self): + 'Test getting a single item from a geometry' + for g in slice_geometries(): + for i in seqrange(): + self.assertEqual(g.coords[i], getcoords(g.geom[i])) + + def test02_getslice(self): + 'Test getting a slice from a geometry' + for f in getslice_functions(): + for g in slice_geometries(): + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type)) + self.assertEqual(f(g.coords), getcoords(f(g.geom)), msg) + + def test03_getitem_indexException(self): + 'Test get single item with out-of-bounds index' + for g in slice_geometries(): + for i in SEQ_OUT_OF_BOUNDS: + self.assertRaises(IndexError, lambda: g.geom[i]) + + def test04_delitem_single(self): + 'Test delete single item from a geometry' + for i in seqrange(): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS: continue + del g.coords[i] + del g.geom[i] + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test05_delitem_slice(self): + 'Test delete slice from a geometry' + for f in delslice_functions(): + for g in slice_geometries(): + if g.geom.ring and not f.ring: continue + f(g.coords) + f(g.geom) + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type)) + self.assertEqual(g.tuple_coords, g.geom.coords, msg) + + def test06_delitem_single_indexException(self): + 'Test delete single item with out-of-bounds index' + def func(x, i): del x[i] + for g in slice_geometries(): + for i in SEQ_OUT_OF_BOUNDS: + self.assertRaises(IndexError, func, g.geom, i) + + def test07_setitem_single(self): + "Test set single item (make sure we didn't break this)" + for i in seqrange(): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS: continue + ag, ac = g.newitem(rng=(400,410)) + g.coords[i] = ac + g.geom[i] = ag + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test08_setslice_simple(self): + 'Test setting a simple slice of a geometry' + for g in slice_geometries(): + for f in setslice_simple_functions(g): + if g.geom.ring and not f.ring: continue + f(g.coords) + f(g.geom) + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type)) + self.assertEqual(g.tuple_coords, g.geom.coords, msg) + + def test09_setslice_extended(self): + 'Test setting an extended slice of a geometry' + for g in slice_geometries(): + for f in setslice_extended_functions(g): + if g.geom.ring and not f.ring: continue + f(g.coords) + f(g.geom) + msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type)) + self.assertEqual(g.tuple_coords, g.geom.coords, msg) + + def test10_setslice_extended_mismatched(self): + 'Test setting extended slice with array of mismatched length' + for g in slice_geometries(): + ag, ac = g.newitem(rng=(400,410)) + def func(): g.geom[2:8:2] = [ ag, ] + self.assertRaises(ValueError, func) + + def test11_setitem_single_indexException(self): + 'Test set single item with out-of-bounds index' + for g in slice_geometries(): + ag, ac = g.newitem(rng=(400,410)) + def func(i): g.geom[i] = ag + for i in SEQ_OUT_OF_BOUNDS: + self.assertRaises(IndexError, func, i) + + def test12_append(self): + 'Test list method append' + for g in slice_geometries(ring=False): + ag, ac = g.newitem(rng=(400,410)) + g.geom.append(ag) + g.coords.append(ac) + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test13_extend(self): + 'Test list method extend' + for g in slice_geometries(): + items = g.coords_fcn(5) + if g.geom.ring: items[-1] = g.coords[0] + g.geom.extend(map(g.subtype,items)) + g.coords.extend(items) + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test14_insert(self): + 'Test list method insert' + for i in xrange(*SEQ_OUT_OF_BOUNDS): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS: + continue + ag, ac = g.newitem(rng=(200,250)) + g.geom.insert(i, ag) + g.coords.insert(i, ac) + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test15_insert_typeError(self): + 'Test list method insert raises error on invalid index' + for g in slice_geometries(): + ag, ac = g.newitem(rng=(200,250)) + self.assertRaises(TypeError, g.geom.insert, 'hi', ag) + + def test16_pop(self): + 'Test list method pop' + for i in seqrange(): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS: + continue + self.assertEqual(g.coords.pop(i), getcoords(g.geom.pop(i))) + + def test16_index(self): + 'Test list method index' + for i in xrange(0, SEQ_LENGTH): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS: continue + ag, ac = g.newitem(rng=(400,410)) + g.geom[i] = ag + self.assertEqual(i, g.geom.index(ag)) + + def test17_index_ValueError(self): + 'Test list method raises ValueError if value not found' + for g in slice_geometries(): + ag, ac = g.newitem(rng=(400,410)) + self.assertRaises(ValueError, g.geom.index, ag) + + def test18_remove(self): + 'Test list method remove' + for i in xrange(0, SEQ_LENGTH): + for g in slice_geometries(): + if g.geom.ring and i in SEQ_BOUNDS: continue + ag, ac = g.newitem(rng=(400,410)) + g.geom[i] = ag + g.coords[i] = ac + g.geom.remove(ag) + g.coords.remove(ac) + self.assertEqual(g.tuple_coords, g.geom.coords) + + def test19_count(self): + 'Test list method count' + for g in slice_geometries(): + self.assertEqual(SEQ_LENGTH, g.geom.count()) + + def test20_setslice_geos_fcns(self): + 'Test geos properties after setting a simple slice of a geometry' + for g in slice_geometries(ring=False): + for f in setslice_simple_functions(g): + f(g.coords) + f(g.geom) + if not len(g.coords): continue + for tf in test_geos_functions(): + cg = g.make_geom() + self.assertEqual(tf(cg) , tf(g.geom)) + + def test21_delslice_geos_fcns(self): + 'Test geos properties after deleting a slice of a geometry' + for f in delslice_functions(): + for g in slice_geometries(ring=False): + f(g.coords) + f(g.geom) + if not len(g.coords): continue + for tf in test_geos_functions(): + cg = g.make_geom() + self.assertEqual(tf(cg) , tf(g.geom)) + +def suite(): + s = unittest.TestSuite() + s.addTest(unittest.makeSuite(GEOSPyMutableTest)) + return s + +def run(verbosity=2): + unittest.TextTestRunner(verbosity=verbosity).run(suite()) + +if __name__ == '__main__': + run() Property changes on: tests/test_geos_pymutable.py ___________________________________________________________________ Name: svn:executable + * Index: tests/pymutable_geometries.py =================================================================== --- tests/pymutable_geometries.py (revision 0) +++ tests/pymutable_geometries.py (revision 13) @@ -0,0 +1,185 @@ +from django.contrib.gis.geos import * +from random import random + +SEQ_LENGTH = 10 +SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH) +SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1) +SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH) + +def seqrange(): return xrange(*SEQ_RANGE) + +def random_coord(dim = 2, # coordinate dimensions + rng = (-50,50), # coordinate range + num_type = float, + round_coords = True): + + if round_coords: + num = lambda: num_type(round(random() * (rng[1]-rng[0]) + rng[0])) + else: + num = lambda: num_type(random() * (rng[1]-rng[0]) + rng[0]) + + return tuple( num() for axis in xrange(dim) ) + +def random_list(length = SEQ_LENGTH, ring = False, **kwargs): + result = [ random_coord(**kwargs) for index in xrange(length) ] + if ring: + result[-1] = result[0] + + return result + +random_list.single = random_coord + +def random_coll(count = SEQ_LENGTH, **kwargs): + return [ tuple(random_list(**kwargs)) for i in xrange(count) ] + +random_coll.single = random_list + +class PyMutTestGeom: + "The Test Geometry class container." + def __init__(self, geom_type, coords_fcn=random_list, subtype=tuple, **kwargs): + self.geom_type = geom_type + self.subtype = subtype + self.coords_fcn = coords_fcn + self.fcn_args = kwargs + self.coords = self.coords_fcn(**kwargs) + self.geom = self.make_geom() + + def newitem(self, **kwargs): + a = self.coords_fcn.single(**kwargs) + return self.subtype(a), tuple(a) + + @property + def tuple_coords(self): + return tuple(self.coords) + + def make_geom(self): + return self.geom_type(map(self.subtype,self.coords)) + + +def slice_geometries(ring=True): + testgeoms = [ + PyMutTestGeom(LineString), + PyMutTestGeom(MultiPoint, subtype=Point), + PyMutTestGeom(MultiLineString, coords_fcn=random_coll, subtype=LineString), + ] + if ring: + testgeoms.append(PyMutTestGeom(LinearRing, ring=True)) + + return testgeoms + +def getslice_functions(): + def gs_01(x): x[0:4], + def gs_02(x): x[5:-1], + def gs_03(x): x[6:2:-1], + def gs_04(x): x[:], + def gs_05(x): x[:3], + def gs_06(x): x[::2], + def gs_07(x): x[::-4], + def gs_08(x): x[7:7], + def gs_09(x): x[20:], + + # don't really care about ringy-ness here + return mark_ring(vars(), 'gs_') + +def delslice_functions(): + def ds_01(x): del x[0:4] + def ds_02(x): del x[5:-1] + def ds_03(x): del x[6:2:-1] + def ds_04(x): del x[:] # should this be allowed? + def ds_05(x): del x[:3] + def ds_06(x): del x[1:9:2] + def ds_07(x): del x[::-4] + def ds_08(x): del x[7:7] + def ds_09(x): del x[-7:-2] + + return mark_ring(vars(), 'ds_') + +def setslice_extended_functions(g): + a = g.coords_fcn(3, rng=(100,150)) + def maptype(x,a): + if isinstance(x, list): return a + else: return map(g.subtype, a) + + def sse_00(x): x[:3:1] = maptype(x, a) + def sse_01(x): x[0:3:1] = maptype(x, a) + def sse_02(x): x[2:5:1] = maptype(x, a) + def sse_03(x): x[-3::1] = maptype(x, a) + def sse_04(x): x[-4:-1:1] = maptype(x, a) + def sse_05(x): x[8:5:-1] = maptype(x, a) + def sse_06(x): x[-6:-9:-1] = maptype(x, a) + def sse_07(x): x[:8:3] = maptype(x, a) + def sse_08(x): x[1::3] = maptype(x, a) + def sse_09(x): x[-2::-3] = maptype(x, a) + def sse_10(x): x[7:1:-2] = maptype(x, a) + def sse_11(x): x[2:8:2] = maptype(x, a) + + return mark_ring(vars(), 'sse_') + +def setslice_simple_functions(g): + a = g.coords_fcn(3, rng=(100,150)) + def maptype(x,a): + if isinstance(x, list): return a + else: return map(g.subtype, a) + + def ss_00(x): x[:0] = maptype(x, a) + def ss_01(x): x[:1] = maptype(x, a) + def ss_02(x): x[:2] = maptype(x, a) + def ss_03(x): x[:3] = maptype(x, a) + def ss_04(x): x[-4:] = maptype(x, a) + def ss_05(x): x[-3:] = maptype(x, a) + def ss_06(x): x[-2:] = maptype(x, a) + def ss_07(x): x[-1:] = maptype(x, a) + def ss_08(x): x[5:] = maptype(x, a) + def ss_09(x): x[:] = maptype(x, a) + def ss_10(x): x[4:4] = maptype(x, a) + def ss_11(x): x[4:5] = maptype(x, a) + def ss_12(x): x[4:7] = maptype(x, a) + def ss_13(x): x[4:8] = maptype(x, a) + def ss_14(x): x[10:] = maptype(x, a) + def ss_15(x): x[20:30] = maptype(x, a) + def ss_16(x): x[-13:-8] = maptype(x, a) + def ss_17(x): x[-13:-9] = maptype(x, a) + def ss_18(x): x[-13:-10] = maptype(x, a) + def ss_19(x): x[-13:-11] = maptype(x, a) + + return mark_ring(vars(), 'ss_') + +def test_geos_functions(): + + return ( + lambda x: x.num_coords, + lambda x: x.empty, + lambda x: x.valid, + lambda x: x.simple, + lambda x: x.ring, + lambda x: x.boundary, + lambda x: x.convex_hull, + lambda x: x.extend, + lambda x: x.area, + lambda x: x.length, + ) + +def mark_ring(locals, name_pat, length=SEQ_LENGTH): + ''' + Accepts an array of functions which perform slice modifications + and labels each function as to whether or not it preserves ring-ness + ''' + func_array = [ val for name, val in locals.items() + if hasattr(val, '__call__') + and name.startswith(name_pat) ] + + for i in xrange(len(func_array)): + a = range(length) + a[-1] = a[0] + func_array[i](a) + ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0]) + func_array[i].ring = ring + + return func_array + +def getcoords(o): + if hasattr(o, 'coords'): + return o.coords + else: + return o + Property changes on: tests/pymutable_geometries.py ___________________________________________________________________ Name: svn:executable + *