/[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 1525 - (hide annotations)
Wed Jul 30 15:42:56 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 16477 byte(s)
Add docstrings. Rename specific Ramp instances to use lower_case_style.

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