Ticket #9877: pythonic_mutation_patch_part_I.2

File pythonic_mutation_patch_part_I.2, 19.3 KB (added by Aryeh Leib Taurog <vim@…>, 16 years ago)

This is an updated patch to Django 1.0.2 release

Line 
1Index: geos/geometries.py
2===================================================================
3--- geos/geometries.py (revision 2)
4+++ geos/geometries.py (revision 8)
5@@ -156,10 +156,12 @@
6
7 # Getting the correct initialization function
8 if kwargs.get('ring', False):
9- func = create_linearring
10+ self._init_func = create_linearring
11 else:
12- func = create_linestring
13+ self._init_func = create_linestring
14
15+ func = self._init_func
16+
17 # If SRID was passed in with the keyword arguments
18 srid = kwargs.get('srid', None)
19
20@@ -168,13 +170,152 @@
21 super(LineString, self).__init__(func(cs.ptr), srid=srid)
22
23 def __getitem__(self, index):
24- "Gets the point at the specified index."
25- return self._cs[index]
26+ "Gets the coordinates of the point(s) at the specified index/slice."
27+ if isinstance(index, slice):
28+ return [self._cs[i] for i in xrange(*index.indices(len(self._cs)))]
29+ else:
30+ if index < 0:
31+ index += len(self._cs)
32+ return self._cs[index]
33
34+ def __delitem__(self, index):
35+ "Delete the point(s) at the specified index/slice."
36+ if not isinstance(index, (int, long, slice)):
37+ raise TypeError("%s is not a legal index" % index)
38+
39+ # calculate new length and dimensions
40+ origLen = len(self._cs)
41+ if isinstance(index, (int, long)):
42+ if index < 0: index += origLen
43+ if index < 0 or origLen <= index:
44+ raise GEOSIndexError('invalid GEOS Geometry index: %d' % index)
45+ indexRange = [index]
46+ else:
47+ indexRange = range(*index.indices(origLen))
48+
49+ newLen = origLen - len(indexRange)
50+ ndim = self._cs.dims
51+ hasz = self._cs.hasz # I don't understand why these are different
52+
53+ # create a new coordinate sequence and populate accordingly
54+ cs = GEOSCoordSeq(create_cs(newLen, ndim), z=hasz)
55+ new_i = 0
56+ for orig_i in xrange(origLen):
57+ if orig_i in indexRange: continue
58+ cs[new_i] = self._cs[orig_i]
59+ new_i += 1
60+
61+ ptr = self._init_func(cs.ptr)
62+ if ptr:
63+ destroy_geom(self.ptr)
64+ self._ptr = ptr
65+ self._post_init(self.srid)
66+ else:
67+ # can this happen?
68+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
69+
70 def __setitem__(self, index, value):
71- "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
72- self._cs[index] = value
73+ """Sets the point(s) at the specified index/slice,
74+ e.g., line_str[0] = (1, 2)."""
75+ if isinstance(index, slice):
76+ try:
77+ valueIter = iter(value)
78+ except TypeError:
79+ raise TypeError('can only assign an iterable')
80
81+ # calculate length and dimensions
82+ origLen = len(self._cs)
83+ valueList = list(value)
84+ start, stop, step = index.indices(origLen)
85+ stop = max(0, stop) # stop will be -1 if out-of-bounds
86+ # negative index is given
87+
88+ # CAREFUL: index.step and step are not the same!
89+ # step will never be None
90+ #
91+ if index.step is None:
92+ # this is a simple slice, can assign slice of any length
93+ # calculate new length
94+ newLen = origLen - stop + start + len(valueList)
95+ ndim = self._cs.dims
96+ hasz = self._cs.hasz # not sure why these are different
97+
98+ # create a new coordinate sequence and populate accordingly
99+ cs = GEOSCoordSeq(create_cs(newLen, ndim), z=hasz)
100+ new_i = 0
101+ for orig_i in xrange(origLen + 1):
102+ if orig_i == start:
103+ for val in valueList:
104+ cs[new_i] = val
105+ new_i += 1
106+
107+ if orig_i < origLen:
108+ if orig_i < start or orig_i >= stop:
109+ cs[new_i] = self._cs[orig_i]
110+ new_i += 1
111+
112+ ptr = self._init_func(cs.ptr)
113+ # Polygon.__setitem__ doesn't check this, but it seems
114+ # that it can't hurt.
115+ if ptr:
116+ destroy_geom(self.ptr)
117+ self._ptr = ptr
118+ self._post_init(self.srid)
119+ else:
120+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
121+
122+ else:
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+ # we're not changing the length of the sequence
131+ # we can just iterate the indices and value and set them
132+ for i, val in zip(indexList , valueList):
133+ self._cs[i] = val
134+
135+ else:
136+ length = len(self._cs)
137+ if index < 0: index += length
138+ self._cs[index] = value
139+
140+ def append(self, val):
141+ "Standard list append method"
142+ self[len(self):] = [val]
143+
144+ def extend(self, vals):
145+ "Standard list extend method"
146+ self[len(self):] = vals
147+
148+ def insert(self, index, val):
149+ "Standard list insert method"
150+ if not isinstance(index, (int, long)):
151+ raise TypeError("%s is not a legal index" % index)
152+ self[index:index] = [val]
153+
154+ def pop(self, index=-1):
155+ "Standard list pop method"
156+ result = self[index]
157+ del self[index]
158+ return result
159+
160+ def index(self, val):
161+ "Standard list index method"
162+ for i in xrange(0, len(self)):
163+ if self[i] == val: return i
164+ raise ValueError('%s not in geometry' % str(val))
165+
166+ def remove(self, val):
167+ "Standard list remove method"
168+ del self[self.index(val)]
169+
170+ def count(self):
171+ "Standard list count method"
172+ return len(self)
173+
174 def __iter__(self):
175 "Allows iteration over this LineString."
176 for i in xrange(len(self)):
177Index: tests/__init__.py
178===================================================================
179--- tests/__init__.py (revision 2)
180+++ tests/__init__.py (revision 8)
181@@ -18,6 +18,7 @@
182 # Tests that do not require setting up and tearing down a spatial database.
183 test_suite_names = [
184 'test_geos',
185+ 'test_geos_pymutable',
186 'test_measure',
187 ]
188 if HAS_GDAL:
189Index: tests/test_geos_pymutable.py
190===================================================================
191--- tests/test_geos_pymutable.py (revision 0)
192+++ tests/test_geos_pymutable.py (revision 8)
193@@ -0,0 +1,199 @@
194+import unittest
195+from pymutable_geometries import *
196+from django.contrib.gis.geos.error import GEOSIndexError
197+
198+class GEOSPyMutableTest(unittest.TestCase):
199+ '''
200+ Tests Pythonic Mutability of Python GEOS geometry wrappers
201+ get/set/delitem on a slice, normal list methods
202+ '''
203+
204+ def test01_getslice(self):
205+ 'Test getting a slice from a geometry'
206+ for f in getslice_functions():
207+ for g in slice_geometries():
208+ self.assertEqual(f(g.coords), f(g.geom), f.__name__)
209+
210+ def test02_getitem(self):
211+ 'Test getting a single item from a geometry'
212+ for g in slice_geometries():
213+ for i in seqrange():
214+ self.assertEqual(g.coords[i], g.geom[i])
215+
216+ def test03_getitem_indexException(self):
217+ 'Test get single item with out-of-bounds index'
218+ for g in slice_geometries():
219+ for i in SEQ_OUT_OF_BOUNDS:
220+ self.assertRaises(GEOSIndexError, lambda: g.geom[i])
221+
222+ def test04_delitem_single(self):
223+ 'Test delete single item from a geometry'
224+ for i in seqrange():
225+ for g in slice_geometries():
226+ if g.geom.ring and i in SEQ_BOUNDS: continue
227+ del g.coords[i]
228+ del g.geom[i]
229+ self.assertEqual(g.coords , g.geom[:])
230+
231+ def test05_delitem_slice(self):
232+ 'Test delete slice from a geometry'
233+ for f in delslice_functions():
234+ for g in slice_geometries():
235+ if g.geom.ring and not f.ring: continue
236+ f(g.coords)
237+ f(g.geom)
238+ self.assertEqual(g.coords , g.geom[:], f.__name__)
239+
240+ def test06_delitem_single_indexException(self):
241+ 'Test delete single item with out-of-bounds index'
242+ def func(x, i): del x[i]
243+ for g in slice_geometries():
244+ for i in SEQ_OUT_OF_BOUNDS:
245+ self.assertRaises(GEOSIndexError, func, g.geom, i)
246+
247+ def test07_setitem_single(self):
248+ "Test set single item (make sure we didn't break this)"
249+ for i in seqrange():
250+ for g in slice_geometries():
251+ if g.geom.ring and i in SEQ_BOUNDS: continue
252+ g.coords[i] = (3.14159, 3.14159)
253+ g.geom[i] = (3.14159, 3.14159)
254+ self.assertEqual(g.coords , g.geom[:])
255+
256+ def test08_setslice_simple(self):
257+ 'Test setting a simple slice of a geometry'
258+ for f in setslice_simple_functions():
259+ for g in slice_geometries():
260+ if g.geom.ring and not f.ring: continue
261+ f(g.coords)
262+ f(g.geom)
263+ self.assertEqual(g.coords , g.geom[:], f.__name__)
264+
265+ def test09_setslice_extended(self):
266+ 'Test setting an extended slice of a geometry'
267+ for f in setslice_extended_functions():
268+ for g in slice_geometries():
269+ if g.geom.ring and not f.ring: continue
270+ f(g.coords)
271+ f(g.geom)
272+ self.assertEqual(g.coords , g.geom[:], f.__name__)
273+
274+ def test10_setslice_extended_mismatched(self):
275+ 'Test setting extended slice with array of mismatched length'
276+ def func(x): x[2:8:2] = [(3.1415, 3.1415)]
277+ for g in slice_geometries():
278+ self.assertRaises(ValueError, func, g.geom)
279+
280+ def test11_setitem_single_indexException(self):
281+ 'Test set single item with out-of-bounds index'
282+ def func(x, i): x[i] = (3.1415, 3.1415)
283+ for g in slice_geometries():
284+ for i in SEQ_OUT_OF_BOUNDS:
285+ self.assertRaises(GEOSIndexError, func, g.geom, i)
286+
287+ def test12_append(self):
288+ 'Test list method append'
289+ for g in slice_geometries():
290+ if g.geom.ring: continue
291+ g.geom.append((200.0,200.0))
292+ g.coords.append((200.0,200.0))
293+ self.assertEqual(g.coords , g.geom[:])
294+
295+ def test13_extend(self):
296+ 'Test list method extend'
297+ for g in slice_geometries():
298+ points = random_coords(5)
299+ if g.geom.ring: points[-1] = g.coords[0]
300+ g.geom.extend(points)
301+ g.coords.extend(points)
302+ self.assertEqual(g.coords , g.geom[:])
303+
304+ def test14_insert(self):
305+ 'Test list method insert'
306+ for i in xrange(*SEQ_OUT_OF_BOUNDS):
307+ for g in slice_geometries():
308+ if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
309+ continue
310+ g.geom.insert(i, (3141.5, 3141.5))
311+ g.coords.insert(i, (3141.5, 3141.5))
312+ self.assertEqual(g.coords , g.geom[:])
313+
314+ def test15_insert_typeError(self):
315+ 'Test list method insert raises error on invalid index'
316+ for g in slice_geometries():
317+ self.assertRaises(TypeError, g.geom.insert,
318+ 'hi', (3141.5, 3141.5))
319+
320+ def test16_pop(self):
321+ 'Test list method pop'
322+ for i in seqrange():
323+ for g in slice_geometries():
324+ if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
325+ continue
326+ self.assertEqual(g.coords.pop(i), g.geom.pop(i))
327+
328+ def test16_index(self):
329+ 'Test list method index'
330+ for i in xrange(0, SEQ_LENGTH):
331+ for g in slice_geometries():
332+ if g.geom.ring and i in SEQ_BOUNDS: continue
333+ p = (200.0, 200.0)
334+ g.geom[i] = p
335+ self.assertEqual(i, g.geom.index(p))
336+
337+ def test17_index_ValueError(self):
338+ 'Test list method raises ValueError if value not found'
339+ for g in slice_geometries():
340+ self.assertRaises(ValueError, g.geom.index, (200.0,200.0))
341+
342+ def test18_remove(self):
343+ 'Test list method remove'
344+ for i in xrange(0, SEQ_LENGTH):
345+ for g in slice_geometries():
346+ if g.geom.ring and i in SEQ_BOUNDS: continue
347+ p = (200.0, 200.0)
348+ g.geom[i] = p
349+ g.coords[i] = p
350+ g.geom.remove(p)
351+ g.coords.remove(p)
352+ self.assertEqual(g.coords, g.geom[:])
353+
354+ def test19_count(self):
355+ 'Test list method count'
356+ for g in slice_geometries():
357+ self.assertEqual(SEQ_LENGTH, g.geom.count())
358+
359+ def test20_setslice_geos_fcns(self):
360+ 'Test geos properties after setting a simple slice of a geometry'
361+ for f in setslice_simple_functions():
362+ for g in slice_geometries():
363+ if g.geom.ring: continue
364+ f(g.coords)
365+ f(g.geom)
366+ if not len(g.coords): continue
367+ for tf in test_geos_functions():
368+ cg = g.make_geom()
369+ self.assertEqual(tf(cg) , tf(g.geom))
370+
371+ def test21_delslice_geos_fcns(self):
372+ 'Test geos properties after deleting a slice of a geometry'
373+ for f in delslice_functions():
374+ for g in slice_geometries():
375+ if g.geom.ring: continue
376+ f(g.coords)
377+ f(g.geom)
378+ if not len(g.coords): continue
379+ for tf in test_geos_functions():
380+ cg = g.make_geom()
381+ self.assertEqual(tf(cg) , tf(g.geom))
382+
383+def suite():
384+ s = unittest.TestSuite()
385+ s.addTest(unittest.makeSuite(GEOSPyMutableTest))
386+ return s
387+
388+def run(verbosity=2):
389+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
390+
391+if __name__ == '__main__':
392+ run()
393
394Property changes on: tests/test_geos_pymutable.py
395___________________________________________________________________
396Name: svn:executable
397 + *
398
399Index: tests/pymutable_geometries.py
400===================================================================
401--- tests/pymutable_geometries.py (revision 0)
402+++ tests/pymutable_geometries.py (revision 8)
403@@ -0,0 +1,146 @@
404+from django.contrib.gis.geos import *
405+from random import random
406+
407+SEQ_LENGTH = 10
408+SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
409+SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
410+SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
411+
412+def seqrange(): return xrange(*SEQ_RANGE)
413+
414+class PyMutTestGeom:
415+ "The Test Geometry class container."
416+ def __init__(self, geom_type, coords, **kwargs):
417+ self.geom_type = geom_type
418+ self.coords = coords
419+ self.geom = self.make_geom()
420+
421+ def make_geom(self):
422+ return self.geom_type(self.coords)
423+
424+def random_coords(length = SEQ_LENGTH,
425+ dim = 2, # coordinate dimensions
426+ rng = (-50,50), # coordinate range
427+ type = float,
428+ ring = False,
429+ round_coords = True):
430+
431+ if round_coords:
432+ num = lambda: type(round(random() * (rng[1]-rng[0]) + rng[0]))
433+ else:
434+ num = lambda: type(random() * (rng[1]-rng[0]) + rng[0])
435+
436+ result = [ tuple( num() for axis in xrange(dim) )
437+ for index in xrange(length) ]
438+ if ring:
439+ result[-1] = result[0]
440+
441+ return result
442+
443+
444+def slice_geometries():
445+ return (
446+ PyMutTestGeom(LineString, random_coords()),
447+ PyMutTestGeom(LinearRing, random_coords(ring=True)),
448+ )
449+
450+def getslice_functions():
451+ return (
452+ lambda x: x[0:4],
453+ lambda x: x[5:-1],
454+ lambda x: x[6:2:-1],
455+ lambda x: x[:],
456+ lambda x: x[:3],
457+ lambda x: x[::2],
458+ lambda x: x[::-4],
459+ lambda x: x[7:7],
460+ lambda x: x[20:],
461+ )
462+
463+def delslice_functions():
464+ def ds_01(x): del x[0:4]
465+ def ds_02(x): del x[5:-1]
466+ def ds_03(x): del x[6:2:-1]
467+ def ds_04(x): del x[:] # should this be allowed?
468+ def ds_05(x): del x[:3]
469+ def ds_06(x): del x[1:9:2]
470+ def ds_07(x): del x[::-4]
471+ def ds_08(x): del x[7:7]
472+ def ds_09(x): del x[-7:-2]
473+
474+ return mark_ring(vars(), 'ds_')
475+
476+def setslice_extended_functions():
477+ a = [(1.0,2.0),(1.0,2.0),(1.0,2.0)]
478+ def sse_00(x): x[:3:1] = a
479+ def sse_01(x): x[0:3:1] = a
480+ def sse_02(x): x[2:5:1] = a
481+ def sse_03(x): x[-3::1] = a
482+ def sse_04(x): x[-4:-1:1] = a
483+ def sse_05(x): x[8:5:-1] = a
484+ def sse_06(x): x[-6:-9:-1] = a
485+ def sse_07(x): x[:8:3] = a
486+ def sse_08(x): x[1::3] = a
487+ def sse_09(x): x[-2::-3] = a
488+ def sse_10(x): x[7:1:-2] = a
489+ def sse_11(x): x[2:8:2] = a
490+
491+ return mark_ring(vars(), 'sse_')
492+
493+def setslice_simple_functions():
494+ a = [(1.0,2.0),(2.0,1.0),(2.0,2.0)]
495+ def ss_00(x): x[:0] = a
496+ def ss_01(x): x[:1] = a
497+ def ss_02(x): x[:2] = a
498+ def ss_03(x): x[:3] = a
499+ def ss_04(x): x[-4:] = a
500+ def ss_05(x): x[-3:] = a
501+ def ss_06(x): x[-2:] = a
502+ def ss_07(x): x[-1:] = a
503+ def ss_08(x): x[5:] = a
504+ def ss_09(x): x[:] = a
505+ def ss_10(x): x[4:4] = a
506+ def ss_11(x): x[4:5] = a
507+ def ss_12(x): x[4:7] = a
508+ def ss_13(x): x[4:8] = a
509+ def ss_14(x): x[20:30] = a
510+ def ss_15(x): x[-13:-8] = a
511+ def ss_16(x): x[-13:-9] = a
512+ def ss_17(x): x[-13:-10] = a
513+ def ss_18(x): x[-13:-11] = a
514+ def ss_19(x): x[10:] = a
515+
516+ return mark_ring(vars(), 'ss_')
517+
518+def test_geos_functions():
519+
520+ return (
521+ lambda x: x.num_coords,
522+ lambda x: x.empty,
523+ lambda x: x.valid,
524+ lambda x: x.simple,
525+ lambda x: x.ring,
526+ lambda x: x.boundary,
527+ lambda x: x.convex_hull,
528+ lambda x: x.extend,
529+ lambda x: x.area,
530+ lambda x: x.length,
531+ )
532+
533+def mark_ring(locals, name_pat, length=SEQ_LENGTH):
534+ '''
535+ Accepts an array of functions which perform slice modifications
536+ and labels each function as to whether or not it preserves ring-ness
537+ '''
538+ func_array = [ val for name, val in locals.items()
539+ if hasattr(val, '__call__')
540+ and name.startswith(name_pat) ]
541+
542+ for i in xrange(len(func_array)):
543+ a = range(length)
544+ a[-1] = a[0]
545+ func_array[i](a)
546+ ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
547+ func_array[i].ring = ring
548+
549+ return func_array
550
551Property changes on: tests/pymutable_geometries.py
552___________________________________________________________________
553Name: svn:executable
554 + *
555
Back to Top