/[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 1759 - (hide annotations)
Fri Sep 26 18:36:01 2003 UTC (21 years, 5 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14517 byte(s)
(GenQuantiles0): Removed since it's
only used in GREAT-ER but not used in Thuban itself. When GREAT-ER
is ported to a newer the import will fail, so it should be noticed
immediately that this function is gone.
Fixes RT#1919

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26