/[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 2374 - (show annotations)
Sun Oct 3 21:01:31 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 23679 byte(s)
Removed some trailing whitespaces.
(ClassGroupProperties.__init__): Set default size.
(ClassGroupProperties.SetProperties): Set the size.
(ClassGroupProperties.GetSize): New. Return the size.
(ClassGroupProperties.SetSize): New. Set the size.
(ClassGroupProperties__eq__): Compare also size.
(ClassGroupProperties__repr__): Print also size.

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 def __getattr__(self, attr):
73 """Generate the compiled classification on demand"""
74 if attr == "_compiled_classification":
75 self._compile_classification()
76 return self._compiled_classification
77 raise AttributeError(attr)
78
79 def _compile_classification(self):
80 """Generate the compiled classification
81
82 The compiled classification is a more compact representation of
83 the classification groups that is also more efficient for
84 performing the classification.
85
86 The compiled classification is a list of tuples. The first
87 element of the tuple is a string which describes the rest of the
88 tuple. There are two kinds of tuples:
89
90 'singletons'
91
92 The second element of the tuple is a dictionary which
93 combines several consecutive ClassGroupSingleton instances.
94 The dictionary maps the values of the singletons (as
95 returned by the GetValue() method) to the corresponding
96 group.
97
98 'range'
99
100 The tuple describes a ClassGroupRange instance. The tuples
101 second element is a tuple fo the form (lfunc, min, max,
102 rfunc, group) where group is the original group object,
103 lfunc and rfuct are comparison functions and min and max are
104 lower and upper bounds of the range. Given a value and such
105 a tuple the group matches if and only if
106
107 lfunc(min, value) and rfunc(max, value)
108
109 is true.
110
111 The compiled classification is bound to
112 self._compile_classification.
113 """
114 compiled = []
115 for group in self.__groups[1:]:
116 if isinstance(group, ClassGroupSingleton):
117 if not compiled or compiled[-1][0] != "singletons":
118 compiled.append(("singletons", {}))
119 compiled[-1][1].setdefault(group.GetValue(), group)
120 elif isinstance(group, ClassGroupRange):
121 left, min, max, right = group.GetRangeTuple()
122 if left == "[":
123 lfunc = operator.le
124 elif left == "]":
125 lfunc = operator.lt
126 if right == "[":
127 rfunc = operator.gt
128 elif right == "]":
129 rfunc = operator.ge
130 compiled.append(("range", (lfunc, min, max, rfunc, group)))
131 else:
132 raise TypeError("Unknown group type %s", group)
133 self._compiled_classification = compiled
134
135 def _clear_compiled_classification(self):
136 """Reset the compiled classification.
137
138 If will be created on demand when self._compiled_classification
139 is accessed again.
140
141 Call this method whenever self.__groups is modified.
142 """
143 try:
144 del self._compiled_classification
145 except:
146 pass
147
148 #
149 # these SetDefault* methods are really only provided for
150 # some backward compatibility. they should be considered
151 # for removal once all the classification code is finished.
152 #
153
154 def SetDefaultFill(self, fill):
155 """Set the default fill color.
156
157 fill -- a Color object.
158 """
159 self.GetDefaultGroup().GetProperties().SetFill(fill)
160 self.__SendNotification()
161
162 def GetDefaultFill(self):
163 """Return the default fill color."""
164 return self.GetDefaultGroup().GetProperties().GetFill()
165
166 def SetDefaultLineColor(self, color):
167 """Set the default line color.
168
169 color -- a Color object.
170 """
171 self.GetDefaultGroup().GetProperties().SetLineColor(color)
172 self.__SendNotification()
173
174 def GetDefaultLineColor(self):
175 """Return the default line color."""
176 return self.GetDefaultGroup().GetProperties().GetLineColor()
177
178 def SetDefaultLineWidth(self, lineWidth):
179 """Set the default line width.
180
181 lineWidth -- an integer > 0.
182 """
183 assert isinstance(lineWidth, types.IntType)
184 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
185 self.__SendNotification()
186
187 def GetDefaultLineWidth(self):
188 """Return the default line width."""
189 return self.GetDefaultGroup().GetProperties().GetLineWidth()
190
191
192 #
193 # The methods that manipulate self.__groups have to be kept in
194 # sync. We store the default group in index 0 to make it
195 # convienent to iterate over the classification's groups, but
196 # from the user's perspective the first (non-default) group is
197 # at index 0 and the DefaultGroup is a special entity.
198 #
199
200 def SetDefaultGroup(self, group):
201 """Set the group to be used when a value can't be classified.
202
203 group -- group that the value maps to.
204 """
205 assert isinstance(group, ClassGroupDefault)
206 if len(self.__groups) > 0:
207 self.__groups[0] = group
208 else:
209 self.__groups.append(group)
210 self.__SendNotification()
211
212 def GetDefaultGroup(self):
213 """Return the default group."""
214 return self.__groups[0]
215
216 def AppendGroup(self, item):
217 """Append a new ClassGroup item to the classification.
218
219 item -- this must be a valid ClassGroup object
220 """
221
222 self.InsertGroup(self.GetNumGroups(), item)
223
224 def InsertGroup(self, index, group):
225 assert isinstance(group, ClassGroup)
226 self.__groups.insert(index + 1, group)
227 self._clear_compiled_classification()
228 self.__SendNotification()
229
230 def RemoveGroup(self, index):
231 """Remove the classification group with the given index"""
232 self.__groups.pop(index + 1)
233 self._clear_compiled_classification()
234 self.__SendNotification()
235
236 def ReplaceGroup(self, index, group):
237 assert isinstance(group, ClassGroup)
238 self.__groups[index + 1] = group
239 self._clear_compiled_classification()
240 self.__SendNotification()
241
242 def GetGroup(self, index):
243 return self.__groups[index + 1]
244
245 def GetNumGroups(self):
246 """Return the number of non-default groups in the classification."""
247 return len(self.__groups) - 1
248
249 def FindGroup(self, value):
250 """Return the group that matches the value.
251
252 Groups are effectively checked in the order the were added to
253 the Classification.
254
255 value -- the value to classify. If there is no mapping or value
256 is None, return the default properties
257 """
258
259 if value is not None:
260 for typ, params in self._compiled_classification:
261 if typ == "singletons":
262 group = params.get(value)
263 if group is not None:
264 return group
265 elif typ == "range":
266 lfunc, min, max, rfunc, g = params
267 if lfunc(min, value) and rfunc(max, value):
268 return g
269
270 return self.GetDefaultGroup()
271
272 def GetProperties(self, value):
273 """Return the properties associated with the given value.
274
275 Use this function rather than Classification.FindGroup().GetProperties()
276 since the returned group may be a ClassGroupMap which doesn't support
277 a call to GetProperties().
278 """
279
280 group = self.FindGroup(value)
281 if isinstance(group, ClassGroupMap):
282 return group.GetPropertiesFromValue(value)
283 else:
284 return group.GetProperties()
285
286 def TreeInfo(self):
287 items = []
288
289 def build_color_item(text, color):
290 if color is Transparent:
291 return ("%s: %s" % (text, _("None")), None)
292
293 return ("%s: (%.3f, %.3f, %.3f)" %
294 (text, color.red, color.green, color.blue),
295 color)
296
297 def build_item(group, string):
298 label = group.GetLabel()
299 if label == "":
300 label = string
301 else:
302 label += " (%s)" % string
303
304 props = group.GetProperties()
305 i = []
306 v = props.GetLineColor()
307 i.append(build_color_item(_("Line Color"), v))
308 v = props.GetLineWidth()
309 i.append(_("Line Width: %s") % v)
310 v = props.GetFill()
311 i.append(build_color_item(_("Fill"), v))
312 return (label, i)
313
314 for p in self:
315 items.append(build_item(p, p.GetDisplayText()))
316
317 return (_("Classification"), items)
318
319 class ClassIterator:
320 """Allows the Groups in a Classifcation to be interated over.
321
322 The items are returned in the following order:
323 default data, singletons, ranges, maps
324 """
325
326 def __init__(self, data): #default, points, ranges, maps):
327 """Constructor.
328
329 default -- the default group
330
331 points -- a list of singleton groups
332
333 ranges -- a list of range groups
334
335 maps -- a list of map groups
336 """
337
338 self.data = data
339 self.data_index = 0
340
341 def __iter__(self):
342 return self
343
344 def next(self):
345 """Return the next item."""
346
347 if self.data_index >= len(self.data):
348 raise StopIteration
349 else:
350 d = self.data[self.data_index]
351 self.data_index += 1
352 return d
353
354 class ClassGroupProperties:
355 """Represents the properties of a single Classification Group.
356
357 These are used when rendering a layer."""
358
359 # TODO: Actually, size is only relevant for point objects.
360 # Eventually it should be spearated, e.g. when introducing symbols.
361
362 def __init__(self, props = None):
363 """Constructor.
364
365 props -- a ClassGroupProperties object. The class is copied if
366 prop is not None. Otherwise, a default set of properties
367 is created such that: line color = Black, line width = 1,
368 size = 5 and fill color = Transparent
369 """
370
371 if props is not None:
372 self.SetProperties(props)
373 else:
374 self.SetLineColor(Black)
375 self.SetLineWidth(1)
376 self.SetSize(5)
377 self.SetFill(Transparent)
378
379 def SetProperties(self, props):
380 """Set this class's properties to those in class props."""
381
382 assert isinstance(props, ClassGroupProperties)
383 self.SetLineColor(props.GetLineColor())
384 self.SetLineWidth(props.GetLineWidth())
385 self.SetSize(props.GetSize())
386 self.SetFill(props.GetFill())
387
388 def GetLineColor(self):
389 """Return the line color as a Color object."""
390 return self.__stroke
391
392 def SetLineColor(self, color):
393 """Set the line color.
394
395 color -- the color of the line. This must be a Color object.
396 """
397
398 self.__stroke = color
399
400 def GetLineWidth(self):
401 """Return the line width."""
402 return self.__strokeWidth
403
404 def SetLineWidth(self, lineWidth):
405 """Set the line width.
406
407 lineWidth -- the new line width. This must be > 0.
408 """
409 assert isinstance(lineWidth, types.IntType)
410 if (lineWidth < 1):
411 raise ValueError(_("lineWidth < 1"))
412
413 self.__strokeWidth = lineWidth
414
415 def GetSize(self):
416 """Return the size."""
417 return self.__size
418
419 def SetSize(self, size):
420 """Set the size.
421
422 size -- the new size. This must be > 0.
423 """
424 assert isinstance(size, types.IntType)
425 if (size < 1):
426 raise ValueError(_("size < 1"))
427
428 self.__size = size
429
430 def GetFill(self):
431 """Return the fill color as a Color object."""
432 return self.__fill
433
434 def SetFill(self, fill):
435 """Set the fill color.
436
437 fill -- the color of the fill. This must be a Color object.
438 """
439
440 self.__fill = fill
441
442 def __eq__(self, other):
443 """Return true if 'props' has the same attributes as this class"""
444
445 #
446 # using 'is' over '==' results in a huge performance gain
447 # in the renderer
448 #
449 return isinstance(other, ClassGroupProperties) \
450 and (self.__stroke is other.__stroke or \
451 self.__stroke == other.__stroke) \
452 and (self.__fill is other.__fill or \
453 self.__fill == other.__fill) \
454 and self.__strokeWidth == other.__strokeWidth\
455 and self.__size == other.__size
456
457 def __ne__(self, other):
458 return not self.__eq__(other)
459
460 def __copy__(self):
461 return ClassGroupProperties(self)
462
463 def __deepcopy__(self):
464 return ClassGroupProperties(self)
465
466 def __repr__(self):
467 return repr((self.__stroke, self.__strokeWidth, self.__size,
468 self.__fill))
469
470 class ClassGroup:
471 """A base class for all Groups within a Classification"""
472
473 def __init__(self, label = "", props = None, group = None):
474 """Constructor.
475
476 label -- A string representing the Group's label
477 """
478
479 if group is not None:
480 self.SetLabel(copy.copy(group.GetLabel()))
481 self.SetProperties(copy.copy(group.GetProperties()))
482 self.SetVisible(group.IsVisible())
483 else:
484 self.SetLabel(label)
485 self.SetProperties(props)
486 self.SetVisible(True)
487
488 def GetLabel(self):
489 """Return the Group's label."""
490 return self.label
491
492 def SetLabel(self, label):
493 """Set the Group's label.
494
495 label -- a string representing the Group's label. This must
496 not be None.
497 """
498 assert isinstance(label, types.StringTypes)
499 self.label = label
500
501 def GetDisplayText(self):
502 assert False, "GetDisplay must be overridden by subclass!"
503 return ""
504
505 def Matches(self, value):
506 """Determines if this Group is associated with the given value.
507
508 Returns False. This needs to be overridden by all subclasses.
509 """
510 assert False, "GetMatches must be overridden by subclass!"
511 return False
512
513 def GetProperties(self):
514 """Return the properties associated with the given value."""
515
516 return self.prop
517
518 def SetProperties(self, prop):
519 """Set the properties associated with this Group.
520
521 prop -- a ClassGroupProperties object. if prop is None,
522 a default set of properties is created.
523 """
524
525 if prop is None: prop = ClassGroupProperties()
526 assert isinstance(prop, ClassGroupProperties)
527 self.prop = prop
528
529 def IsVisible(self):
530 return self.visible
531
532 def SetVisible(self, visible):
533 self.visible = visible
534
535 def __eq__(self, other):
536 return isinstance(other, ClassGroup) \
537 and self.label == other.label \
538 and self.GetProperties() == other.GetProperties()
539
540 def __ne__(self, other):
541 return not self.__eq__(other)
542
543 def __repr__(self):
544 return repr(self.label) + ", " + repr(self.GetProperties())
545
546 class ClassGroupSingleton(ClassGroup):
547 """A Group that is associated with a single value."""
548
549 def __init__(self, value = 0, props = None, label = "", group = None):
550 """Constructor.
551
552 value -- the associated value.
553
554 prop -- a ClassGroupProperites object. If prop is None a default
555 set of properties is created.
556
557 label -- a label for this group.
558 """
559 ClassGroup.__init__(self, label, props, group)
560
561 self.SetValue(value)
562
563 def __copy__(self):
564 return ClassGroupSingleton(self.GetValue(),
565 self.GetProperties(),
566 self.GetLabel())
567
568 def __deepcopy__(self, memo):
569 return ClassGroupSingleton(self.GetValue(), group = self)
570
571 def GetValue(self):
572 """Return the associated value."""
573 return self.__value
574
575 def SetValue(self, value):
576 """Associate this Group with the given value."""
577 self.__value = value
578
579 def Matches(self, value):
580 """Determine if the given value matches the associated Group value."""
581
582 """Returns True if the value matches, False otherwise."""
583
584 return self.__value == value
585
586 def GetDisplayText(self):
587 label = self.GetLabel()
588
589 if label != "": return label
590
591 return str(self.GetValue())
592
593 def __eq__(self, other):
594 return ClassGroup.__eq__(self, other) \
595 and isinstance(other, ClassGroupSingleton) \
596 and self.__value == other.__value
597
598 def __repr__(self):
599 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
600
601 class ClassGroupDefault(ClassGroup):
602 """The default Group. When values do not match any other
603 Group within a Classification, the properties from this
604 class are used."""
605
606 def __init__(self, props = None, label = "", group = None):
607 """Constructor.
608
609 prop -- a ClassGroupProperites object. If prop is None a default
610 set of properties is created.
611
612 label -- a label for this group.
613 """
614
615 ClassGroup.__init__(self, label, props, group)
616
617 def __copy__(self):
618 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
619
620 def __deepcopy__(self, memo):
621 return ClassGroupDefault(label = self.GetLabel(), group = self)
622
623 def Matches(self, value):
624 return True
625
626 def GetDisplayText(self):
627 label = self.GetLabel()
628
629 if label != "": return label
630
631 return _("DEFAULT")
632
633 def __eq__(self, other):
634 return ClassGroup.__eq__(self, other) \
635 and isinstance(other, ClassGroupDefault) \
636 and self.GetProperties() == other.GetProperties()
637
638 def __repr__(self):
639 return "(" + ClassGroup.__repr__(self) + ")"
640
641 class ClassGroupRange(ClassGroup):
642 """A Group that represents a range of values that map to the same
643 set of properties."""
644
645 def __init__(self, _range = (0,1), props = None, label = "", group=None):
646 """Constructor.
647
648 The minumum value must be strictly less than the maximum.
649
650 _range -- either a tuple (min, max) where min < max or
651 a Range object
652
653 prop -- a ClassGroupProperites object. If prop is None a default
654 set of properties is created.
655
656 label -- a label for this group.
657 """
658
659 ClassGroup.__init__(self, label, props, group)
660 self.SetRange(_range)
661
662 def __copy__(self):
663 return ClassGroupRange(self.__range,
664 props = self.GetProperties(),
665 label = self.GetLabel())
666
667 def __deepcopy__(self, memo):
668 return ClassGroupRange(copy.copy(self.__range),
669 group = self)
670
671 def GetMin(self):
672 """Return the range's minimum value."""
673 return self.__range.GetRange()[1]
674
675 def SetMin(self, min):
676 """Set the range's minimum value.
677
678 min -- the new minimum. Note that this must be less than the current
679 maximum value. Use SetRange() to change both min and max values.
680 """
681
682 self.SetRange((min, self.__range.GetRange()[2]))
683
684 def GetMax(self):
685 """Return the range's maximum value."""
686 return self.__range.GetRange()[2]
687
688 def SetMax(self, max):
689 """Set the range's maximum value.
690
691 max -- the new maximum. Note that this must be greater than the current
692 minimum value. Use SetRange() to change both min and max values.
693 """
694 self.SetRange((self.__range.GetRange()[1], max))
695
696 def SetRange(self, _range):
697 """Set a new range.
698
699 _range -- Either a tuple (min, max) where min < max or
700 a Range object.
701
702 Raises ValueError on error.
703 """
704
705 if isinstance(_range, Range):
706 self.__range = _range
707 elif isinstance(_range, types.TupleType) and len(_range) == 2:
708 self.__range = Range(("[", _range[0], _range[1], "["))
709 else:
710 raise ValueError()
711
712 def GetRange(self):
713 """Return the range as a string"""
714 return self.__range.string(self.__range.GetRange())
715
716 def GetRangeTuple(self):
717 return self.__range.GetRange()
718
719 def Matches(self, value):
720 """Determine if the given value lies with the current range.
721
722 The following check is used: min <= value < max.
723 """
724
725 return operator.contains(self.__range, value)
726
727 def GetDisplayText(self):
728 label = self.GetLabel()
729
730 if label != "": return label
731
732 return self.__range.string(self.__range.GetRange())
733
734 def __eq__(self, other):
735 return ClassGroup.__eq__(self, other) \
736 and isinstance(other, ClassGroupRange) \
737 and self.__range == other.__range
738
739 def __repr__(self):
740 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
741
742 class ClassGroupMap(ClassGroup):
743 """Currently, this class is not used."""
744
745 FUNC_ID = "id"
746
747 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
748 ClassGroup.__init__(self, label)
749
750 self.map_type = map_type
751 self.func = func
752
753 if self.func is None:
754 self.func = func_id
755
756 def Map(self, value):
757 return self.func(value)
758
759 def GetProperties(self):
760 return None
761
762 def GetPropertiesFromValue(self, value):
763 pass
764
765 def GetDisplayText(self):
766 return "Map: " + self.map_type
767
768 #
769 # built-in mappings
770 #
771 def func_id(value):
772 return value
773

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26