/[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 1335 - (show annotations)
Tue Jul 1 16:09:09 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: 14457 byte(s)
Fixes RTbug #1972, 1971.
        Import used objects/class from color.
(generate_singletons): We don't
        need the numGroups parameter anymore because we are using
        the new ramps with GetProperties().
(generate_uniform_distribution): Use new ramp method
        GetProperties().
(generate_quantiles, GenQuantiles0): Use new ramp method
        GetProperties().
(CustomRamp.SetNumGroups): Removed. The ramps now map
        a value from 0 to 1 to class properties so the number
        of groups is not needed ahead of time.
(CustomRamp.next): Removed. CustomRamp does not support
        interation anymore.
(CustomRamp.GetProperties): Returns a ClassGroupProperties
        object based on the index value from 0 to 1 that is
        passed to it.
(GreyRamp, RedRamp, GreenRamp, BlueRamp, GreenToRedRamp):
        Made into instances of Monochromatic class instread of
        deriving from it.
(HotToCold.SetNumGroups): Removed. See CustomRamp.
(HotToCold.next): Removed. See CustomRamp.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26