/[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 986 - (hide annotations)
Thu May 22 16:41:10 2003 UTC (21 years, 9 months ago) by tkoester
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 16794 byte(s)
* Thuban/Model/classgen.py: Added short module doc string and CVS id.
(ClassGenerator.GenUniformDistribution): Use new Range __init__, too.

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