Ticket #9877: pythonic_mutation_patch_part_II

File pythonic_mutation_patch_part_II, 28.5 KB (added by Aryeh Leib Taurog <vim@…>, 7 years ago)

More complete list-style mutations for Geometry Collections; refactored code

Line 
1Index: geos/base.py
2===================================================================
3--- geos/base.py        (revision 2)
4+++ geos/base.py        (revision 13)
5@@ -594,6 +594,180 @@
6         "Clones this Geometry."
7         return GEOSGeometry(geom_clone(self.ptr), srid=self.srid)
8 
9+class ListMixin(object):
10+
11+    def __init__(self, *args, **kwargs):
12+        if not hasattr(self, '_getItemInternal'):
13+            self._getItemInternal = self._getItemExternal
14+
15+        if hasattr(self, '_setSingle'):
16+            self._canSetSingle = True
17+            self._assignExtendedSlice = self._assignExtendedSlice_no_rebuild
18+        else:
19+            self._canSetSingle = False
20+            self._setSingle = self._setSingle_rebuild
21+
22+        super(ListMixin, self).__init__(*args, **kwargs)
23+
24+    def _setSingle_rebuild(self, index, value):
25+        self._checkindex(index)
26+        self._setSlice(slice(index, index + 1, 1), [value])       
27+
28+    def _checkindex(self, index):
29+        if index < 0 or index >= len(self):
30+            raise IndexError('invalid index: %s' % str(index))
31+
32+    def _checkAllowedTypes(self, items):
33+        # Ensuring that only the permitted geometries are allowed in this collection
34+        if hasattr(self, '_allowed'):
35+            if False in [isinstance(geom, self._allowed) for geom in items]:
36+                raise TypeError('Invalid Geometry type encountered in the arguments.')
37+
38+    def __getitem__(self, index):
39+        "Gets the coordinates of the point(s) at the specified index/slice."
40+        if isinstance(index, slice):
41+            return [self._getItemExternal(i) for i in xrange(*index.indices(len(self)))]
42+        else:
43+            if index < 0:
44+                index += len(self)
45+            return self._getItemExternal(index)
46+
47+    def __delitem__(self, index):
48+        "Delete the point(s) at the specified index/slice."
49+        if not isinstance(index, (int, long, slice)):
50+            raise TypeError("%s is not a legal index" % index)
51+
52+        # calculate new length and dimensions
53+        origLen     = len(self)
54+        if isinstance(index, (int, long)):
55+            if index < 0: index += origLen
56+            if not 0 <= index < origLen:
57+                raise IndexError('invalid index: %d' % index)
58+            indexRange  = [index]
59+        else:
60+            indexRange  = range(*index.indices(origLen))
61+
62+        newLen      = origLen - len(indexRange)
63+        newItems    = ( self._getItemInternal(i)
64+                        for i in xrange(origLen)
65+                        if i not in indexRange )
66+
67+        self._setCollection(newLen, newItems)
68+
69+    def __setitem__(self, index, geom):
70+        "Sets the Geometry at the specified index."
71+        if isinstance(index, slice):
72+            self._setSlice(index, geom)
73+        else:
74+            if index < 0: index += len(self)
75+            self._setSingle(index, geom)
76+
77+    def _setSlice(self, index, values):
78+        "Assign values to a slice of the object"
79+        try:
80+            iter(values)
81+        except TypeError:
82+            raise TypeError('can only assign an iterable to a slice')
83+
84+        self._checkAllowedTypes(values)
85+
86+        origLen     = len(self)
87+        valueList   = list(values)
88+        start, stop, step = index.indices(origLen)
89+        stop = max(0, stop) # stop will be -1 if out-of-bounds
90+                            # negative index is given
91+
92+        # CAREFUL: index.step and step are not the same!
93+        # step will never be None
94+        #
95+        if index.step is None:
96+            self._assignSimpleSlice(start, stop, valueList)
97+        else:
98+            self._assignExtendedSlice(start, stop, step, valueList)
99+
100+    def _assignExtendedSlice(self, start, stop, step, valueList):
101+        'Assign an extended slice by rebuilding entire list'
102+        indexList   = range(start, stop, step)
103+        # extended slice, only allow assigning slice of same size
104+        if len(valueList) != len(indexList):
105+            raise ValueError('attempt to assign sequence of size %d '
106+                             'to extended slice of size %d'
107+                             % (len(valueList), len(indexList)))
108+
109+        # we're not changing the length of the sequence
110+        newLen  = len(self)
111+        newVals = dict(zip(indexList, valueList))
112+        def newItems():
113+            for i in xrange(newLen):
114+                if i in newVals:
115+                    yield newVals[i]
116+                else:
117+                    yield self._getItemInternal(i)
118+
119+        self._setCollection(newLen, newItems())
120+
121+    def _assignExtendedSlice_no_rebuild(self, start, stop, step, valueList):
122+        'Assign an extended slice by re-assigning individual items'
123+        indexList   = range(start, stop, step)
124+        # extended slice, only allow assigning slice of same size
125+        if len(valueList) != len(indexList):
126+            raise ValueError('attempt to assign sequence of size %d '
127+                             'to extended slice of size %d'
128+                             % (len(valueList), len(indexList)))
129+
130+        for i, val in zip(indexList, valueList):
131+            self._setSingle(i, val)
132+
133+    def _assignSimpleSlice(self, start, stop, valueList):
134+        'Assign a simple slice; Can assign slice of any length'
135+        origLen = len(self)
136+        newLen  = origLen - stop + start + len(valueList)
137+        def newItems():
138+            for i in xrange(origLen + 1):
139+                if i == start:
140+                    for val in valueList:
141+                        yield val
142+
143+                if i < origLen:
144+                    if i < start or i >= stop:
145+                        yield self._getItemInternal(i)
146+
147+        self._setCollection(newLen, newItems())
148+
149+    def append(self, val):
150+        "Standard list append method"
151+        self[len(self):] = [val]
152+
153+    def extend(self, vals):
154+        "Standard list extend method"
155+        self[len(self):] = vals
156+
157+    def insert(self, index, val):
158+        "Standard list insert method"
159+        if not isinstance(index, (int, long)):
160+            raise TypeError("%s is not a legal index" % index)
161+        self[index:index] = [val]
162+
163+    def pop(self, index=-1):
164+        "Standard list pop method"
165+        result = self[index]
166+        del self[index]
167+        return result
168+
169+    def index(self, val):
170+        "Standard list index method"
171+        for i in xrange(0, len(self)):
172+            if self[i] == val: return i
173+        raise ValueError('%s not in geometry' % str(val))
174+
175+    def remove(self, val):
176+        "Standard list remove method"
177+        del self[self.index(val)]
178+
179+    def count(self):
180+        "Standard list count method"
181+        return len(self)
182+
183 # Class mapping dictionary
184 from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
185 from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
186@@ -606,3 +780,4 @@
187                 6 : MultiPolygon,
188                 7 : GeometryCollection,
189                 }
190+
191Index: geos/collections.py
192===================================================================
193--- geos/collections.py (revision 2)
194+++ geos/collections.py (revision 13)
195@@ -4,16 +4,30 @@
196 """
197 from ctypes import c_int, c_uint, byref
198 from types import TupleType, ListType
199-from django.contrib.gis.geos.base import GEOSGeometry
200+from django.contrib.gis.geos.base import GEOSGeometry, ListMixin
201 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
202 from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
203 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
204 from django.contrib.gis.geos.prototypes import create_collection, destroy_geom, geom_clone, geos_typeid, get_cs, get_geomn
205 
206-class GeometryCollection(GEOSGeometry):
207+class GeometryCollection(ListMixin, GEOSGeometry):
208     _allowed = (Point, LineString, LinearRing, Polygon)
209     _typeid = 7
210 
211+    @classmethod
212+    def _createCollection(cls, length, items):
213+        # Creating the geometry pointer array.
214+        geoms = get_pointer_arr(length)
215+        for i, g in enumerate(items):
216+            # this is a little sloppy, but makes life easier
217+            # allow GEOSGeometry types (python wrappers) or pointer types
218+            if hasattr(g, 'ptr'):
219+                geoms[i] = geom_clone(g.ptr)
220+            else:
221+                geoms[i] = geom_clone(g)
222+
223+        return create_collection(c_int(cls._typeid), byref(geoms), c_uint(length))
224+
225     def __init__(self, *args, **kwargs):
226         "Initializes a Geometry Collection from a sequence of Geometry objects."
227 
228@@ -32,39 +46,26 @@
229             init_geoms = args
230 
231         # Ensuring that only the permitted geometries are allowed in this collection
232-        if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
233-            raise TypeError('Invalid Geometry type encountered in the arguments.')
234+        self._checkAllowedTypes(init_geoms)
235 
236         # Creating the geometry pointer array.
237-        ngeoms = len(init_geoms)
238-        geoms = get_pointer_arr(ngeoms)
239-        for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i].ptr)
240-        super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs)
241+        collection = self._createCollection(len(init_geoms), iter(init_geoms))
242+        super(GeometryCollection, self).__init__(collection, **kwargs)
243+       
244+    def _getItemInternal(self, index):
245+        return get_geomn(self.ptr, index)
246 
247-    def __getitem__(self, index):
248+    def _getItemExternal(self, index):
249         "Returns the Geometry from this Collection at the given index (0-based)."
250         # Checking the index and returning the corresponding GEOS geometry.
251         self._checkindex(index)
252-        return GEOSGeometry(geom_clone(get_geomn(self.ptr, index)), srid=self.srid)
253+        return GEOSGeometry(geom_clone(self._getItemInternal(index)), srid=self.srid)
254 
255-    def __setitem__(self, index, geom):
256-        "Sets the Geometry at the specified index."
257-        self._checkindex(index)
258-        if not isinstance(geom, self._allowed):
259-            raise TypeError('Incompatible Geometry for collection.')
260-       
261-        ngeoms = len(self)
262-        geoms = get_pointer_arr(ngeoms)
263-        for i in xrange(ngeoms):
264-            if i == index:
265-                geoms[i] = geom_clone(geom.ptr)
266-            else:
267-                geoms[i] = geom_clone(get_geomn(self.ptr, i))
268-       
269-        # Creating a new collection, and destroying the contents of the previous poiner.
270+    def _setCollection(self, length, items):
271+        "Create a new collection, and destroy the contents of the previous pointer."
272         prev_ptr = self.ptr
273         srid = self.srid
274-        self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms))
275+        self._ptr = self._createCollection(length, items)
276         if srid: self.srid = srid
277         destroy_geom(prev_ptr)
278 
279@@ -77,11 +78,6 @@
280         "Returns the number of geometries in this Collection."
281         return self.num_geom
282 
283-    def _checkindex(self, index):
284-        "Checks the given geometry index."
285-        if index < 0 or index >= self.num_geom:
286-            raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index))
287-
288     @property
289     def kml(self):
290         "Returns the KML for this Geometry Collection."
291Index: geos/geometries.py
292===================================================================
293--- geos/geometries.py  (revision 2)
294+++ geos/geometries.py  (revision 13)
295@@ -4,7 +4,7 @@
296  GEOSGeometry.
297 """
298 from ctypes import c_uint, byref
299-from django.contrib.gis.geos.base import GEOSGeometry
300+from django.contrib.gis.geos.base import GEOSGeometry, ListMixin
301 from django.contrib.gis.geos.coordseq import GEOSCoordSeq
302 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
303 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY
304@@ -104,7 +104,7 @@
305     tuple = property(get_coords, set_coords)
306     coords = tuple
307 
308-class LineString(GEOSGeometry):
309+class LineString(ListMixin, GEOSGeometry):
310 
311     #### Python 'magic' routines ####
312     def __init__(self, *args, **kwargs):
313@@ -156,10 +156,12 @@
314 
315         # Getting the correct initialization function
316         if kwargs.get('ring', False):
317-            func = create_linearring
318+            self._init_func = create_linearring
319         else:
320-            func = create_linestring
321+            self._init_func = create_linestring
322 
323+        func = self._init_func
324+
325         # If SRID was passed in with the keyword arguments
326         srid = kwargs.get('srid', None)
327       
328@@ -167,12 +169,30 @@
329         #  from the function.
330         super(LineString, self).__init__(func(cs.ptr), srid=srid)
331 
332-    def __getitem__(self, index):
333-        "Gets the point at the specified index."
334+    def _getItemExternal(self, index):
335+        self._checkindex(index)
336         return self._cs[index]
337 
338-    def __setitem__(self, index, value):
339-        "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
340+    def _setCollection(self, length, items):
341+        ndim        = self._cs.dims #
342+        hasz        = self._cs.hasz # I don't understand why these are different
343+
344+        # create a new coordinate sequence and populate accordingly
345+        cs = GEOSCoordSeq(create_cs(length, ndim), z=hasz)
346+        for i, c in enumerate(items):
347+            cs[i] = c
348+
349+        ptr = self._init_func(cs.ptr)
350+        if ptr:
351+            destroy_geom(self.ptr)
352+            self._ptr = ptr
353+            self._post_init(self.srid)
354+        else:
355+            # can this happen?
356+            raise GEOSException('Geometry resulting from slice deletion was invalid.')
357+       
358+    def _setSingle(self, index, value):
359+        self._checkindex(index)
360         self._cs[index] = value
361 
362     def __iter__(self):
363Index: tests/__init__.py
364===================================================================
365--- tests/__init__.py   (revision 2)
366+++ tests/__init__.py   (revision 13)
367@@ -18,6 +18,7 @@
368     # Tests that do not require setting up and tearing down a spatial database.
369     test_suite_names = [
370         'test_geos',
371+        'test_geos_pymutable',
372         'test_measure',
373         ]
374     if HAS_GDAL:
375Index: tests/test_geos_pymutable.py
376===================================================================
377--- tests/test_geos_pymutable.py        (revision 0)
378+++ tests/test_geos_pymutable.py        (revision 13)
379@@ -0,0 +1,206 @@
380+import unittest
381+from pymutable_geometries import *
382+from django.contrib.gis.geos.error import GEOSIndexError
383+   
384+class GEOSPyMutableTest(unittest.TestCase):
385+    '''
386+    Tests Pythonic Mutability of Python GEOS geometry wrappers
387+    get/set/delitem on a slice, normal list methods
388+    '''
389+
390+    def test01_getitem(self):
391+        'Test getting a single item from a geometry'
392+        for g in slice_geometries():
393+            for i in seqrange():
394+                self.assertEqual(g.coords[i], getcoords(g.geom[i]))
395+
396+    def test02_getslice(self):
397+        'Test getting a slice from a geometry'
398+        for f in getslice_functions():
399+            for g in slice_geometries():
400+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
401+                self.assertEqual(f(g.coords), getcoords(f(g.geom)), msg)
402+
403+    def test03_getitem_indexException(self):
404+        'Test get single item with out-of-bounds index'
405+        for g in slice_geometries():
406+            for i in SEQ_OUT_OF_BOUNDS:
407+                self.assertRaises(IndexError, lambda: g.geom[i])
408+
409+    def test04_delitem_single(self):
410+        'Test delete single item from a geometry'
411+        for i in seqrange():
412+            for g in slice_geometries():
413+                if g.geom.ring and i in SEQ_BOUNDS: continue
414+                del g.coords[i]
415+                del g.geom[i]
416+                self.assertEqual(g.tuple_coords, g.geom.coords)
417+
418+    def test05_delitem_slice(self):
419+        'Test delete slice from a geometry'
420+        for f in delslice_functions():
421+            for g in slice_geometries():
422+                if g.geom.ring and not f.ring: continue
423+                f(g.coords)
424+                f(g.geom)
425+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
426+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
427+
428+    def test06_delitem_single_indexException(self):
429+        'Test delete single item with out-of-bounds index'
430+        def func(x, i): del x[i]
431+        for g in slice_geometries():
432+            for i in SEQ_OUT_OF_BOUNDS:
433+                self.assertRaises(IndexError, func, g.geom, i)
434+
435+    def test07_setitem_single(self):
436+        "Test set single item (make sure we didn't break this)"
437+        for i in seqrange():
438+            for g in slice_geometries():
439+                if g.geom.ring and i in SEQ_BOUNDS: continue
440+                ag, ac = g.newitem(rng=(400,410))
441+                g.coords[i] = ac
442+                g.geom[i] = ag
443+                self.assertEqual(g.tuple_coords, g.geom.coords)
444+
445+    def test08_setslice_simple(self):
446+        'Test setting a simple slice of a geometry'
447+        for g in slice_geometries():
448+            for f in setslice_simple_functions(g):
449+                if g.geom.ring and not f.ring: continue
450+                f(g.coords)
451+                f(g.geom)
452+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
453+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
454+
455+    def test09_setslice_extended(self):
456+        'Test setting an extended slice of a geometry'
457+        for g in slice_geometries():
458+            for f in setslice_extended_functions(g):
459+                if g.geom.ring and not f.ring: continue
460+                f(g.coords)
461+                f(g.geom)
462+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
463+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
464+               
465+    def test10_setslice_extended_mismatched(self):
466+        'Test setting extended slice with array of mismatched length'
467+        for g in slice_geometries():
468+            ag, ac = g.newitem(rng=(400,410))
469+            def func(): g.geom[2:8:2] = [ ag, ]
470+            self.assertRaises(ValueError, func)
471+
472+    def test11_setitem_single_indexException(self):
473+        'Test set single item with out-of-bounds index'
474+        for g in slice_geometries():
475+            ag, ac = g.newitem(rng=(400,410))
476+            def func(i): g.geom[i] = ag
477+            for i in SEQ_OUT_OF_BOUNDS:
478+                self.assertRaises(IndexError, func, i)
479+
480+    def test12_append(self):
481+        'Test list method append'
482+        for g in slice_geometries(ring=False):
483+            ag, ac = g.newitem(rng=(400,410))
484+            g.geom.append(ag)
485+            g.coords.append(ac)
486+            self.assertEqual(g.tuple_coords, g.geom.coords)
487+
488+    def test13_extend(self):
489+        'Test list method extend'
490+        for g in slice_geometries():
491+            items = g.coords_fcn(5)
492+            if g.geom.ring: items[-1] = g.coords[0]
493+            g.geom.extend(map(g.subtype,items))
494+            g.coords.extend(items)
495+            self.assertEqual(g.tuple_coords, g.geom.coords)
496+
497+    def test14_insert(self):
498+        'Test list method insert'
499+        for i in xrange(*SEQ_OUT_OF_BOUNDS):
500+            for g in slice_geometries():
501+                if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
502+                    continue
503+                ag, ac = g.newitem(rng=(200,250))
504+                g.geom.insert(i, ag)
505+                g.coords.insert(i, ac)
506+                self.assertEqual(g.tuple_coords, g.geom.coords)
507+
508+    def test15_insert_typeError(self):
509+        'Test list method insert raises error on invalid index'
510+        for g in slice_geometries():
511+            ag, ac = g.newitem(rng=(200,250))
512+            self.assertRaises(TypeError, g.geom.insert, 'hi', ag)
513+
514+    def test16_pop(self):
515+        'Test list method pop'
516+        for i in seqrange():
517+            for g in slice_geometries():
518+                if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
519+                    continue
520+                self.assertEqual(g.coords.pop(i), getcoords(g.geom.pop(i)))
521+
522+    def test16_index(self):
523+        'Test list method index'
524+        for i in xrange(0, SEQ_LENGTH):
525+            for g in slice_geometries():
526+                if g.geom.ring and i in SEQ_BOUNDS: continue
527+                ag, ac = g.newitem(rng=(400,410))
528+                g.geom[i] = ag
529+                self.assertEqual(i, g.geom.index(ag))
530+
531+    def test17_index_ValueError(self):
532+        'Test list method raises ValueError if value not found'
533+        for g in slice_geometries():
534+            ag, ac = g.newitem(rng=(400,410))
535+            self.assertRaises(ValueError, g.geom.index, ag)
536+
537+    def test18_remove(self):
538+        'Test list method remove'
539+        for i in xrange(0, SEQ_LENGTH):
540+            for g in slice_geometries():
541+                if g.geom.ring and i in SEQ_BOUNDS: continue
542+                ag, ac = g.newitem(rng=(400,410))
543+                g.geom[i] = ag
544+                g.coords[i] = ac
545+                g.geom.remove(ag)
546+                g.coords.remove(ac)
547+                self.assertEqual(g.tuple_coords, g.geom.coords)
548+
549+    def test19_count(self):
550+        'Test list method count'
551+        for g in slice_geometries():
552+            self.assertEqual(SEQ_LENGTH, g.geom.count())
553+
554+    def test20_setslice_geos_fcns(self):
555+        'Test geos properties after setting a simple slice of a geometry'
556+        for g in slice_geometries(ring=False):
557+            for f in setslice_simple_functions(g):
558+                f(g.coords)
559+                f(g.geom)
560+                if not len(g.coords): continue
561+                for tf in test_geos_functions():
562+                    cg = g.make_geom()
563+                    self.assertEqual(tf(cg) , tf(g.geom))
564+
565+    def test21_delslice_geos_fcns(self):
566+        'Test geos properties after deleting a slice of a geometry'
567+        for f in delslice_functions():
568+            for g in slice_geometries(ring=False):
569+                f(g.coords)
570+                f(g.geom)
571+                if not len(g.coords): continue
572+                for tf in test_geos_functions():
573+                    cg = g.make_geom()
574+                    self.assertEqual(tf(cg) , tf(g.geom))
575+
576+def suite():
577+    s = unittest.TestSuite()
578+    s.addTest(unittest.makeSuite(GEOSPyMutableTest))
579+    return s
580+
581+def run(verbosity=2):
582+    unittest.TextTestRunner(verbosity=verbosity).run(suite())
583+
584+if __name__ == '__main__':
585+    run()
586
587Property changes on: tests/test_geos_pymutable.py
588___________________________________________________________________
589Name: svn:executable
590   + *
591
592Index: tests/pymutable_geometries.py
593===================================================================
594--- tests/pymutable_geometries.py       (revision 0)
595+++ tests/pymutable_geometries.py       (revision 13)
596@@ -0,0 +1,185 @@
597+from django.contrib.gis.geos import *
598+from random import random
599+
600+SEQ_LENGTH = 10
601+SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
602+SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
603+SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
604+
605+def seqrange(): return xrange(*SEQ_RANGE)
606+
607+def random_coord(dim    = 2,           # coordinate dimensions
608+                 rng    = (-50,50),    # coordinate range
609+                 num_type     = float,
610+                 round_coords = True):
611+
612+    if round_coords:
613+        num = lambda: num_type(round(random() * (rng[1]-rng[0]) + rng[0]))
614+    else:
615+        num = lambda: num_type(random() * (rng[1]-rng[0]) + rng[0])
616+   
617+    return tuple( num() for axis in xrange(dim) )
618+
619+def random_list(length = SEQ_LENGTH, ring = False, **kwargs):
620+    result  = [ random_coord(**kwargs) for index in xrange(length) ]
621+    if ring:
622+        result[-1] = result[0]
623+
624+    return result
625+
626+random_list.single = random_coord
627+
628+def random_coll(count = SEQ_LENGTH, **kwargs):
629+    return [ tuple(random_list(**kwargs)) for i in xrange(count) ]
630+
631+random_coll.single = random_list
632+
633+class PyMutTestGeom:
634+    "The Test Geometry class container."
635+    def __init__(self, geom_type, coords_fcn=random_list, subtype=tuple, **kwargs):
636+        self.geom_type  = geom_type
637+        self.subtype    = subtype
638+        self.coords_fcn = coords_fcn
639+        self.fcn_args   = kwargs
640+        self.coords = self.coords_fcn(**kwargs)
641+        self.geom   = self.make_geom()
642+
643+    def newitem(self, **kwargs):
644+        a = self.coords_fcn.single(**kwargs)
645+        return self.subtype(a), tuple(a)
646+
647+    @property
648+    def tuple_coords(self):
649+        return tuple(self.coords)
650+
651+    def make_geom(self):
652+        return self.geom_type(map(self.subtype,self.coords))
653+
654+
655+def slice_geometries(ring=True):
656+    testgeoms = [
657+        PyMutTestGeom(LineString),
658+        PyMutTestGeom(MultiPoint, subtype=Point),
659+        PyMutTestGeom(MultiLineString, coords_fcn=random_coll, subtype=LineString),
660+        ]
661+    if ring:
662+        testgeoms.append(PyMutTestGeom(LinearRing, ring=True))
663+
664+    return testgeoms
665+
666+def getslice_functions():
667+    def gs_01(x): x[0:4],   
668+    def gs_02(x): x[5:-1], 
669+    def gs_03(x): x[6:2:-1],
670+    def gs_04(x): x[:],     
671+    def gs_05(x): x[:3],   
672+    def gs_06(x): x[::2],   
673+    def gs_07(x): x[::-4], 
674+    def gs_08(x): x[7:7],   
675+    def gs_09(x): x[20:],   
676+
677+    # don't really care about ringy-ness here
678+    return mark_ring(vars(), 'gs_')
679+
680+def delslice_functions():
681+    def ds_01(x): del x[0:4]   
682+    def ds_02(x): del x[5:-1] 
683+    def ds_03(x): del x[6:2:-1]
684+    def ds_04(x): del x[:]     # should this be allowed?
685+    def ds_05(x): del x[:3]   
686+    def ds_06(x): del x[1:9:2]   
687+    def ds_07(x): del x[::-4] 
688+    def ds_08(x): del x[7:7]   
689+    def ds_09(x): del x[-7:-2]
690+
691+    return mark_ring(vars(), 'ds_')
692+
693+def setslice_extended_functions(g):
694+    a = g.coords_fcn(3, rng=(100,150))
695+    def maptype(x,a):
696+        if isinstance(x, list): return a
697+        else: return map(g.subtype, a)
698+
699+    def sse_00(x): x[:3:1]      = maptype(x, a)
700+    def sse_01(x): x[0:3:1]     = maptype(x, a)
701+    def sse_02(x): x[2:5:1]     = maptype(x, a)
702+    def sse_03(x): x[-3::1]     = maptype(x, a)
703+    def sse_04(x): x[-4:-1:1]   = maptype(x, a)
704+    def sse_05(x): x[8:5:-1]    = maptype(x, a)
705+    def sse_06(x): x[-6:-9:-1]  = maptype(x, a)
706+    def sse_07(x): x[:8:3]      = maptype(x, a)
707+    def sse_08(x): x[1::3]      = maptype(x, a)
708+    def sse_09(x): x[-2::-3]    = maptype(x, a)
709+    def sse_10(x): x[7:1:-2]    = maptype(x, a)
710+    def sse_11(x): x[2:8:2]     = maptype(x, a)
711+
712+    return mark_ring(vars(), 'sse_')
713+
714+def setslice_simple_functions(g):
715+    a = g.coords_fcn(3, rng=(100,150))
716+    def maptype(x,a):
717+        if isinstance(x, list): return a
718+        else: return map(g.subtype, a)
719+
720+    def ss_00(x): x[:0]  = maptype(x, a)
721+    def ss_01(x): x[:1]  = maptype(x, a)
722+    def ss_02(x): x[:2]  = maptype(x, a)
723+    def ss_03(x): x[:3]  = maptype(x, a)
724+    def ss_04(x): x[-4:] = maptype(x, a)
725+    def ss_05(x): x[-3:] = maptype(x, a)
726+    def ss_06(x): x[-2:] = maptype(x, a)
727+    def ss_07(x): x[-1:] = maptype(x, a)
728+    def ss_08(x): x[5:]  = maptype(x, a)
729+    def ss_09(x): x[:]   = maptype(x, a)
730+    def ss_10(x): x[4:4] = maptype(x, a)
731+    def ss_11(x): x[4:5] = maptype(x, a)
732+    def ss_12(x): x[4:7] = maptype(x, a)
733+    def ss_13(x): x[4:8] = maptype(x, a)
734+    def ss_14(x): x[10:] = maptype(x, a)
735+    def ss_15(x): x[20:30]  = maptype(x, a)
736+    def ss_16(x): x[-13:-8] = maptype(x, a)
737+    def ss_17(x): x[-13:-9] = maptype(x, a)
738+    def ss_18(x): x[-13:-10] = maptype(x, a)
739+    def ss_19(x): x[-13:-11] = maptype(x, a)
740+
741+    return mark_ring(vars(), 'ss_')
742+
743+def test_geos_functions():
744+
745+    return (
746+        lambda x: x.num_coords,
747+        lambda x: x.empty,
748+        lambda x: x.valid,
749+        lambda x: x.simple,
750+        lambda x: x.ring,
751+        lambda x: x.boundary,
752+        lambda x: x.convex_hull,
753+        lambda x: x.extend,
754+        lambda x: x.area,
755+        lambda x: x.length,
756+            )
757+
758+def mark_ring(locals, name_pat, length=SEQ_LENGTH):
759+    '''
760+    Accepts an array of functions which perform slice modifications
761+    and labels each function as to whether or not it preserves ring-ness
762+    '''
763+    func_array = [ val for name, val in locals.items()
764+                    if hasattr(val, '__call__')
765+                    and name.startswith(name_pat) ]
766+
767+    for i in xrange(len(func_array)):
768+        a = range(length)
769+        a[-1] = a[0]
770+        func_array[i](a)
771+        ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
772+        func_array[i].ring = ring
773+
774+    return func_array
775+
776+def getcoords(o):
777+    if hasattr(o, 'coords'):
778+        return o.coords
779+    else:
780+        return o
781+
782
783Property changes on: tests/pymutable_geometries.py
784___________________________________________________________________
785Name: svn:executable
786   + *
787
Back to Top