/[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 1352 - (hide annotations)
Wed Jul 2 09:36:39 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: 14359 byte(s)
Fixed parameters to ClassGroupRange constructor.

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