/[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 1157 - (hide annotations)
Thu Jun 12 12:39:54 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 17065 byte(s)
Renamed functions to follow the
        function_names_with_underscores style. Fixes RTbug #1903.
(calculate_quantiles): Raise ValueError if 'percents' is invalid.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26