/[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 627 - (show annotations)
Wed Apr 9 10:08:47 2003 UTC (21 years, 11 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 24491 byte(s)
(Classification.__deepcopy__): Need to copy over field and fieldType attributes.

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 return isinstance(other, ClassGroupProperties) \
495 and self.stroke == other.GetLineColor() \
496 and self.strokeWidth == other.GetLineWidth() \
497 and self.fill == other.GetFill()
498
499 def __ne__(self, other):
500 return not self.__eq__(other)
501
502 def __copy__(self):
503 return ClassGroupProperties(self)
504
505 def __deepcopy__(self):
506 return ClassGroupProperties(self)
507
508 class ClassGroup:
509 """A base class for all Groups within a Classification"""
510
511 def __init__(self, label = ""):
512 """Constructor.
513
514 label -- A string representing the Group's label
515 """
516
517 self.label = None
518
519 self.SetLabel(label)
520
521 def GetLabel(self):
522 """Return the Group's label."""
523 return self.label
524
525 def SetLabel(self, label):
526 """Set the Group's label.
527
528 label -- a string representing the Group's label. This must
529 not be None.
530 """
531 assert isinstance(label, StringType)
532 self.label = label
533
534 def GetDisplayText(self):
535 assert False, "GetDisplay must be overridden by subclass!"
536 return ""
537
538 def Matches(self, value):
539 """Determines if this Group is associated with the given value.
540
541 Returns False. This needs to be overridden by all subclasses.
542 """
543 assert False, "GetMatches must be overridden by subclass!"
544 return False
545
546 def GetProperties(self):
547 """Return the properties associated with the given value.
548
549 Returns None. This needs to be overridden by all subclasses.
550 """
551 assert False, "GetProperties must be overridden by subclass!"
552 return None
553
554
555 class ClassGroupSingleton(ClassGroup):
556 """A Group that is associated with a single value."""
557
558 def __init__(self, value = 0, prop = None, label = ""):
559 """Constructor.
560
561 value -- the associated value.
562
563 prop -- a ClassGroupProperites object. If prop is None a default
564 set of properties is created.
565
566 label -- a label for this group.
567 """
568 ClassGroup.__init__(self, label)
569
570 self.prop = None
571 self.value = None
572
573 self.SetValue(value)
574 self.SetProperties(prop)
575
576 def __copy__(self):
577 return ClassGroupSingleton(self.GetValue(),
578 self.GetProperties(),
579 self.GetLabel())
580
581 def __deepcopy__(self, memo):
582 return ClassGroupSingleton(copy.copy(self.GetValue()),
583 copy.copy(self.GetProperties()),
584 copy.copy(self.GetLabel()))
585
586 def GetValue(self):
587 """Return the associated value."""
588 return self.value
589
590 def SetValue(self, value):
591 """Associate this Group with the given value."""
592 self.value = value
593
594 def Matches(self, value):
595 """Determine if the given value matches the associated Group value."""
596
597 """Returns True if the value matches, False otherwise."""
598
599 return self.value == value
600
601 def GetProperties(self):
602 """Return the Properties associated with this Group."""
603
604 return self.prop
605
606 def SetProperties(self, prop):
607 """Set the properties associated with this Group.
608
609 prop -- a ClassGroupProperties object. if prop is None,
610 a default set of properties is created.
611 """
612
613 if prop is None: prop = ClassGroupProperties()
614 assert isinstance(prop, ClassGroupProperties)
615 self.prop = prop
616
617 def GetDisplayText(self):
618 label = self.GetLabel()
619
620 if label != "": return label
621
622 return str(self.GetValue())
623
624 def __eq__(self, other):
625 return isinstance(other, ClassGroupSingleton) \
626 and self.GetProperties() == other.GetProperties() \
627 and self.GetValue() == other.GetValue()
628
629 def __ne__(self, other):
630 return not self.__eq__(other)
631
632 class ClassGroupDefault(ClassGroup):
633 """The default Group. When values do not match any other
634 Group within a Classification, the properties from this
635 class are used."""
636
637 def __init__(self, prop = None, label = ""):
638 """Constructor.
639
640 prop -- a ClassGroupProperites object. If prop is None a default
641 set of properties is created.
642
643 label -- a label for this group.
644 """
645
646 ClassGroup.__init__(self, label)
647 self.SetProperties(prop)
648
649 def __copy__(self):
650 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
651
652 def __deepcopy__(self, memo):
653 return ClassGroupDefault(copy.copy(self.GetProperties()),
654 copy.copy(self.GetLabel()))
655
656 def Matches(self, value):
657 return True
658
659 def GetProperties(self):
660 """Return the Properties associated with this Group."""
661 return self.prop
662
663 def SetProperties(self, prop):
664 """Set the properties associated with this Group.
665
666 prop -- a ClassGroupProperties object. if prop is None,
667 a default set of properties is created.
668 """
669
670 if prop is None: prop = ClassGroupProperties()
671 assert isinstance(prop, ClassGroupProperties)
672 self.prop = prop
673
674 def GetDisplayText(self):
675 label = self.GetLabel()
676
677 if label != "": return label
678
679 return _("DEFAULT")
680
681 def __eq__(self, other):
682 return isinstance(other, ClassGroupDefault) \
683 and self.GetProperties() == other.GetProperties()
684
685 def __ne__(self, other):
686 return not self.__eq__(other)
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, prop = None, label = ""):
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)
708
709 self.min = self.max = 0
710 self.prop = None
711
712 self.SetRange(min, max)
713 self.SetProperties(prop)
714
715 def __copy__(self):
716 return ClassGroupRange(self.GetMin(),
717 self.GetMax(),
718 self.GetProperties(),
719 self.GetLabel())
720
721 def __deepcopy__(self, memo):
722 return ClassGroupRange(copy.copy(self.GetMin()),
723 copy.copy(self.GetMax()),
724 copy.copy(self.GetProperties()),
725 copy.copy(self.GetLabel()))
726
727 def GetMin(self):
728 """Return the range's minimum value."""
729 return self.min
730
731 def SetMin(self, min):
732 """Set the range's minimum value.
733
734 min -- the new minimum. Note that this must be less than the current
735 maximum value. Use SetRange() to change both min and max values.
736 """
737
738 self.SetRange(min, self.max)
739
740 def GetMax(self):
741 """Return the range's maximum value."""
742 return self.max
743
744 def SetMax(self, max):
745 """Set the range's maximum value.
746
747 max -- the new maximum. Note that this must be greater than the current
748 minimum value. Use SetRange() to change both min and max values.
749 """
750 self.SetRange(self.min, max)
751
752 def SetRange(self, min, max):
753 """Set a new range.
754
755 Note that min must be strictly less than max.
756
757 min -- the new minimum value
758 min -- the new maximum value
759 """
760
761 if min >= max:
762 raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
763 (min, max))
764 self.min = min
765 self.max = max
766
767 def GetRange(self):
768 """Return the range as a tuple (min, max)"""
769 return (self.min, self.max)
770
771 def Matches(self, value):
772 """Determine if the given value lies with the current range.
773
774 The following check is used: min <= value < max.
775 """
776
777 return self.min <= value < self.max
778
779 def GetProperties(self):
780 """Return the Properties associated with this Group."""
781 return self.prop
782
783 def SetProperties(self, prop):
784 """Set the properties associated with this Group.
785
786 prop -- a ClassGroupProperties object. if prop is None,
787 a default set of properties is created.
788 """
789 if prop is None: prop = ClassGroupProperties()
790 assert isinstance(prop, ClassGroupProperties)
791 self.prop = prop
792
793 def GetDisplayText(self):
794 label = self.GetLabel()
795
796 if label != "": return label
797
798 return _("%s - %s") % (self.GetMin(), self.GetMax())
799
800 def __eq__(self, other):
801 return isinstance(other, ClassGroupRange) \
802 and self.GetProperties() == other.GetProperties() \
803 and self.GetRange() == other.GetRange()
804
805 def __ne__(self, other):
806 return not self.__eq__(other)
807
808 class ClassGroupMap(ClassGroup):
809 """Currently, this class is not used."""
810
811 FUNC_ID = "id"
812
813 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
814 ClassGroup.__init__(self, label)
815
816 self.map_type = map_type
817 self.func = func
818
819 if self.func is None:
820 self.func = func_id
821
822 def Map(self, value):
823 return self.func(value)
824
825 def GetProperties(self):
826 return None
827
828 def GetPropertiesFromValue(self, value):
829 pass
830
831 def GetDisplayText(self):
832 return "Map: " + self.map_type
833
834 #
835 # built-in mappings
836 #
837 def func_id(value):
838 return value
839

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26