/[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 1759 - (show annotations)
Fri Sep 26 18:36:01 2003 UTC (21 years, 5 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/classgen.py
File MIME type: text/x-python
File size: 14517 byte(s)
(GenQuantiles0): Removed since it's
only used in GREAT-ER but not used in Thuban itself. When GREAT-ER
is ported to a newer the import will fail, so it should be noticed
immediately that this function is gone.
Fixes RT#1919

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
156 def calculate_quantiles(_list, percents, _range):
157 """Calculate quantiles for the given _list of percents from the
158 sorted list of values that are in range.
159
160 This may not actually generate len(percents) quantiles if
161 many of the values that fall on quantile borders are the same.
162
163 Returns a tuple of the form:
164 (adjusted, minIndex, maxIndex, [quantile_list])
165
166 where adjusted is True if the the quantile percentages differ from
167 those supplied, minIndex is the index into _list where the
168 minimum value used is located, maxIndex is the index into _list
169 where the maximum value used is located, and quantile_list is a
170 list of tuples of the form: (list_index, quantile_percentage)
171
172 Returns None, if no quantiles could be generated based on the
173 given range or input list.
174
175 _list -- a sort list of values
176
177 percents -- a sorted list of floats in the range 0.0-1.0 which
178 represent the upper bound of each quantile. the
179 union of all percentiles should be the entire
180 range from 0.0-1.0
181
182 _range -- a Range object
183
184 Raises a Value Error if 'percents' has fewer than two items, or
185 does not cover the entire range.
186 """
187
188 quantiles = []
189 adjusted = False
190
191 if len(percents) <= 1:
192 raise ValueError("percents parameter must have more than one item")
193
194 if percents[-1] != 1.0:
195 raise ValueError("percents does not cover the entire range")
196
197 #
198 # find what part of the _list range covers
199 #
200 minIndex = -1
201 maxIndex = -2
202 for i in xrange(0, len(_list), 1):
203 if operator.contains(_range, _list[i]):
204 minIndex = i
205 break
206
207 for i in xrange(len(_list)-1, -1, -1):
208 if operator.contains(_range, _list[i]):
209 maxIndex = i
210 break
211
212 numValues = maxIndex - minIndex + 1
213
214 if numValues > 0:
215
216 #
217 # build a list of unique indices into list of where each
218 # quantile *should* be. set adjusted if the resulting
219 # indices are different
220 #
221 quantiles = {}
222 for p in percents:
223 index = min(minIndex + int(p*numValues)-1, maxIndex)
224
225 adjusted = adjusted \
226 or quantiles.has_key(index) \
227 or ((index - minIndex + 1) / float(numValues)) != p
228
229 quantiles[index] = 0
230
231 quantiles = quantiles.keys()
232 quantiles.sort()
233
234 #
235 # the current quantile index must be strictly greater than
236 # the lowerBound
237 #
238 lowerBound = minIndex - 1
239
240 for qindex in xrange(len(quantiles)):
241 if lowerBound >= maxIndex:
242 # discard higher quantiles
243 quantiles = quantiles[:qindex]
244 break
245
246 # lowerBound + 1 is always a valid index
247
248 #
249 # bump up the current quantile index to be a usable index
250 # if it currently falls below the lowerBound
251 #
252 if quantiles[qindex] <= lowerBound:
253 quantiles[qindex] = lowerBound + 1
254
255 listIndex = quantiles[qindex]
256 value = _list[listIndex]
257
258 #
259 # look for similar values around the quantile index
260 #
261 lindex = listIndex - 1
262 while lindex > lowerBound and value == _list[lindex]:
263 lindex -= 1
264 lcount = (listIndex - 1) - lindex
265
266 rindex = listIndex + 1
267 while rindex < maxIndex + 1 and value == _list[rindex]:
268 rindex += 1
269 rcount = (listIndex + 1) - rindex
270
271 #
272 # adjust the current quantile index based on how many
273 # numbers in the _list are the same as the current value
274 #
275 newIndex = listIndex
276 if lcount == rcount:
277 if lcount != 0:
278 #
279 # there are an equal number of numbers to the left
280 # and right, try going to the left first unless
281 # doing so creates an empty quantile.
282 #
283 if lindex != lowerBound:
284 newIndex = lindex
285 else:
286 newIndex = rindex - 1
287
288 elif lcount < rcount:
289 # there are fewer items to the left, so
290 # try going to the left first unless
291 # doing so creates an empty quantile.
292 if lindex != lowerBound:
293 newIndex = lindex
294 else:
295 newIndex = rindex - 1
296
297 elif rcount < lcount:
298 # there are fewer items to the right, so go to the right
299 newIndex = rindex - 1
300
301 adjusted = adjusted or newIndex != listIndex
302
303 quantiles[qindex] = newIndex
304 lowerBound = quantiles[qindex]
305
306 if len(quantiles) == 0:
307 return None
308 else:
309 return (adjusted, minIndex, maxIndex,
310 [(q, (q - minIndex+1) / float(numValues)) \
311 for q in quantiles])
312
313 class CustomRamp:
314
315 def __init__(self, prop1, prop2):
316 """Create a ramp between prop1 and prop2."""
317 self.prop1 = prop1
318 self.prop2 = prop2
319
320 def GetRamp(self):
321 """Return this ramp."""
322 return self
323
324 def GetProperties(self, index):
325 """Return a ClassGroupProperties object whose properties
326 represent a point at 'index' between prop1 and prop2 in
327 the constructor.
328
329 index -- a value such that 0 <= index <= 1
330 """
331
332 if not (0 <= index <= 1):
333 raise ValueError(_("invalid index"))
334
335 newProps = ClassGroupProperties()
336
337 self.__SetProperty(self.prop1.GetLineColor(),
338 self.prop2.GetLineColor(),
339 index, newProps.SetLineColor)
340 self.__SetProperty(self.prop1.GetFill(), self.prop2.GetFill(),
341 index, newProps.SetFill)
342
343 w = (self.prop2.GetLineWidth() - self.prop1.GetLineWidth()) \
344 * index \
345 + self.prop1.GetLineWidth()
346 newProps.SetLineWidth(int(round(w)))
347
348 return newProps
349
350 def __SetProperty(self, color1, color2, index, setf):
351 """Use setf to set the appropriate property for the point
352 index percent between color1 and color2. setf is a function
353 to call that accepts a Color object or Transparent.
354 """
355
356 if color1 is Transparent and color2 is Transparent:
357 setf(Transparent)
358 elif color1 is Transparent:
359 setf(Color(
360 color2.red * index,
361 color2.green * index,
362 color2.blue * index))
363 elif color2 is Transparent:
364 setf(Color(
365 color1.red * index,
366 color1.green * index,
367 color1.blue * index))
368 else:
369 setf(Color(
370 (color2.red - color1.red) * index + color1.red,
371 (color2.green - color1.green) * index + color1.green,
372 (color2.blue - color1.blue) * index + color1.blue))
373
374 class MonochromaticRamp(CustomRamp):
375 """Helper class to make ramps between two colors."""
376
377 def __init__(self, start, end):
378 """Create a Monochromatic Ramp.
379
380 start -- starting Color
381
382 end -- ending Color
383 """
384 sp = ClassGroupProperties()
385 sp.SetLineColor(start)
386 sp.SetFill(start)
387
388 ep = ClassGroupProperties()
389 ep.SetLineColor(end)
390 ep.SetFill(end)
391
392 CustomRamp.__init__(self, sp, ep)
393
394 grey_ramp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, 0))
395 red_ramp = MonochromaticRamp(Color(1, 1, 1), Color(.8, 0, 0))
396 green_ramp = MonochromaticRamp(Color(1, 1, 1), Color(0, .8, 0))
397 blue_ramp = MonochromaticRamp(Color(1, 1, 1), Color(0, 0, .8))
398 green_to_red_ramp = MonochromaticRamp(Color(0, .8, 0), Color(1, 0, 0))
399
400 class HotToColdRamp:
401 """A ramp that generates properties with colors ranging from
402 'hot' colors (e.g. red, orange) to 'cold' colors (e.g. green, blue)
403 """
404
405 def GetRamp(self):
406 """Return this ramp."""
407 return self
408
409 def GetProperties(self, index):
410 """Return a ClassGroupProperties object whose properties
411 represent a point at 'index' between "hot" and "cold".
412
413 index -- a value such that 0 <= index <= 1
414 """
415
416 clr = [1.0, 1.0, 1.0]
417
418 if index < .25:
419 clr[0] = 0
420 clr[1] = 4 * index
421 elif index < .5:
422 clr[0] = 0
423 clr[2] = 1 + 4 * (.25 - index)
424 elif index < .75:
425 clr[0] = 4 * (index - .5)
426 clr[2] = 0
427 else:
428 clr[1] = 1 + 4 * (.75 - index)
429 clr[2] = 0
430
431 prop = ClassGroupProperties()
432 prop.SetLineColor(Color(clr[0], clr[1], clr[2]))
433 prop.SetFill(Color(clr[0], clr[1], clr[2]))
434
435 return prop
436
437 class FixedRamp:
438 """FixedRamp allows particular properties of a ramp to be
439 held constant over the ramp.
440 """
441
442 def __init__(self, ramp, fixes):
443 """
444 ramp -- a source ramp to get the default properties
445
446 fixes -- a tuple (lineColor, lineWidth, fillColor) such that
447 if any item is not None, the appropriate property will
448 be fixed to that item value.
449 """
450
451 self.fixes = fixes
452 self.ramp = ramp
453
454 def GetRamp(self):
455 """Return this ramp."""
456 return self
457
458 def GetProperties(self, index):
459 """Return a ClassGroupProperties object whose properties
460 represent a point at 'index' between the properties in
461 the ramp that initialized this FixedRamp.
462
463 index -- a value such that 0 <= index <= 1
464 """
465
466 props = self.ramp.GetProperties(index)
467 if self.fixes[0] is not None: props.SetLineColor(self.fixes[0])
468 if self.fixes[1] is not None: props.SetLineWidth(self.fixes[1])
469 if self.fixes[2] is not None: props.SetFill(self.fixes[2])
470
471 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