/[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 2385 - (hide annotations)
Thu Oct 7 14:28:51 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14746 byte(s)
(CustomRamp.GetProperties): Added size gradient.

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