/[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 2441 - (show annotations)
Thu Dec 9 10:50:34 2004 UTC (20 years, 3 months ago) by joey
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14779 byte(s)
Added missing character encoding so that Python doesn't barf upon
evaluating this file

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