/[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 895 - (show annotations)
Mon May 12 11:21:02 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: 16679 byte(s)
(ClassGenerator.GenQuantiles): Add comments describing the parameters,
        use new return values from CalculateQuantiles to produce the correct
        range bounds in the Classification.
(ClassGenerator.CalculateQuantiles): Add more comments describing
        the return values and parameters. Make minor adjustments to improve
        the legibility of the code. Fix problem with adjusted not being set
        in most cases.

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