/[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 2441 - (hide annotations)
Thu Dec 9 10:50:34 2004 UTC (20 years, 3 months ago) by joey
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14779 byte(s)
Added missing character encoding so that Python doesn't barf upon
evaluating this file

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