/[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 2385 - (show annotations)
Thu Oct 7 14:28:51 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14746 byte(s)
(CustomRamp.GetProperties): Added size gradient.

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