/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/classgen.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/classgen.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 900 - (hide annotations)
Wed May 14 11:15:41 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 17164 byte(s)
(ClassGenerator.GenSingletonsFromList): Fix docstring.
(ClassGenerator.GenUniformDistribution): Fix spelling of method name.
(ClassGenerator.GenQuantiles): Use the left/right brackets and min/max
        values of the supplied range to determine the beginning and end
        bounds of the generated classes.

1 jonathan 886 # Copyright (c) 2003 by Intevation GmbH
2     # Authors:
3     # Jonathan Coles <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     import operator
9    
10     from color import Color
11     from range import Range
12     from classification import Classification, ClassGroupSingleton, \
13     ClassGroupRange, ClassGroupProperties
14    
15     class ClassGenerator:
16    
17 jonathan 895 def GenSingletonsFromList(self, _list, numGroups, ramp):
18 jonathan 886 """Generate a new classification consisting solely of singletons.
19    
20     The resulting classification will consist of at most 'numGroups'
21     groups whose group properties ramp between 'prop1' and 'prop2'. There
22 jonathan 895 could be fewer groups if '_list' contains fewer that 'numGroups' items.
23 jonathan 886
24 jonathan 895 _list -- any object that implements the iterator interface
25 jonathan 886
26     numGroups -- how many groups to generate. This can not be
27     determined while the classification is being
28     generated because the stepping values must
29     be precalculated to ramp between prop1 and prop2.
30    
31 jonathan 900 ramp -- an object which implements the CustomRamp interface
32 jonathan 886 """
33    
34     clazz = Classification()
35     if numGroups == 0: return clazz
36    
37     ramp.SetNumGroups(numGroups)
38    
39 jonathan 895 for value, prop in zip(_list, ramp):
40 jonathan 886 clazz.AppendGroup(ClassGroupSingleton(value, prop))
41    
42     return clazz
43    
44     def GenSingletons(self, min, max, numGroups, ramp):
45    
46     clazz = Classification()
47    
48     #step = int((max - min) / float(numGroups))
49    
50     if numGroups > 0:
51    
52     step = int((max - min + 1) / float(numGroups))
53     cur_value = min
54    
55     ramp.SetNumGroups(numGroups)
56    
57     for prop in ramp:
58     clazz.AppendGroup(ClassGroupSingleton(cur_value), prop)
59     cur_value += step
60    
61     return clazz
62    
63 jonathan 900 def GenUniformDistribution(self, min, max, numGroups,
64 jonathan 886 ramp, intStep = False):
65     """Generate a classification with numGroups range groups
66     each with the same interval.
67    
68     intStep -- force the calculated stepping to an integer.
69     Useful if the values are integers but the
70     number of groups specified doesn't evenly
71     divide (max - min).
72     """
73    
74     clazz = Classification()
75     if numGroups == 0: return clazz
76    
77     ramp.SetNumGroups(numGroups)
78    
79     step = (max - min) / float(numGroups)
80    
81     if intStep:
82     step = int(step)
83    
84     cur_min = min
85     cur_max = cur_min + step
86    
87     i = 0
88     end = "["
89     for prop in ramp:
90    
91     if i == (numGroups - 1):
92     cur_max = max
93     end = "]"
94    
95    
96     # this check guards against rounding issues
97     if cur_min != cur_max:
98     range = Range("[" + str(float(cur_min)) + ";" +
99     str(float(cur_max)) + end)
100     clazz.AppendGroup(ClassGroupRange(range, None, prop))
101    
102     cur_min = cur_max
103     cur_max += step
104     i += 1
105    
106     return clazz
107    
108    
109 jonathan 895 def GenQuantiles(self, _list, percents, ramp, _range):
110     """Generates a Classification which has groups of ranges that
111     represent quantiles of _list at the percentages given in percents.
112     Only the values that fall within _range are considered.
113    
114     Returns a tuple (adjusted, Classification) where adjusted is
115     True if the Classification does not exactly represent the given
116     range, or if the Classification is empty.
117    
118     _list -- a sort list of values
119    
120     percents -- a sorted list of floats in the range 0.0-1.0 which
121     represent the upper bound of each quantile
122    
123     ramp -- an object which implements the CustomRamp interface
124    
125     _range -- a Range object
126     """
127    
128 jonathan 886 clazz = Classification()
129 jonathan 895 quantiles = self.CalculateQuantiles(_list, percents, _range)
130     adjusted = True
131 jonathan 886
132 jonathan 895 if quantiles is not None:
133 jonathan 886
134 jonathan 895 numGroups = len(quantiles[3])
135 jonathan 886
136 jonathan 895 if numGroups != 0:
137 jonathan 886
138 jonathan 895 adjusted = quantiles[0]
139 jonathan 886
140 jonathan 895 ramp.SetNumGroups(numGroups)
141 jonathan 886
142 jonathan 900 start, min, endMax, right = _range.GetRange()
143    
144     if str(min) == '-inf':
145     min = "-oo"
146     elif str(min) == 'inf':
147     min = "oo"
148    
149     if str(endMax) == '-inf':
150     endMax = "-oo"
151     elif str(endMax) == 'inf':
152     endMax = "oo"
153    
154 jonathan 895 oldp = 0
155 jonathan 900 i = 1
156     end = "]"
157    
158 jonathan 895 for (q, p), prop in zip(quantiles[3], ramp):
159 jonathan 900 if i == numGroups:
160     max = endMax
161     end = right
162     else:
163     max = _list[q]
164    
165 jonathan 895 group = ClassGroupRange(Range(start + str(min) + ";" +
166 jonathan 900 str(max) + end),
167 jonathan 895 None, prop)
168    
169     group.SetLabel("%s%% - %s%%" % (round(oldp*100, 2),
170     round(p*100, 2)))
171     oldp = p
172     start = "]"
173     min = max
174     clazz.AppendGroup(group)
175 jonathan 900 i += 1
176 jonathan 895
177     return (adjusted, clazz)
178    
179     def CalculateQuantiles(self, _list, percents, _range):
180     """Calculate quantiles for the given _list of percents from the
181 jonathan 886 sorted list of values that are in range.
182    
183 jonathan 895 This may not actually generate len(percents) quantiles if
184 jonathan 886 many of the values that fall on quantile borders are the same.
185    
186 jonathan 895 Returns a tuple of the form:
187     (adjusted, minIndex, maxIndex, [quantile_list])
188 jonathan 886
189 jonathan 895 where adjusted is True if the the quantile percentages differ from
190     those supplied, minIndex is the index into _list where the
191     minimum value used is located, maxIndex is the index into _list
192     where the maximum value used is located, and quantile_list is a
193     list of tuples of the form: (list_index, quantile_percentage)
194    
195     Returns None, if no quantiles could be generated based on the
196     given range or input list.
197    
198     _list -- a sort list of values
199    
200     percents -- a sorted list of floats in the range 0.0-1.0 which
201     represent the upper bound of each quantile
202    
203     _range -- a Range object
204 jonathan 886 """
205    
206     quantiles = []
207     adjusted = False
208 jonathan 895
209 jonathan 886 if len(percents) != 0:
210    
211     #
212 jonathan 895 # find what part of the _list range covers
213 jonathan 886 #
214     minIndex = -1
215     maxIndex = -2
216 jonathan 895 for i in xrange(0, len(_list), 1):
217     if operator.contains(_range, _list[i]):
218 jonathan 886 minIndex = i
219     break
220    
221 jonathan 895 for i in xrange(len(_list)-1, -1, -1):
222     if operator.contains(_range, _list[i]):
223 jonathan 886 maxIndex = i
224 jonathan 895 break
225 jonathan 886
226     numValues = maxIndex - minIndex + 1
227    
228 jonathan 895 if numValues > 0:
229    
230 jonathan 886 #
231     # build a list of unique indices into list of where each
232 jonathan 891 # quantile *should* be. set adjusted if the resulting
233     # indices are different
234 jonathan 886 #
235     quantiles = {}
236     for p in percents:
237     index = min(minIndex + int(p*numValues)-1, maxIndex)
238    
239     adjusted = adjusted \
240     or quantiles.has_key(index) \
241 jonathan 891 or ((index - minIndex + 1) / float(numValues)) != p
242 jonathan 886
243     quantiles[index] = 0
244    
245     quantiles = quantiles.keys()
246     quantiles.sort()
247    
248     #
249     # the current quantile index must be strictly greater than
250     # the lowerBound
251     #
252     lowerBound = minIndex - 1
253    
254 jonathan 895 for qindex in xrange(len(quantiles)):
255 jonathan 886 if lowerBound >= maxIndex:
256     # discard higher quantiles
257     quantiles = quantiles[:qindex]
258     break
259    
260     # lowerBound + 1 is always a valid index
261    
262     #
263     # bump up the current quantile index to be a usable index
264     # if it currently falls below the lowerBound
265     #
266     if quantiles[qindex] <= lowerBound:
267 jonathan 895 quantiles[qindex] = lowerBound + 1
268 jonathan 886
269     listIndex = quantiles[qindex]
270 jonathan 895 value = _list[listIndex]
271 jonathan 886
272     #
273     # look for similar values around the quantile index
274     #
275     lindex = listIndex - 1
276 jonathan 895 while lindex > lowerBound and value == _list[lindex]:
277 jonathan 886 lindex -= 1
278 jonathan 895 lcount = (listIndex - 1) - lindex
279 jonathan 886
280     rindex = listIndex + 1
281 jonathan 895 while rindex < maxIndex + 1 and value == _list[rindex]:
282 jonathan 886 rindex += 1
283 jonathan 895 rcount = (listIndex + 1) - rindex
284 jonathan 886
285     #
286     # adjust the current quantile index based on how many
287 jonathan 895 # numbers in the _list are the same as the current value
288 jonathan 886 #
289     newIndex = listIndex
290     if lcount == rcount:
291     if lcount != 0:
292     #
293     # there are an equal number of numbers to the left
294     # and right, try going to the left first unless
295     # doing so creates an empty quantile.
296     #
297     if lindex != lowerBound:
298     newIndex = lindex
299     else:
300     newIndex = rindex - 1
301    
302     elif lcount < rcount:
303     # there are fewer items to the left, so
304     # try going to the left first unless
305     # doing so creates an empty quantile.
306     if lindex != lowerBound:
307     newIndex = lindex
308     else:
309     newIndex = rindex - 1
310    
311     elif rcount < lcount:
312     # there are fewer items to the right, so go to the right
313     newIndex = rindex - 1
314    
315 jonathan 895 adjusted = adjusted or newIndex != listIndex
316    
317 jonathan 886 quantiles[qindex] = newIndex
318     lowerBound = quantiles[qindex]
319    
320 jonathan 891 #
321     # since quantiles is only set if the code is at least a little
322     # successful, an empty list will be generated in the case that
323     # we fail to get to the real body of the algorithm
324     #
325 jonathan 895 if len(quantiles) == 0:
326     return None
327     else:
328     return (adjusted, minIndex, maxIndex,
329     [(q, (q - minIndex+1) / float(numValues)) \
330     for q in quantiles])
331 jonathan 886
332     CLR = 0
333     STEP = 1
334     class CustomRamp:
335    
336     def __init__(self, prop1, prop2):
337     self.prop1 = prop1
338     self.prop2 = prop2
339    
340     self.count = 0
341    
342     def __iter__(self):
343     return self
344    
345     def GetRamp(self):
346     return self
347    
348     def SetNumGroups(self, num):
349    
350     if num <= 0:
351     return False
352    
353     self.count = int(num)
354     num = float(num)
355    
356     prop1 = self.prop1
357     prop2 = self.prop2
358    
359     clr = prop1.GetLineColor()
360     lineColor2 = prop2.GetLineColor()
361    
362     self.noLine = clr is not Color.Transparent \
363     and lineColor2 is not Color.Transparent
364    
365    
366     self.lineInfo = self.__GetColorInfo(prop1.GetLineColor(),
367     prop2.GetLineColor(),
368     num)
369    
370     self.fillInfo = self.__GetColorInfo(prop1.GetFill(),
371     prop2.GetFill(),
372     num)
373    
374     self.lineWidth = prop1.GetLineWidth()
375     self.lineWidthStep = (prop2.GetLineWidth() - self.lineWidth) / num
376    
377     return True
378    
379     def next(self):
380     if self.count == 0:
381     raise StopIteration
382    
383     prop = ClassGroupProperties()
384    
385     if self.lineInfo is None:
386     prop.SetLineColor(Color.Transparent)
387     else:
388     prop.SetLineColor(Color(self.lineInfo[CLR][0] / 255,
389     self.lineInfo[CLR][1] / 255,
390     self.lineInfo[CLR][2] / 255))
391    
392     self.lineInfo[CLR][0] += self.lineInfo[STEP][0]
393     self.lineInfo[CLR][1] += self.lineInfo[STEP][1]
394     self.lineInfo[CLR][2] += self.lineInfo[STEP][2]
395    
396     if self.fillInfo is None:
397     prop.SetFill(Color.Transparent)
398     else:
399     prop.SetFill(Color(self.fillInfo[CLR][0] / 255,
400     self.fillInfo[CLR][1] / 255,
401     self.fillInfo[CLR][2] / 255))
402    
403     self.fillInfo[CLR][0] += self.fillInfo[STEP][0]
404     self.fillInfo[CLR][1] += self.fillInfo[STEP][1]
405     self.fillInfo[CLR][2] += self.fillInfo[STEP][2]
406    
407    
408     prop.SetLineWidth(int(self.lineWidth))
409     self.lineWidth += self.lineWidthStep
410    
411     self.count -= 1
412    
413     return prop
414    
415     def __GetColorInfo(self, color1, color2, numGroups):
416    
417     if color1 is Color.Transparent and color2 is Color.Transparent:
418     #
419     # returning early
420     #
421     return None
422     elif color1 is not Color.Transparent and color2 is Color.Transparent:
423     color = [color1.red * 255,
424     color1.green * 255,
425     color1.blue * 255]
426     step = (0, 0, 0)
427     elif color1 is Color.Transparent and color2 is not Color.Transparent:
428     color = [color2.red * 255,
429     color2.green * 255,
430     color2.blue * 255]
431     step = (0, 0, 0)
432     else:
433     color = [color1.red * 255,
434     color1.green * 255,
435     color1.blue * 255]
436     step = ((color2.red * 255 - color1.red * 255) / numGroups,
437     (color2.green * 255 - color1.green * 255) / numGroups,
438     (color2.blue * 255 - color1.blue * 255) / numGroups)
439    
440    
441     return (color, step)
442    
443     class MonochromaticRamp(CustomRamp):
444     def __init__(self, start, end):
445     sp = ClassGroupProperties()
446     sp.SetLineColor(start)
447     sp.SetFill(start)
448    
449     ep = ClassGroupProperties()
450     ep.SetLineColor(end)
451     ep.SetFill(end)
452    
453     CustomRamp.__init__(self, sp, ep)
454    
455     class GreyRamp(MonochromaticRamp):
456     def __init__(self):
457     MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, 0, 0))
458    
459     class RedRamp(MonochromaticRamp):
460     def __init__(self):
461     MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(.8, 0, 0))
462    
463     class GreenRamp(MonochromaticRamp):
464     def __init__(self):
465     MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, .8, 0))
466    
467     class BlueRamp(MonochromaticRamp):
468     def __init__(self):
469     MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, 0, .8))
470    
471     class GreenToRedRamp(MonochromaticRamp):
472     def __init__(self):
473     MonochromaticRamp.__init__(self, Color(0, .8, 0), Color(1, 0, 0))
474    
475     class HotToColdRamp:
476    
477     def __iter__(self):
478     return self
479    
480     def GetRamp(self):
481     return self
482    
483     def SetNumGroups(self, num):
484     if num < 0:
485     return False
486    
487     self.num = float(num)
488     self.index = 0
489    
490     return True
491    
492     def next(self):
493     if self.index == self.num:
494     raise StopIteration
495    
496     clr = [1.0, 1.0, 1.0]
497    
498     if self.index < (.25 * self.num):
499     clr[0] = 0
500     clr[1] = 4 * self.index / self.num
501     elif self.index < (.5 * self.num):
502     clr[0] = 0
503     clr[2] = 1 + 4 * (.25 * self.num - self.index) / self.num
504     elif self.index < (.75 * self.num):
505     clr[0] = 4 * (self.index - .5 * self.num) / self.num
506     clr[2] = 0
507     else:
508     clr[1] = 1 + 4 * (.75 * self.num - self.index) / self.num
509     clr[2] = 0
510    
511     self.index += 1
512    
513     prop = ClassGroupProperties()
514     prop.SetLineColor(Color(clr[0], clr[1], clr[2]))
515     prop.SetFill(Color(clr[0], clr[1], clr[2]))
516    
517     return prop
518    
519     #class Colors16Ramp:
520     #
521     #def __iter__(self):
522     #return self
523     #
524     #def GetRamp(self):
525     #return self
526     #
527     #def SetNumGroups(self, num):
528     #if num < 0:
529     #return False
530     #
531     #self.index = 0
532     #
533     #return True

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26