/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/classification.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/Model/classification.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1909 - (show annotations)
Fri Oct 31 18:16:34 2003 UTC (21 years, 4 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 19732 byte(s)
(Classification.SetDefaultGroup):
Send a CLASS_CHANGED message
(Classification.RemoveGroup): Send a CLASS_CHANGED message and do
not return the removed group since it wasn't used.

1 # Copyright (c) 2001, 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 __version__ = "$Revision$"
9
10 """
11 A Classification provides a mapping from an input value
12 to data. This mapping can be specified in two ways.
13 First, specific values can be associated with data.
14 Second, ranges can be associated with data such that if
15 an input value falls with a range that data is returned.
16 If no mapping can be found then default data will
17 be returned. Input values must be hashable objects
18
19 See the description of FindGroup() for more information
20 on the mapping algorithm.
21 """
22
23 import copy, operator, types
24
25 from Thuban import _
26
27 from messages import \
28 LAYER_PROJECTION_CHANGED, \
29 LAYER_LEGEND_CHANGED, \
30 LAYER_VISIBILITY_CHANGED,\
31 CLASS_CHANGED
32
33 from Thuban.Model.color import Color, Transparent, Black
34 from Thuban.Model.range import Range
35
36 import Thuban.Model.layer
37
38 from Thuban.Lib.connector import Publisher
39
40 class Classification(Publisher):
41 """Encapsulates the classification of layer.
42
43 The Classification divides some kind of data into Groups which
44 are associated with properties. Later the properties can be
45 retrieved by matching data values to the appropriate group.
46 """
47
48 def __init__(self):
49 """Initialize a classification."""
50
51 self.__groups = []
52
53 self.SetDefaultGroup(ClassGroupDefault())
54
55 def __iter__(self):
56 return ClassIterator(self.__groups)
57
58 def __deepcopy__(self, memo):
59 clazz = Classification()
60
61 clazz.__groups[0] = copy.deepcopy(self.__groups[0])
62
63 for i in range(1, len(self.__groups)):
64 clazz.__groups.append(copy.deepcopy(self.__groups[i]))
65
66 return clazz
67
68 def __SendNotification(self):
69 """Notify the layer that this class has changed."""
70 self.issue(CLASS_CHANGED)
71
72 #
73 # these SetDefault* methods are really only provided for
74 # some backward compatibility. they should be considered
75 # for removal once all the classification code is finished.
76 #
77
78 def SetDefaultFill(self, fill):
79 """Set the default fill color.
80
81 fill -- a Color object.
82 """
83 self.GetDefaultGroup().GetProperties().SetFill(fill)
84 self.__SendNotification()
85
86 def GetDefaultFill(self):
87 """Return the default fill color."""
88 return self.GetDefaultGroup().GetProperties().GetFill()
89
90 def SetDefaultLineColor(self, color):
91 """Set the default line color.
92
93 color -- a Color object.
94 """
95 self.GetDefaultGroup().GetProperties().SetLineColor(color)
96 self.__SendNotification()
97
98 def GetDefaultLineColor(self):
99 """Return the default line color."""
100 return self.GetDefaultGroup().GetProperties().GetLineColor()
101
102 def SetDefaultLineWidth(self, lineWidth):
103 """Set the default line width.
104
105 lineWidth -- an integer > 0.
106 """
107 assert isinstance(lineWidth, types.IntType)
108 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
109 self.__SendNotification()
110
111 def GetDefaultLineWidth(self):
112 """Return the default line width."""
113 return self.GetDefaultGroup().GetProperties().GetLineWidth()
114
115
116 #
117 # The methods that manipulate self.__groups have to be kept in
118 # sync. We store the default group in index 0 to make it
119 # convienent to iterate over the classification's groups, but
120 # from the user's perspective the first (non-default) group is
121 # at index 0 and the DefaultGroup is a special entity.
122 #
123
124 def SetDefaultGroup(self, group):
125 """Set the group to be used when a value can't be classified.
126
127 group -- group that the value maps to.
128 """
129 assert isinstance(group, ClassGroupDefault)
130 if len(self.__groups) > 0:
131 self.__groups[0] = group
132 else:
133 self.__groups.append(group)
134 self.__SendNotification()
135
136 def GetDefaultGroup(self):
137 """Return the default group."""
138 return self.__groups[0]
139
140 def AppendGroup(self, item):
141 """Append a new ClassGroup item to the classification.
142
143 item -- this must be a valid ClassGroup object
144 """
145
146 self.InsertGroup(self.GetNumGroups(), item)
147
148 def InsertGroup(self, index, group):
149 assert isinstance(group, ClassGroup)
150 self.__groups.insert(index + 1, group)
151 self.__SendNotification()
152
153 def RemoveGroup(self, index):
154 """Remove the classification group with the given index"""
155 self.__groups.pop(index + 1)
156 self.__SendNotification()
157
158 def ReplaceGroup(self, index, group):
159 assert isinstance(group, ClassGroup)
160 self.__groups[index + 1] = group
161 self.__SendNotification()
162
163 def GetGroup(self, index):
164 return self.__groups[index + 1]
165
166 def GetNumGroups(self):
167 """Return the number of non-default groups in the classification."""
168 return len(self.__groups) - 1
169
170 def FindGroup(self, value):
171 """Return the associated group, or the default group.
172
173 Groups are checked in the order the were added to the
174 Classification.
175
176 value -- the value to classify. If there is no mapping,
177 the field is None or value is None,
178 return the default properties
179 """
180
181 if value is not None:
182 for i in range(1, len(self.__groups)):
183 group = self.__groups[i]
184 if group.Matches(value):
185 return group
186
187 return self.GetDefaultGroup()
188
189 def GetProperties(self, value):
190 """Return the properties associated with the given value.
191
192 Use this function rather than Classification.FindGroup().GetProperties()
193 since the returned group may be a ClassGroupMap which doesn't support
194 a call to GetProperties().
195 """
196
197 group = self.FindGroup(value)
198 if isinstance(group, ClassGroupMap):
199 return group.GetPropertiesFromValue(value)
200 else:
201 return group.GetProperties()
202
203 def TreeInfo(self):
204 items = []
205
206 def build_color_item(text, color):
207 if color is Transparent:
208 return ("%s: %s" % (text, _("None")), None)
209
210 return ("%s: (%.3f, %.3f, %.3f)" %
211 (text, color.red, color.green, color.blue),
212 color)
213
214 def build_item(group, string):
215 label = group.GetLabel()
216 if label == "":
217 label = string
218 else:
219 label += " (%s)" % string
220
221 props = group.GetProperties()
222 i = []
223 v = props.GetLineColor()
224 i.append(build_color_item(_("Line Color"), v))
225 v = props.GetLineWidth()
226 i.append(_("Line Width: %s") % v)
227 v = props.GetFill()
228 i.append(build_color_item(_("Fill"), v))
229 return (label, i)
230
231 for p in self:
232 items.append(build_item(p, p.GetDisplayText()))
233
234 return (_("Classification"), items)
235
236 class ClassIterator:
237 """Allows the Groups in a Classifcation to be interated over.
238
239 The items are returned in the following order:
240 default data, singletons, ranges, maps
241 """
242
243 def __init__(self, data): #default, points, ranges, maps):
244 """Constructor.
245
246 default -- the default group
247
248 points -- a list of singleton groups
249
250 ranges -- a list of range groups
251
252 maps -- a list of map groups
253 """
254
255 self.data = data
256 self.data_index = 0
257
258 def __iter__(self):
259 return self
260
261 def next(self):
262 """Return the next item."""
263
264 if self.data_index >= len(self.data):
265 raise StopIteration
266 else:
267 d = self.data[self.data_index]
268 self.data_index += 1
269 return d
270
271 class ClassGroupProperties:
272 """Represents the properties of a single Classification Group.
273
274 These are used when rendering a layer."""
275
276 def __init__(self, props = None):
277 """Constructor.
278
279 props -- a ClassGroupProperties object. The class is copied if
280 prop is not None. Otherwise, a default set of properties
281 is created such that: line color = Black, line width = 1,
282 and fill color = Transparent
283 """
284
285 if props is not None:
286 self.SetProperties(props)
287 else:
288 self.SetLineColor(Black)
289 self.SetLineWidth(1)
290 self.SetFill(Transparent)
291
292 def SetProperties(self, props):
293 """Set this class's properties to those in class props."""
294
295 assert isinstance(props, ClassGroupProperties)
296 self.SetLineColor(props.GetLineColor())
297 self.SetLineWidth(props.GetLineWidth())
298 self.SetFill(props.GetFill())
299
300 def GetLineColor(self):
301 """Return the line color as a Color object."""
302 return self.__stroke
303
304 def SetLineColor(self, color):
305 """Set the line color.
306
307 color -- the color of the line. This must be a Color object.
308 """
309
310 self.__stroke = color
311
312 def GetLineWidth(self):
313 """Return the line width."""
314 return self.__strokeWidth
315
316 def SetLineWidth(self, lineWidth):
317 """Set the line width.
318
319 lineWidth -- the new line width. This must be > 0.
320 """
321 assert isinstance(lineWidth, types.IntType)
322 if (lineWidth < 1):
323 raise ValueError(_("lineWidth < 1"))
324
325 self.__strokeWidth = lineWidth
326
327 def GetFill(self):
328 """Return the fill color as a Color object."""
329 return self.__fill
330
331 def SetFill(self, fill):
332 """Set the fill color.
333
334 fill -- the color of the fill. This must be a Color object.
335 """
336
337 self.__fill = fill
338
339 def __eq__(self, other):
340 """Return true if 'props' has the same attributes as this class"""
341
342 #
343 # using 'is' over '==' results in a huge performance gain
344 # in the renderer
345 #
346 return isinstance(other, ClassGroupProperties) \
347 and (self.__stroke is other.__stroke or \
348 self.__stroke == other.__stroke) \
349 and (self.__fill is other.__fill or \
350 self.__fill == other.__fill) \
351 and self.__strokeWidth == other.__strokeWidth
352
353 def __ne__(self, other):
354 return not self.__eq__(other)
355
356 def __copy__(self):
357 return ClassGroupProperties(self)
358
359 def __deepcopy__(self):
360 return ClassGroupProperties(self)
361
362 def __repr__(self):
363 return repr((self.__stroke, self.__strokeWidth, self.__fill))
364
365 class ClassGroup:
366 """A base class for all Groups within a Classification"""
367
368 def __init__(self, label = "", props = None, group = None):
369 """Constructor.
370
371 label -- A string representing the Group's label
372 """
373
374 if group is not None:
375 self.SetLabel(copy.copy(group.GetLabel()))
376 self.SetProperties(copy.copy(group.GetProperties()))
377 self.SetVisible(group.IsVisible())
378 else:
379 self.SetLabel(label)
380 self.SetProperties(props)
381 self.SetVisible(True)
382
383 def GetLabel(self):
384 """Return the Group's label."""
385 return self.label
386
387 def SetLabel(self, label):
388 """Set the Group's label.
389
390 label -- a string representing the Group's label. This must
391 not be None.
392 """
393 assert isinstance(label, types.StringTypes)
394 self.label = label
395
396 def GetDisplayText(self):
397 assert False, "GetDisplay must be overridden by subclass!"
398 return ""
399
400 def Matches(self, value):
401 """Determines if this Group is associated with the given value.
402
403 Returns False. This needs to be overridden by all subclasses.
404 """
405 assert False, "GetMatches must be overridden by subclass!"
406 return False
407
408 def GetProperties(self):
409 """Return the properties associated with the given value."""
410
411 return self.prop
412
413 def SetProperties(self, prop):
414 """Set the properties associated with this Group.
415
416 prop -- a ClassGroupProperties object. if prop is None,
417 a default set of properties is created.
418 """
419
420 if prop is None: prop = ClassGroupProperties()
421 assert isinstance(prop, ClassGroupProperties)
422 self.prop = prop
423
424 def IsVisible(self):
425 return self.visible
426
427 def SetVisible(self, visible):
428 self.visible = visible
429
430 def __eq__(self, other):
431 return isinstance(other, ClassGroup) \
432 and self.label == other.label \
433 and self.GetProperties() == other.GetProperties()
434
435 def __ne__(self, other):
436 return not self.__eq__(other)
437
438 def __repr__(self):
439 return repr(self.label) + ", " + repr(self.GetProperties())
440
441 class ClassGroupSingleton(ClassGroup):
442 """A Group that is associated with a single value."""
443
444 def __init__(self, value = 0, props = None, label = "", group = None):
445 """Constructor.
446
447 value -- the associated value.
448
449 prop -- a ClassGroupProperites object. If prop is None a default
450 set of properties is created.
451
452 label -- a label for this group.
453 """
454 ClassGroup.__init__(self, label, props, group)
455
456 self.SetValue(value)
457
458 def __copy__(self):
459 return ClassGroupSingleton(self.GetValue(),
460 self.GetProperties(),
461 self.GetLabel())
462
463 def __deepcopy__(self, memo):
464 return ClassGroupSingleton(self.GetValue(), group = self)
465
466 def GetValue(self):
467 """Return the associated value."""
468 return self.__value
469
470 def SetValue(self, value):
471 """Associate this Group with the given value."""
472 self.__value = value
473
474 def Matches(self, value):
475 """Determine if the given value matches the associated Group value."""
476
477 """Returns True if the value matches, False otherwise."""
478
479 return self.__value == value
480
481 def GetDisplayText(self):
482 label = self.GetLabel()
483
484 if label != "": return label
485
486 return str(self.GetValue())
487
488 def __eq__(self, other):
489 return ClassGroup.__eq__(self, other) \
490 and isinstance(other, ClassGroupSingleton) \
491 and self.__value == other.__value
492
493 def __repr__(self):
494 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
495
496 class ClassGroupDefault(ClassGroup):
497 """The default Group. When values do not match any other
498 Group within a Classification, the properties from this
499 class are used."""
500
501 def __init__(self, props = None, label = "", group = None):
502 """Constructor.
503
504 prop -- a ClassGroupProperites object. If prop is None a default
505 set of properties is created.
506
507 label -- a label for this group.
508 """
509
510 ClassGroup.__init__(self, label, props, group)
511
512 def __copy__(self):
513 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
514
515 def __deepcopy__(self, memo):
516 return ClassGroupDefault(label = self.GetLabel(), group = self)
517
518 def Matches(self, value):
519 return True
520
521 def GetDisplayText(self):
522 label = self.GetLabel()
523
524 if label != "": return label
525
526 return _("DEFAULT")
527
528 def __eq__(self, other):
529 return ClassGroup.__eq__(self, other) \
530 and isinstance(other, ClassGroupDefault) \
531 and self.GetProperties() == other.GetProperties()
532
533 def __repr__(self):
534 return "(" + ClassGroup.__repr__(self) + ")"
535
536 class ClassGroupRange(ClassGroup):
537 """A Group that represents a range of values that map to the same
538 set of properties."""
539
540 def __init__(self, _range = (0,1), props = None, label = "", group=None):
541 """Constructor.
542
543 The minumum value must be strictly less than the maximum.
544
545 _range -- either a tuple (min, max) where min < max or
546 a Range object
547
548 prop -- a ClassGroupProperites object. If prop is None a default
549 set of properties is created.
550
551 label -- a label for this group.
552 """
553
554 ClassGroup.__init__(self, label, props, group)
555 self.SetRange(_range)
556
557 def __copy__(self):
558 return ClassGroupRange(self.__range,
559 props = self.GetProperties(),
560 label = self.GetLabel())
561
562 def __deepcopy__(self, memo):
563 return ClassGroupRange(copy.copy(self.__range),
564 group = self)
565
566 def GetMin(self):
567 """Return the range's minimum value."""
568 return self.__range.GetRange()[1]
569
570 def SetMin(self, min):
571 """Set the range's minimum value.
572
573 min -- the new minimum. Note that this must be less than the current
574 maximum value. Use SetRange() to change both min and max values.
575 """
576
577 self.SetRange((min, self.__range.GetRange()[2]))
578
579 def GetMax(self):
580 """Return the range's maximum value."""
581 return self.__range.GetRange()[2]
582
583 def SetMax(self, max):
584 """Set the range's maximum value.
585
586 max -- the new maximum. Note that this must be greater than the current
587 minimum value. Use SetRange() to change both min and max values.
588 """
589 self.SetRange((self.__range.GetRange()[1], max))
590
591 def SetRange(self, _range):
592 """Set a new range.
593
594 _range -- Either a tuple (min, max) where min < max or
595 a Range object.
596
597 Raises ValueError on error.
598 """
599
600 if isinstance(_range, Range):
601 self.__range = _range
602 elif isinstance(_range, types.TupleType) and len(_range) == 2:
603 self.__range = Range(("[", _range[0], _range[1], "["))
604 else:
605 raise ValueError()
606
607 def GetRange(self):
608 """Return the range as a string"""
609 return self.__range.string(self.__range.GetRange())
610
611 def Matches(self, value):
612 """Determine if the given value lies with the current range.
613
614 The following check is used: min <= value < max.
615 """
616
617 return operator.contains(self.__range, value)
618
619 def GetDisplayText(self):
620 label = self.GetLabel()
621
622 if label != "": return label
623
624 return self.__range.string(self.__range.GetRange())
625
626 def __eq__(self, other):
627 return ClassGroup.__eq__(self, other) \
628 and isinstance(other, ClassGroupRange) \
629 and self.__range == other.__range
630
631 def __repr__(self):
632 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
633
634 class ClassGroupMap(ClassGroup):
635 """Currently, this class is not used."""
636
637 FUNC_ID = "id"
638
639 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
640 ClassGroup.__init__(self, label)
641
642 self.map_type = map_type
643 self.func = func
644
645 if self.func is None:
646 self.func = func_id
647
648 def Map(self, value):
649 return self.func(value)
650
651 def GetProperties(self):
652 return None
653
654 def GetPropertiesFromValue(self, value):
655 pass
656
657 def GetDisplayText(self):
658 return "Map: " + self.map_type
659
660 #
661 # built-in mappings
662 #
663 def func_id(value):
664 return value
665

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26