/[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 895 - (hide annotations)
Mon May 12 11:21:02 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: 16679 byte(s)
(ClassGenerator.GenQuantiles): Add comments describing the parameters,
        use new return values from CalculateQuantiles to produce the correct
        range bounds in the Classification.
(ClassGenerator.CalculateQuantiles): Add more comments describing
        the return values and parameters. Make minor adjustments to improve
        the legibility of the code. Fix problem with adjusted not being set
        in most cases.

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