/[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 1387 - (show annotations)
Thu Jul 10 14:53:03 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: 15914 byte(s)
(generate_singletons,
        generate_uniform_distribution, generate_quantiles):
        Added 'fixes' parameter so that property attributes can
        be held constant over the generated classification groups.
(CustomRamp.GetProperties): Remove unused variables.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26