/[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 1359 - (hide annotations)
Wed Jul 2 10:51:49 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: 14494 byte(s)
(generate_singletons,
        generate_uniform_distribution, generate_quantiles,
        GenQuantiles0): The denominator was one to high when
        calculating the index for the ramp causing the index
        to never to reach one.

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