/[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 1525 - (show annotations)
Wed Jul 30 15:42:56 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 16477 byte(s)
Add docstrings. Rename specific Ramp instances to use lower_case_style.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26