/[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 681 - (show annotations)
Tue Apr 15 21:54:32 2003 UTC (21 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 24208 byte(s)
Implemented __repr__ for the ClassGroup* classes to make debugging a bit easier.
(ClassGroup.SetLabel): Check that the string is an instance
        of StringTypes not StringType. Accounts for Unicode strings.

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 # fix for people using python2.1
24 from __future__ import nested_scopes
25
26 import copy
27
28 from Thuban import _
29
30 from types import *
31
32 from messages import \
33 LAYER_PROJECTION_CHANGED, \
34 LAYER_LEGEND_CHANGED, \
35 LAYER_VISIBILITY_CHANGED
36
37 from Thuban.Model.color import Color
38
39 import Thuban.Model.layer
40
41 class Classification:
42 """Encapsulates the classification of layer.
43
44 The Classification divides some kind of data into Groups which
45 are associated with properties. Later the properties can be
46 retrieved by matching data values to the appropriate group.
47 """
48
49 def __init__(self, layer = None, field = None):
50 """Initialize a classification.
51
52 layer -- the Layer object who owns this classification
53
54 field -- the name of the data table field that
55 is to be used to classify layer properties
56 """
57
58 self.layer = None
59 self.field = None
60 self.fieldType = None
61 self.__groups = []
62
63 self.__setLayerLock = False
64
65 self.SetDefaultGroup(ClassGroupDefault())
66
67 self.SetLayer(layer)
68 self.SetField(field)
69
70 def __iter__(self):
71 return ClassIterator(self.__groups)
72
73 def __deepcopy__(self, memo):
74 clazz = Classification()
75
76 # note: the only thing that isn't copied is the layer reference
77 clazz.field = self.field
78 clazz.fieldType = self.fieldType
79 clazz.__groups[0] = copy.deepcopy(self.__groups[0])
80
81 for i in range(1, len(self.__groups)):
82 clazz.__groups.append(copy.deepcopy(self.__groups[i]))
83
84 return clazz
85
86 def __SendNotification(self):
87 """Notify the layer that this class has changed."""
88 if self.layer is not None:
89 self.layer.ClassChanged()
90
91 def SetField(self, field):
92 """Set the name of the data table field to use.
93
94 If there is no layer then the field type is set to None,
95 otherwise the layer is queried to find the type of the
96 field data
97
98 field -- if None then all values map to the default data
99 """
100
101 if field == "":
102 field = None
103
104
105 if field is None:
106 if self.layer is not None:
107 self.fieldType = None
108 else:
109 if self.layer is not None:
110 fieldType = self.layer.GetFieldType(field)
111 if fieldType is None:
112 raise ValueError("'%s' was not found in the layer's table."
113 % self.field)
114
115 #
116 # unfortunately we cannot call SetFieldType() because it
117 # requires the layer to be None
118 #
119 self.fieldType = fieldType
120 #self.SetFieldType(fieldType)
121
122 self.field = field
123
124 self.__SendNotification()
125
126 def GetField(self):
127 """Return the name of the field."""
128 return self.field
129
130 def GetFieldType(self):
131 """Return the field type."""
132 return self.fieldType
133
134 def SetFieldType(self, type):
135 """Set the type of the field used by this classification.
136
137 A ValueError is raised if the owning layer is not None and
138 'type' is different from the current field type.
139 """
140
141 if type != self.fieldType:
142 if self.layer is not None:
143 raise ValueError()
144 else:
145 self.fieldType = type
146 self.__SendNotification()
147
148 def SetLayer(self, layer):
149 """Set the owning Layer of this classification.
150
151 A ValueError exception will be thrown either the field or
152 field type mismatch the information in the layer's table.
153 """
154
155 # prevent infinite recursion when calling SetClassification()
156 if self.__setLayerLock: return
157
158 self.__setLayerLock = True
159
160 if layer is None:
161 if self.layer is not None:
162 l = self.layer
163 self.layer = None
164 l.SetClassification(None)
165 else:
166 assert isinstance(layer, Thuban.Model.layer.Layer)
167
168 old_layer = self.layer
169
170 self.layer = layer
171
172 try:
173 self.SetField(self.GetField()) # this sync's the fieldType
174 except ValueError:
175 self.layer = old_layer
176 self.__setLayerLock = False
177 raise ValueError
178 else:
179 self.layer.SetClassification(self)
180
181 self.__setLayerLock = False
182
183 def GetLayer(self):
184 """Return the parent layer."""
185 return self.layer
186
187
188 #
189 # these SetDefault* methods are really only provided for
190 # some backward compatibility. they should be considered
191 # for removal once all the classification code is finished.
192 #
193
194 def SetDefaultFill(self, fill):
195 """Set the default fill color.
196
197 fill -- a Color object.
198 """
199 assert isinstance(fill, Color)
200 self.GetDefaultGroup().GetProperties().SetFill(fill)
201 self.__SendNotification()
202
203 def GetDefaultFill(self):
204 """Return the default fill color."""
205 return self.GetDefaultGroup().GetProperties().GetFill()
206
207 def SetDefaultLineColor(self, color):
208 """Set the default line color.
209
210 color -- a Color object.
211 """
212 assert isinstance(color, Color)
213 self.GetDefaultGroup().GetProperties().SetLineColor(color)
214 self.__SendNotification()
215
216 def GetDefaultLineColor(self):
217 """Return the default line color."""
218 return self.GetDefaultGroup().GetProperties().GetLineColor()
219
220 def SetDefaultLineWidth(self, lineWidth):
221 """Set the default line width.
222
223 lineWidth -- an integer > 0.
224 """
225 assert isinstance(lineWidth, IntType)
226 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
227 self.__SendNotification()
228
229 def GetDefaultLineWidth(self):
230 """Return the default line width."""
231 return self.GetDefaultGroup().GetProperties().GetLineWidth()
232
233
234 #
235 # The methods that manipulate self.__groups have to be kept in
236 # sync. We store the default group in index 0 to make it
237 # convienent to iterate over the classification's groups, but
238 # from the user's perspective the first (non-default) group is
239 # at index 0 and the DefaultGroup is a special entity.
240 #
241
242 def SetDefaultGroup(self, group):
243 """Set the group to be used when a value can't be classified.
244
245 group -- group that the value maps to.
246 """
247
248 assert isinstance(group, ClassGroupDefault)
249 if len(self.__groups) > 0:
250 self.__groups[0] = group
251 else:
252 self.__groups.append(group)
253
254 def GetDefaultGroup(self):
255 """Return the default group."""
256 return self.__groups[0]
257
258 def AppendGroup(self, item):
259 """Append a new ClassGroup item to the classification.
260
261 item -- this must be a valid ClassGroup object
262 """
263
264 self.InsertGroup(self.GetNumGroups(), item)
265
266 def InsertGroup(self, index, group):
267
268 assert isinstance(group, ClassGroup)
269
270 self.__groups.insert(index + 1, group)
271
272 self.__SendNotification()
273
274 def RemoveGroup(self, index):
275 return self.__groups.pop(index + 1)
276
277 def ReplaceGroup(self, index, group):
278 assert isinstance(group, ClassGroup)
279
280 self.__groups[index + 1] = group
281
282 self.__SendNotification()
283
284 def GetGroup(self, index):
285 return self.__groups[index + 1]
286
287 def GetNumGroups(self):
288 """Return the number of non-default groups in the classification."""
289 return len(self.__groups) - 1
290
291
292 def FindGroup(self, value):
293 """Return the associated group, or the default group.
294
295 Groups are checked in the order the were added to the
296 Classification.
297
298 value -- the value to classify. If there is no mapping,
299 the field is None or value is None,
300 return the default properties
301 """
302
303 if self.GetField() is not None and value is not None:
304
305 for i in range(1, len(self.__groups)):
306 group = self.__groups[i]
307 if group.Matches(value):
308 return group
309
310 return self.GetDefaultGroup()
311
312 def GetProperties(self, value):
313 """Return the properties associated with the given value.
314
315 Use this function rather than Classification.FindGroup().GetProperties()
316 since the returned group may be a ClassGroupMap which doesn't support
317 a call to GetProperties().
318 """
319
320 group = self.FindGroup(value)
321 if isinstance(group, ClassGroupMap):
322 return group.GetPropertiesFromValue(value)
323 else:
324 return group.GetProperties()
325
326 def TreeInfo(self):
327 items = []
328
329 def build_color_item(text, color):
330 if color is Color.Transparent:
331 return ("%s: %s" % (text, _("None")), None)
332
333 return ("%s: (%.3f, %.3f, %.3f)" %
334 (text, color.red, color.green, color.blue),
335 color)
336
337 def build_item(group, string):
338 label = group.GetLabel()
339 if label == "":
340 label = string
341 else:
342 label += " (%s)" % string
343
344 props = group.GetProperties()
345 i = []
346 v = props.GetLineColor()
347 i.append(build_color_item(_("Line Color"), v))
348 v = props.GetLineWidth()
349 i.append(_("Line Width: %s") % v)
350 v = props.GetFill()
351 i.append(build_color_item(_("Fill"), v))
352 return (label, i)
353
354 for p in self:
355 items.append(build_item(p, p.GetDisplayText()))
356
357 # if isinstance(p, ClassGroupDefault):
358 # items.append(build_item(self.GetDefaultGroup(), _("'DEFAULT'")))
359 # elif isinstance(p, ClassGroupSingleton):
360 # items.append(build_item(p, str(p.GetValue())))
361 # elif isinstance(p, ClassGroupRange):
362 # items.append(build_item(p, "%s - %s" %
363 # (p.GetMin(), p.GetMax())))
364
365 return (_("Classification"), items)
366
367 class ClassIterator:
368 """Allows the Groups in a Classifcation to be interated over.
369
370 The items are returned in the following order:
371 default data, singletons, ranges, maps
372 """
373
374 def __init__(self, data): #default, points, ranges, maps):
375 """Constructor.
376
377 default -- the default group
378
379 points -- a list of singleton groups
380
381 ranges -- a list of range groups
382
383 maps -- a list of map groups
384 """
385
386 self.data = data #[default, points, ranges, maps]
387 self.data_index = 0
388 #self.data_iter = iter(self.data)
389 #self.iter = None
390
391 def __iter__(self):
392 return self
393
394 def next(self):
395 """Return the next item."""
396
397 if self.data_index >= len(self.data):
398 raise StopIteration
399 else:
400 d = self.data[self.data_index]
401 self.data_index += 1
402 return d
403
404 # if self.iter is None:
405 # try:
406 # self.data_item = self.data_iter.next()
407 # self.iter = iter(self.data_item)
408 # except TypeError:
409 # return self.data_item
410
411 # try:
412 # return self.iter.next()
413 # except StopIteration:
414 # self.iter = None
415 # return self.next()
416
417 class ClassGroupProperties:
418 """Represents the properties of a single Classification Group.
419
420 These are used when rendering a layer."""
421
422 def __init__(self, props = None):
423 """Constructor.
424
425 props -- a ClassGroupProperties object. The class is copied if
426 prop is not None. Otherwise, a default set of properties
427 is created such that: line color = Color.Black, line width = 1,
428 and fill color = Color.Transparent
429 """
430
431 #self.stroke = None
432 #self.strokeWidth = 0
433 #self.fill = None
434
435 if props is not None:
436 self.SetProperties(props)
437 else:
438 self.SetLineColor(Color.Black)
439 self.SetLineWidth(1)
440 self.SetFill(Color.Transparent)
441
442 def SetProperties(self, props):
443 """Set this class's properties to those in class props."""
444
445 assert isinstance(props, ClassGroupProperties)
446 self.SetLineColor(props.GetLineColor())
447 self.SetLineWidth(props.GetLineWidth())
448 self.SetFill(props.GetFill())
449
450 def GetLineColor(self):
451 """Return the line color as a Color object."""
452 return self.__stroke
453
454 def SetLineColor(self, color):
455 """Set the line color.
456
457 color -- the color of the line. This must be a Color object.
458 """
459
460 assert isinstance(color, Color)
461 self.__stroke = color
462
463 def GetLineWidth(self):
464 """Return the line width."""
465 return self.__strokeWidth
466
467 def SetLineWidth(self, lineWidth):
468 """Set the line width.
469
470 lineWidth -- the new line width. This must be > 0.
471 """
472 assert isinstance(lineWidth, IntType)
473 if (lineWidth < 1):
474 raise ValueError(_("lineWidth < 1"))
475
476 self.__strokeWidth = lineWidth
477
478 def GetFill(self):
479 """Return the fill color as a Color object."""
480 return self.__fill
481
482 def SetFill(self, fill):
483 """Set the fill color.
484
485 fill -- the color of the fill. This must be a Color object.
486 """
487
488 assert isinstance(fill, Color)
489 self.__fill = fill
490
491 def __eq__(self, other):
492 """Return true if 'props' has the same attributes as this class"""
493
494 #
495 # using 'is' over '==' results in a huge performance gain
496 # in the renderer
497 #
498 return isinstance(other, ClassGroupProperties) \
499 and (self.__stroke is other.__stroke or \
500 self.__stroke == other.__stroke) \
501 and (self.__fill is other.__fill or \
502 self.__fill == other.__fill) \
503 and self.__strokeWidth == other.__strokeWidth
504
505 def __ne__(self, other):
506 return not self.__eq__(other)
507
508 def __copy__(self):
509 return ClassGroupProperties(self)
510
511 def __deepcopy__(self):
512 return ClassGroupProperties(self)
513
514 def __repr__(self):
515 return repr((self.__stroke, self.__strokeWidth, self.__fill))
516
517 class ClassGroup:
518 """A base class for all Groups within a Classification"""
519
520 def __init__(self, label = "", props = None, group = None):
521 """Constructor.
522
523 label -- A string representing the Group's label
524 """
525
526 if group is not None:
527 self.SetLabel(copy.copy(group.GetLabel()))
528 self.SetProperties(copy.copy(group.GetProperties()))
529 self.SetVisible(group.IsVisible())
530 else:
531 self.SetLabel(label)
532 self.SetProperties(props)
533 self.SetVisible(True)
534
535 def GetLabel(self):
536 """Return the Group's label."""
537 return self.label
538
539 def SetLabel(self, label):
540 """Set the Group's label.
541
542 label -- a string representing the Group's label. This must
543 not be None.
544 """
545 assert isinstance(label, StringTypes)
546 self.label = label
547
548 def GetDisplayText(self):
549 assert False, "GetDisplay must be overridden by subclass!"
550 return ""
551
552 def Matches(self, value):
553 """Determines if this Group is associated with the given value.
554
555 Returns False. This needs to be overridden by all subclasses.
556 """
557 assert False, "GetMatches must be overridden by subclass!"
558 return False
559
560 def GetProperties(self):
561 """Return the properties associated with the given value."""
562
563 return self.prop
564
565 def SetProperties(self, prop):
566 """Set the properties associated with this Group.
567
568 prop -- a ClassGroupProperties object. if prop is None,
569 a default set of properties is created.
570 """
571
572 if prop is None: prop = ClassGroupProperties()
573 assert isinstance(prop, ClassGroupProperties)
574 self.prop = prop
575
576 def IsVisible(self):
577 return self.visible
578
579 def SetVisible(self, visible):
580 self.visible = visible
581
582 def __eq__(self, other):
583 return isinstance(other, ClassGroup) \
584 and self.label == other.label \
585 and self.GetProperties() == other.GetProperties()
586
587 def __ne__(self, other):
588 return not self.__eq__(other)
589
590 def __repr__(self):
591 return "'" + self.label + "', " + repr(self.GetProperties())
592
593 class ClassGroupSingleton(ClassGroup):
594 """A Group that is associated with a single value."""
595
596 def __init__(self, value = 0, props = None, label = "", group = None):
597 """Constructor.
598
599 value -- the associated value.
600
601 prop -- a ClassGroupProperites object. If prop is None a default
602 set of properties is created.
603
604 label -- a label for this group.
605 """
606 ClassGroup.__init__(self, label, props, group)
607
608 self.SetValue(value)
609
610 def __copy__(self):
611 return ClassGroupSingleton(self.GetValue(),
612 self.GetProperties(),
613 self.GetLabel())
614
615 def __deepcopy__(self, memo):
616 return ClassGroupSingleton(self.GetValue(), group = self)
617
618 def GetValue(self):
619 """Return the associated value."""
620 return self.__value
621
622 def SetValue(self, value):
623 """Associate this Group with the given value."""
624 self.__value = value
625
626 def Matches(self, value):
627 """Determine if the given value matches the associated Group value."""
628
629 """Returns True if the value matches, False otherwise."""
630
631 return self.__value == value
632
633 def GetDisplayText(self):
634 label = self.GetLabel()
635
636 if label != "": return label
637
638 return str(self.GetValue())
639
640 def __eq__(self, other):
641 return ClassGroup.__eq__(self, other) \
642 and isinstance(other, ClassGroupSingleton) \
643 and self.__value == other.__value
644
645 def __repr__(self):
646 return "(" + self.__value + ", " + ClassGroup.__repr__(self) + ")"
647
648 class ClassGroupDefault(ClassGroup):
649 """The default Group. When values do not match any other
650 Group within a Classification, the properties from this
651 class are used."""
652
653 def __init__(self, props = None, label = "", group = None):
654 """Constructor.
655
656 prop -- a ClassGroupProperites object. If prop is None a default
657 set of properties is created.
658
659 label -- a label for this group.
660 """
661
662 ClassGroup.__init__(self, label, props, group)
663
664 def __copy__(self):
665 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
666
667 def __deepcopy__(self, memo):
668 return ClassGroupDefault(label = self.GetLabel(), group = self)
669
670 def Matches(self, value):
671 return True
672
673 def GetDisplayText(self):
674 label = self.GetLabel()
675
676 if label != "": return label
677
678 return _("DEFAULT")
679
680 def __eq__(self, other):
681 return ClassGroup.__eq__(self, other) \
682 and isinstance(other, ClassGroupDefault) \
683 and self.GetProperties() == other.GetProperties()
684
685 def __repr__(self):
686 return "(" + ClassGroup.__repr__(self) + ")"
687
688 class ClassGroupRange(ClassGroup):
689 """A Group that represents a range of values that map to the same
690 set of properties."""
691
692 def __init__(self, min = 0, max = 1, props = None, label = "", group=None):
693 """Constructor.
694
695 The minumum value must be strictly less than the maximum.
696
697 min -- the minimum range value
698
699 max -- the maximum range value
700
701 prop -- a ClassGroupProperites object. If prop is None a default
702 set of properties is created.
703
704 label -- a label for this group.
705 """
706
707 ClassGroup.__init__(self, label, props, group)
708
709 self.__min = self.__max = 0
710
711 self.SetRange(min, max)
712
713 def __copy__(self):
714 return ClassGroupRange(self.GetMin(),
715 self.GetMax(),
716 self.GetProperties(),
717 self.GetLabel())
718
719 def __deepcopy__(self, memo):
720 return ClassGroupRange(copy.copy(self.GetMin()),
721 copy.copy(self.GetMax()),
722 group = self)
723
724 def GetMin(self):
725 """Return the range's minimum value."""
726 return self.__min
727
728 def SetMin(self, min):
729 """Set the range's minimum value.
730
731 min -- the new minimum. Note that this must be less than the current
732 maximum value. Use SetRange() to change both min and max values.
733 """
734
735 self.SetRange(min, self.__max)
736
737 def GetMax(self):
738 """Return the range's maximum value."""
739 return self.__max
740
741 def SetMax(self, max):
742 """Set the range's maximum value.
743
744 max -- the new maximum. Note that this must be greater than the current
745 minimum value. Use SetRange() to change both min and max values.
746 """
747 self.SetRange(self.__min, max)
748
749 def SetRange(self, min, max):
750 """Set a new range.
751
752 Note that min must be strictly less than max.
753
754 min -- the new minimum value
755 min -- the new maximum value
756 """
757
758 if min >= max:
759 raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
760 (min, max))
761 self.__min = min
762 self.__max = max
763
764 def GetRange(self):
765 """Return the range as a tuple (min, max)"""
766 return (self.__min, self.__max)
767
768 def Matches(self, value):
769 """Determine if the given value lies with the current range.
770
771 The following check is used: min <= value < max.
772 """
773
774 return self.__min <= value < self.__max
775
776 def GetDisplayText(self):
777 label = self.GetLabel()
778
779 if label != "": return label
780
781 return _("%s - %s") % (self.GetMin(), self.GetMax())
782
783 def __eq__(self, other):
784 return ClassGroup.__eq__(self, other) \
785 and isinstance(other, ClassGroupRange) \
786 and self.__min == other.__min \
787 and self.__max == other.__max
788
789 def __repr__(self):
790 return "(" + str(self.__min) + ", " + str(self.__max) + ", " + \
791 ClassGroup.__repr__(self) + ")"
792
793 class ClassGroupMap(ClassGroup):
794 """Currently, this class is not used."""
795
796 FUNC_ID = "id"
797
798 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
799 ClassGroup.__init__(self, label)
800
801 self.map_type = map_type
802 self.func = func
803
804 if self.func is None:
805 self.func = func_id
806
807 def Map(self, value):
808 return self.func(value)
809
810 def GetProperties(self):
811 return None
812
813 def GetPropertiesFromValue(self, value):
814 pass
815
816 def GetDisplayText(self):
817 return "Map: " + self.map_type
818
819 #
820 # built-in mappings
821 #
822 def func_id(value):
823 return value
824

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26