/[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 1352 - (show annotations)
Wed Jul 2 09:36:39 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14359 byte(s)
Fixed parameters to ClassGroupRange constructor.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26