/[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 1335 - (hide annotations)
Tue Jul 1 16:09:09 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: 14457 byte(s)
Fixes RTbug #1972, 1971.
        Import used objects/class from color.
(generate_singletons): We don't
        need the numGroups parameter anymore because we are using
        the new ramps with GetProperties().
(generate_uniform_distribution): Use new ramp method
        GetProperties().
(generate_quantiles, GenQuantiles0): Use new ramp method
        GetProperties().
(CustomRamp.SetNumGroups): Removed. The ramps now map
        a value from 0 to 1 to class properties so the number
        of groups is not needed ahead of time.
(CustomRamp.next): Removed. CustomRamp does not support
        interation anymore.
(CustomRamp.GetProperties): Returns a ClassGroupProperties
        object based on the index value from 0 to 1 that is
        passed to it.
(GreyRamp, RedRamp, GreenRamp, BlueRamp, GreenToRedRamp):
        Made into instances of Monochromatic class instread of
        deriving from it.
(HotToCold.SetNumGroups): Removed. See CustomRamp.
(HotToCold.next): Removed. See CustomRamp.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26