Ticket #9877: pythonic_mutation_patch_part_II

File pythonic_mutation_patch_part_II, 28.5 KB (added by Aryeh Leib Taurog <vim@…>, 15 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