/[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 900 - (show annotations)
Wed May 14 11:15:41 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 17164 byte(s)
(ClassGenerator.GenSingletonsFromList): Fix docstring.
(ClassGenerator.GenUniformDistribution): Fix spelling of method name.
(ClassGenerator.GenQuantiles): Use the left/right brackets and min/max
        values of the supplied range to determine the beginning and end
        bounds of the generated classes.

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