/[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 1157 - (show annotations)
Thu Jun 12 12:39:54 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: 17065 byte(s)
Renamed functions to follow the
        function_names_with_underscores style. Fixes RTbug #1903.
(calculate_quantiles): Raise ValueError if 'percents' is invalid.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26