/[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 1387 - (hide annotations)
Thu Jul 10 14:53:03 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: 15914 byte(s)
(generate_singletons,
        generate_uniform_distribution, generate_quantiles):
        Added 'fixes' parameter so that property attributes can
        be held constant over the generated classification groups.
(CustomRamp.GetProperties): Remove unused variables.

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