/[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 986 - (show annotations)
Thu May 22 16:41:10 2003 UTC (21 years, 9 months ago) by tkoester
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 16794 byte(s)
* Thuban/Model/classgen.py: Added short module doc string and CVS id.
(ClassGenerator.GenUniformDistribution): Use new Range __init__, too.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26