/[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 1379 - (show annotations)
Tue Jul 8 13:23:20 2003 UTC (21 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14797 byte(s)
* Thuban/Model/classgen.py (CustomRamp.GetProperties): Compute the
interpolated colors correctly.

* test/test_classgen.py (TestCustomRamp.test_color_interpolation):
New. Test case for the fix in classgen.py

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 self.prop1 = prop1
381 self.prop2 = prop2
382
383 def GetRamp(self):
384 return self
385
386 def GetProperties(self, index):
387 """Return a ClassGroupProperties object whose properties
388 represent a point at 'index' between prop1 and prop2 in
389 the constructor.
390
391 index -- a value such that 0 <= index <= 1
392 """
393
394 if not (0 <= index <= 1):
395 raise ValueError(_("invalid index"))
396
397 newProps = ClassGroupProperties()
398
399 color1 = self.prop1.GetLineColor()
400 color2 = self.prop2.GetLineColor()
401
402 self.__SetProperty(self.prop1.GetLineColor(),
403 self.prop2.GetLineColor(),
404 index, newProps.SetLineColor)
405 self.__SetProperty(self.prop1.GetFill(), self.prop2.GetFill(),
406 index, newProps.SetFill)
407
408 w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
409 * index \
410 + self.prop1.GetLineWidth()
411
412 newProps.SetLineWidth(int(round(w)))
413
414 return newProps
415
416 def __SetProperty(self, color1, color2, index, setf):
417
418 if color1 is Transparent and color2 is Transparent:
419 setf(Transparent)
420 elif color1 is Transparent:
421 setf(Color(
422 color2.red * index,
423 color2.green * index,
424 color2.blue * index))
425 elif color2 is Transparent:
426 setf(Color(
427 color1.red * index,
428 color1.green * index,
429 color1.blue * index))
430 else:
431 setf(Color(
432 (color2.red - color1.red) * index + color1.red,
433 (color2.green - color1.green) * index + color1.green,
434 (color2.blue - color1.blue) * index + color1.blue))
435
436 class MonochromaticRamp(CustomRamp):
437 def __init__(self, start, end):
438 sp = ClassGroupProperties()
439 sp.SetLineColor(start)
440 sp.SetFill(start)
441
442 ep = ClassGroupProperties()
443 ep.SetLineColor(end)
444 ep.SetFill(end)
445
446 CustomRamp.__init__(self, sp, ep)
447
448 GreyRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, 0))
449 RedRamp = MonochromaticRamp(Color(1, 1, 1), Color(.8, 0, 0))
450 GreenRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, .8, 0))
451 BlueRamp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, .8))
452 GreenToRedRamp = MonochromaticRamp(Color(1, .8, 1), Color(1, 0, 0))
453
454 class HotToColdRamp:
455
456 def GetRamp(self):
457 return self
458
459 def GetProperties(self, index):
460 """Return a ClassGroupProperties object whose properties
461 represent a point at 'index' between "hot" and "cold".
462
463 index -- a value such that 0 <= index <= 1
464 """
465
466 clr = [1.0, 1.0, 1.0]
467
468 if index < .25:
469 clr[0] = 0
470 clr[1] = 4 * index
471 elif index < .5:
472 clr[0] = 0
473 clr[2] = 1 + 4 * (.25 - index)
474 elif index < .75:
475 clr[0] = 4 * (index - .5)
476 clr[2] = 0
477 else:
478 clr[1] = 1 + 4 * (.75 - index)
479 clr[2] = 0
480
481 prop = ClassGroupProperties()
482 prop.SetLineColor(Color(clr[0], clr[1], clr[2]))
483 prop.SetFill(Color(clr[0], clr[1], clr[2]))
484
485 return prop
486

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26