/[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 1361 - (hide annotations)
Wed Jul 2 12:59:20 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: 14650 byte(s)
(generate_singletons,
        generate_uniform_distribution, generate_quantiles,
        GenQuantiles0): Make sure maxValue isn't less than
        one, otherwise we could divide by zero.

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 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 1157 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 1157 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     self.prop1 = prop1
381     self.prop2 = prop2
382    
383     def GetRamp(self):
384     return self
385    
386 jonathan 1335 def GetProperties(self, index):
387     """Return a ClassGroupProperties object whose properties
388     represent a point at 'index' between prop1 and prop2 in
389     the constructor.
390 jonathan 886
391 jonathan 1335 index -- a value such that 0 <= index <= 1
392     """
393 jonathan 886
394 jonathan 1335 if not (0 <= index <= 1):
395     raise ValueError(_("invalid index"))
396 jonathan 886
397 jonathan 1335 newProps = ClassGroupProperties()
398 jonathan 886
399 jonathan 1335 color1 = self.prop1.GetLineColor()
400     color2 = self.prop2.GetLineColor()
401 jonathan 886
402 jonathan 1335 self.__SetProperty(color1, color2, index, newProps.SetLineColor)
403     self.__SetProperty(color1, color2, index, newProps.SetFill)
404 jonathan 886
405 jonathan 1335 w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
406     * index \
407     + self.prop1.GetLineWidth()
408 jonathan 886
409 jonathan 1335 newProps.SetLineWidth(int(round(w)))
410 jonathan 886
411 jonathan 1335 return newProps
412 jonathan 886
413 jonathan 1335 def __SetProperty(self, color1, color2, index, setf):
414 jonathan 886
415 jonathan 1335 if color1 is Transparent and color2 is Transparent:
416     setf(Transparent)
417     elif color1 is Transparent:
418     setf(Color(
419     color2.red * index,
420     color2.green * index,
421     color2.blue * index))
422     elif color2 is Transparent:
423     setf(Color(
424     color1.red * index,
425     color1.green * index,
426     color1.blue * index))
427 jonathan 886 else:
428 jonathan 1335 setf(Color(
429     (color2.red - color1.red) * index + color1.red,
430     (color2.green - color1.green) * index + color1.green,
431     (color2.blue - color1.blue) * index + color1.blue))
432 jonathan 886
433     class MonochromaticRamp(CustomRamp):
434     def __init__(self, start, end):
435     sp = ClassGroupProperties()
436     sp.SetLineColor(start)
437     sp.SetFill(start)
438    
439     ep = ClassGroupProperties()
440     ep.SetLineColor(end)
441     ep.SetFill(end)
442    
443     CustomRamp.__init__(self, sp, ep)
444    
445 jonathan 1335 GreyRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, 0))
446     RedRamp = MonochromaticRamp(Color(1, 1, 1), Color(.8, 0, 0))
447     GreenRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, .8, 0))
448     BlueRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, .8))
449     GreenToRedRamp = MonochromaticRamp(Color(1, .8, 1), Color(1, 0, 0))
450 jonathan 886
451     class HotToColdRamp:
452    
453     def GetRamp(self):
454     return self
455    
456 jonathan 1335 def GetProperties(self, index):
457     """Return a ClassGroupProperties object whose properties
458     represent a point at 'index' between "hot" and "cold".
459 jonathan 886
460 jonathan 1335 index -- a value such that 0 <= index <= 1
461     """
462 jonathan 886
463     clr = [1.0, 1.0, 1.0]
464    
465 jonathan 1335 if index < .25:
466 jonathan 886 clr[0] = 0
467 jonathan 1335 clr[1] = 4 * index
468     elif index < .5:
469 jonathan 886 clr[0] = 0
470 jonathan 1335 clr[2] = 1 + 4 * (.25 - index)
471     elif index < .75:
472     clr[0] = 4 * (index - .5)
473 jonathan 886 clr[2] = 0
474     else:
475 jonathan 1335 clr[1] = 1 + 4 * (.75 - index)
476 jonathan 886 clr[2] = 0
477    
478     prop = ClassGroupProperties()
479     prop.SetLineColor(Color(clr[0], clr[1], clr[2]))
480     prop.SetFill(Color(clr[0], clr[1], clr[2]))
481    
482     return prop
483 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