/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/classgen.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/Model/classgen.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1359 - (show 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 # 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 """
9 Functions to generate Classifications
10 """
11
12 __version__ = "$Revision$"
13 # $Source$
14 # $Id$
15
16 import operator
17
18 from color import Color, Transparent
19 from range import Range
20 from classification import Classification, ClassGroupSingleton, \
21 ClassGroupRange, ClassGroupProperties
22
23 def generate_singletons(_list, ramp):
24 """Generate a new classification consisting solely of singletons.
25
26 The resulting classification will consist of one group for each
27 item in _list whose properties ramp between 'prop1' and 'prop2'.
28
29 _list -- any object that implements the iterator interface
30
31 ramp -- an object which implements the CustomRamp interface
32 """
33
34 clazz = Classification()
35
36 i = 0
37 maxValue = float(len(_list) - 1)
38 for value in _list:
39 prop = ramp.GetProperties(i / maxValue)
40 clazz.AppendGroup(ClassGroupSingleton(value, prop))
41 i += 1
42
43 return clazz
44
45 def generate_uniform_distribution(min, max, numGroups, ramp, intStep = False):
46 """Generate a classification with numGroups range groups
47 each with the same interval.
48
49 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
55 clazz = Classification()
56
57 cur_min = min
58
59 end = "["
60 maxValue = float(numGroups - 1)
61 for i in range(1, numGroups + 1):
62
63 prop = ramp.GetProperties(float(i-1) / maxValue)
64
65 if intStep:
66 cur_max = min + int(round((i * (max - min + 1)) / maxValue))
67 else:
68 cur_max = min + (i * (max - min)) / maxValue
69
70 if i == numGroups:
71 cur_max = max
72 end = "]"
73
74 if cur_min == cur_max:
75 _range = Range(("[", cur_min, cur_max, "]"))
76 else:
77 _range = Range(("[", cur_min, cur_max, end))
78
79 clazz.AppendGroup(ClassGroupRange(_range, prop))
80
81 cur_min = cur_max
82
83 return clazz
84
85 def generate_quantiles(_list, percents, ramp, _range):
86 """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
90 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
94 _list -- a sort list of values
95
96 percents -- a sorted list of floats in the range 0.0-1.0 which
97 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
101 ramp -- an object which implements the CustomRamp interface
102
103 _range -- a Range object
104
105 Raises a Value Error if 'percents' has fewer than two items, or
106 does not cover the entire range.
107 """
108
109 clazz = Classification()
110 quantiles = calculate_quantiles(_list, percents, _range)
111 adjusted = True
112
113 if quantiles is not None:
114
115 numGroups = len(quantiles[3])
116
117 if numGroups != 0:
118
119 adjusted = quantiles[0]
120
121 start, min, endMax, right = _range.GetRange()
122
123 oldp = 0
124 i = 1
125 end = "]"
126
127 maxValue = float(numGroups - 1)
128 for (q, p) in quantiles[3]:
129
130 prop = ramp.GetProperties(float(i-1) / maxValue)
131
132 if i == numGroups:
133 max = endMax
134 end = right
135 else:
136 max = _list[q]
137
138 group = ClassGroupRange(Range((start, min, max, end)), prop)
139
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
148 return (adjusted, clazz)
149
150 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 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
164 ramp -- an object which implements the CustomRamp interface
165
166 _range -- a Range object
167
168 Raises a Value Error if 'percents' has fewer than two items, or
169 does not cover the entire range.
170 """
171
172 clazz = Classification()
173 quantiles = calculate_quantiles(_list, percents, _range)
174 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 maxValue = float(numGroups - 1)
192 for (q, p) in quantiles[3][1:]:
193 prop = ramp.GetProperties(float(i-1) / maxValue)
194
195 if i == numGroups:
196 max = endMax
197 end = right
198 else:
199 max = _list[q]
200
201 group = ClassGroupRange(Range((start, min, max, end)), prop)
202
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 def calculate_quantiles(_list, percents, _range):
215 """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
221 Returns a tuple of the form:
222 (adjusted, minIndex, maxIndex, [quantile_list])
223
224 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
230 Returns None, if no quantiles could be generated based on the
231 given range or input list.
232
233 _list -- a sort list of values
234
235 percents -- a sorted list of floats in the range 0.0-1.0 which
236 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
240 _range -- a Range object
241
242 Raises a Value Error if 'percents' has fewer than two items, or
243 does not cover the entire range.
244 """
245
246 quantiles = []
247 adjusted = False
248
249 if len(percents) <= 1:
250 raise ValueError("percents parameter must have more than one item")
251
252 if percents[-1] != 1.0:
253 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 #
275 # 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 #
279 quantiles = {}
280 for p in percents:
281 index = min(minIndex + int(p*numValues)-1, maxIndex)
282
283 adjusted = adjusted \
284 or quantiles.has_key(index) \
285 or ((index - minIndex + 1) / float(numValues)) != p
286
287 quantiles[index] = 0
288
289 quantiles = quantiles.keys()
290 quantiles.sort()
291
292 #
293 # the current quantile index must be strictly greater than
294 # the lowerBound
295 #
296 lowerBound = minIndex - 1
297
298 for qindex in xrange(len(quantiles)):
299 if lowerBound >= maxIndex:
300 # discard higher quantiles
301 quantiles = quantiles[:qindex]
302 break
303
304 # lowerBound + 1 is always a valid index
305
306 #
307 # bump up the current quantile index to be a usable index
308 # if it currently falls below the lowerBound
309 #
310 if quantiles[qindex] <= lowerBound:
311 quantiles[qindex] = lowerBound + 1
312
313 listIndex = quantiles[qindex]
314 value = _list[listIndex]
315
316 #
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
324 rindex = listIndex + 1
325 while rindex < maxIndex + 1 and value == _list[rindex]:
326 rindex += 1
327 rcount = (listIndex + 1) - rindex
328
329 #
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 # doing so creates an empty quantile.
340 #
341 if lindex != lowerBound:
342 newIndex = lindex
343 else:
344 newIndex = rindex - 1
345
346 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 newIndex = rindex - 1
354
355 elif rcount < lcount:
356 # there are fewer items to the right, so go to the right
357 newIndex = rindex - 1
358
359 adjusted = adjusted or newIndex != listIndex
360
361 quantiles[qindex] = newIndex
362 lowerBound = quantiles[qindex]
363
364 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 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 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
385 index -- a value such that 0 <= index <= 1
386 """
387
388 if not (0 <= index <= 1):
389 raise ValueError(_("invalid index"))
390
391 newProps = ClassGroupProperties()
392
393 color1 = self.prop1.GetLineColor()
394 color2 = self.prop2.GetLineColor()
395
396 self.__SetProperty(color1, color2, index, newProps.SetLineColor)
397 self.__SetProperty(color1, color2, index, newProps.SetFill)
398
399 w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
400 * index \
401 + self.prop1.GetLineWidth()
402
403 newProps.SetLineWidth(int(round(w)))
404
405 return newProps
406
407 def __SetProperty(self, color1, color2, index, setf):
408
409 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 else:
422 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
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 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
445 class HotToColdRamp:
446
447 def GetRamp(self):
448 return self
449
450 def GetProperties(self, index):
451 """Return a ClassGroupProperties object whose properties
452 represent a point at 'index' between "hot" and "cold".
453
454 index -- a value such that 0 <= index <= 1
455 """
456
457 clr = [1.0, 1.0, 1.0]
458
459 if index < .25:
460 clr[0] = 0
461 clr[1] = 4 * index
462 elif index < .5:
463 clr[0] = 0
464 clr[2] = 1 + 4 * (.25 - index)
465 elif index < .75:
466 clr[0] = 4 * (index - .5)
467 clr[2] = 0
468 else:
469 clr[1] = 1 + 4 * (.75 - index)
470 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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26