/[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 959 - (show annotations)
Wed May 21 17:22:58 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: 16791 byte(s)
(ClassGenerator.GenQuantiles): Remove
        references to 'inf' and use new Range __init__ to pass floats
        directly rather than converting them to strings first.
        Fixes RTBug #1876.

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 oldp = 0
145 i = 1
146 end = "]"
147
148 for (q, p), prop in zip(quantiles[3], ramp):
149 if i == numGroups:
150 max = endMax
151 end = right
152 else:
153 max = _list[q]
154
155 group = ClassGroupRange(Range((start, min, max, end)),
156 None, prop)
157
158 group.SetLabel("%s%% - %s%%" % (round(oldp*100, 2),
159 round(p*100, 2)))
160 oldp = p
161 start = "]"
162 min = max
163 clazz.AppendGroup(group)
164 i += 1
165
166 return (adjusted, clazz)
167
168 def CalculateQuantiles(self, _list, percents, _range):
169 """Calculate quantiles for the given _list of percents from the
170 sorted list of values that are in range.
171
172 This may not actually generate len(percents) quantiles if
173 many of the values that fall on quantile borders are the same.
174
175 Returns a tuple of the form:
176 (adjusted, minIndex, maxIndex, [quantile_list])
177
178 where adjusted is True if the the quantile percentages differ from
179 those supplied, minIndex is the index into _list where the
180 minimum value used is located, maxIndex is the index into _list
181 where the maximum value used is located, and quantile_list is a
182 list of tuples of the form: (list_index, quantile_percentage)
183
184 Returns None, if no quantiles could be generated based on the
185 given range or input list.
186
187 _list -- a sort list of values
188
189 percents -- a sorted list of floats in the range 0.0-1.0 which
190 represent the upper bound of each quantile
191
192 _range -- a Range object
193 """
194
195 quantiles = []
196 adjusted = False
197
198 if len(percents) != 0:
199
200 #
201 # find what part of the _list range covers
202 #
203 minIndex = -1
204 maxIndex = -2
205 for i in xrange(0, len(_list), 1):
206 if operator.contains(_range, _list[i]):
207 minIndex = i
208 break
209
210 for i in xrange(len(_list)-1, -1, -1):
211 if operator.contains(_range, _list[i]):
212 maxIndex = i
213 break
214
215 numValues = maxIndex - minIndex + 1
216
217 if numValues > 0:
218
219 #
220 # build a list of unique indices into list of where each
221 # quantile *should* be. set adjusted if the resulting
222 # indices are different
223 #
224 quantiles = {}
225 for p in percents:
226 index = min(minIndex + int(p*numValues)-1, maxIndex)
227
228 adjusted = adjusted \
229 or quantiles.has_key(index) \
230 or ((index - minIndex + 1) / float(numValues)) != p
231
232 quantiles[index] = 0
233
234 quantiles = quantiles.keys()
235 quantiles.sort()
236
237 #
238 # the current quantile index must be strictly greater than
239 # the lowerBound
240 #
241 lowerBound = minIndex - 1
242
243 for qindex in xrange(len(quantiles)):
244 if lowerBound >= maxIndex:
245 # discard higher quantiles
246 quantiles = quantiles[:qindex]
247 break
248
249 # lowerBound + 1 is always a valid index
250
251 #
252 # bump up the current quantile index to be a usable index
253 # if it currently falls below the lowerBound
254 #
255 if quantiles[qindex] <= lowerBound:
256 quantiles[qindex] = lowerBound + 1
257
258 listIndex = quantiles[qindex]
259 value = _list[listIndex]
260
261 #
262 # look for similar values around the quantile index
263 #
264 lindex = listIndex - 1
265 while lindex > lowerBound and value == _list[lindex]:
266 lindex -= 1
267 lcount = (listIndex - 1) - lindex
268
269 rindex = listIndex + 1
270 while rindex < maxIndex + 1 and value == _list[rindex]:
271 rindex += 1
272 rcount = (listIndex + 1) - rindex
273
274 #
275 # adjust the current quantile index based on how many
276 # numbers in the _list are the same as the current value
277 #
278 newIndex = listIndex
279 if lcount == rcount:
280 if lcount != 0:
281 #
282 # there are an equal number of numbers to the left
283 # and right, try going to the left first unless
284 # doing so creates an empty quantile.
285 #
286 if lindex != lowerBound:
287 newIndex = lindex
288 else:
289 newIndex = rindex - 1
290
291 elif lcount < rcount:
292 # there are fewer items to the left, so
293 # try going to the left first unless
294 # doing so creates an empty quantile.
295 if lindex != lowerBound:
296 newIndex = lindex
297 else:
298 newIndex = rindex - 1
299
300 elif rcount < lcount:
301 # there are fewer items to the right, so go to the right
302 newIndex = rindex - 1
303
304 adjusted = adjusted or newIndex != listIndex
305
306 quantiles[qindex] = newIndex
307 lowerBound = quantiles[qindex]
308
309 #
310 # since quantiles is only set if the code is at least a little
311 # successful, an empty list will be generated in the case that
312 # we fail to get to the real body of the algorithm
313 #
314 if len(quantiles) == 0:
315 return None
316 else:
317 return (adjusted, minIndex, maxIndex,
318 [(q, (q - minIndex+1) / float(numValues)) \
319 for q in quantiles])
320
321 CLR = 0
322 STEP = 1
323 class CustomRamp:
324
325 def __init__(self, prop1, prop2):
326 self.prop1 = prop1
327 self.prop2 = prop2
328
329 self.count = 0
330
331 def __iter__(self):
332 return self
333
334 def GetRamp(self):
335 return self
336
337 def SetNumGroups(self, num):
338
339 if num <= 0:
340 return False
341
342 self.count = int(num)
343 num = float(num)
344
345 prop1 = self.prop1
346 prop2 = self.prop2
347
348 clr = prop1.GetLineColor()
349 lineColor2 = prop2.GetLineColor()
350
351 self.noLine = clr is not Color.Transparent \
352 and lineColor2 is not Color.Transparent
353
354
355 self.lineInfo = self.__GetColorInfo(prop1.GetLineColor(),
356 prop2.GetLineColor(),
357 num)
358
359 self.fillInfo = self.__GetColorInfo(prop1.GetFill(),
360 prop2.GetFill(),
361 num)
362
363 self.lineWidth = prop1.GetLineWidth()
364 self.lineWidthStep = (prop2.GetLineWidth() - self.lineWidth) / num
365
366 return True
367
368 def next(self):
369 if self.count == 0:
370 raise StopIteration
371
372 prop = ClassGroupProperties()
373
374 if self.lineInfo is None:
375 prop.SetLineColor(Color.Transparent)
376 else:
377 prop.SetLineColor(Color(self.lineInfo[CLR][0] / 255,
378 self.lineInfo[CLR][1] / 255,
379 self.lineInfo[CLR][2] / 255))
380
381 self.lineInfo[CLR][0] += self.lineInfo[STEP][0]
382 self.lineInfo[CLR][1] += self.lineInfo[STEP][1]
383 self.lineInfo[CLR][2] += self.lineInfo[STEP][2]
384
385 if self.fillInfo is None:
386 prop.SetFill(Color.Transparent)
387 else:
388 prop.SetFill(Color(self.fillInfo[CLR][0] / 255,
389 self.fillInfo[CLR][1] / 255,
390 self.fillInfo[CLR][2] / 255))
391
392 self.fillInfo[CLR][0] += self.fillInfo[STEP][0]
393 self.fillInfo[CLR][1] += self.fillInfo[STEP][1]
394 self.fillInfo[CLR][2] += self.fillInfo[STEP][2]
395
396
397 prop.SetLineWidth(int(self.lineWidth))
398 self.lineWidth += self.lineWidthStep
399
400 self.count -= 1
401
402 return prop
403
404 def __GetColorInfo(self, color1, color2, numGroups):
405
406 if color1 is Color.Transparent and color2 is Color.Transparent:
407 #
408 # returning early
409 #
410 return None
411 elif color1 is not Color.Transparent and color2 is Color.Transparent:
412 color = [color1.red * 255,
413 color1.green * 255,
414 color1.blue * 255]
415 step = (0, 0, 0)
416 elif color1 is Color.Transparent and color2 is not Color.Transparent:
417 color = [color2.red * 255,
418 color2.green * 255,
419 color2.blue * 255]
420 step = (0, 0, 0)
421 else:
422 color = [color1.red * 255,
423 color1.green * 255,
424 color1.blue * 255]
425 step = ((color2.red * 255 - color1.red * 255) / numGroups,
426 (color2.green * 255 - color1.green * 255) / numGroups,
427 (color2.blue * 255 - color1.blue * 255) / numGroups)
428
429
430 return (color, step)
431
432 class MonochromaticRamp(CustomRamp):
433 def __init__(self, start, end):
434 sp = ClassGroupProperties()
435 sp.SetLineColor(start)
436 sp.SetFill(start)
437
438 ep = ClassGroupProperties()
439 ep.SetLineColor(end)
440 ep.SetFill(end)
441
442 CustomRamp.__init__(self, sp, ep)
443
444 class GreyRamp(MonochromaticRamp):
445 def __init__(self):
446 MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, 0, 0))
447
448 class RedRamp(MonochromaticRamp):
449 def __init__(self):
450 MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(.8, 0, 0))
451
452 class GreenRamp(MonochromaticRamp):
453 def __init__(self):
454 MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, .8, 0))
455
456 class BlueRamp(MonochromaticRamp):
457 def __init__(self):
458 MonochromaticRamp.__init__(self, Color(1, 1, 1), Color(0, 0, .8))
459
460 class GreenToRedRamp(MonochromaticRamp):
461 def __init__(self):
462 MonochromaticRamp.__init__(self, Color(0, .8, 0), Color(1, 0, 0))
463
464 class HotToColdRamp:
465
466 def __iter__(self):
467 return self
468
469 def GetRamp(self):
470 return self
471
472 def SetNumGroups(self, num):
473 if num < 0:
474 return False
475
476 self.num = float(num)
477 self.index = 0
478
479 return True
480
481 def next(self):
482 if self.index == self.num:
483 raise StopIteration
484
485 clr = [1.0, 1.0, 1.0]
486
487 if self.index < (.25 * self.num):
488 clr[0] = 0
489 clr[1] = 4 * self.index / self.num
490 elif self.index < (.5 * self.num):
491 clr[0] = 0
492 clr[2] = 1 + 4 * (.25 * self.num - self.index) / self.num
493 elif self.index < (.75 * self.num):
494 clr[0] = 4 * (self.index - .5 * self.num) / self.num
495 clr[2] = 0
496 else:
497 clr[1] = 1 + 4 * (.75 * self.num - self.index) / self.num
498 clr[2] = 0
499
500 self.index += 1
501
502 prop = ClassGroupProperties()
503 prop.SetLineColor(Color(clr[0], clr[1], clr[2]))
504 prop.SetFill(Color(clr[0], clr[1], clr[2]))
505
506 return prop
507
508 #class Colors16Ramp:
509 #
510 #def __iter__(self):
511 #return self
512 #
513 #def GetRamp(self):
514 #return self
515 #
516 #def SetNumGroups(self, num):
517 #if num < 0:
518 #return False
519 #
520 #self.index = 0
521 #
522 #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