/[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 613 - (show annotations)
Mon Apr 7 08:55:55 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: 24389 byte(s)
(Classification.AppendGroup,
        Classification.InsertGroup, Classification.ReplaceGroup,
        Classification.RemoveGroup, Classification.GetGroup): Do as the
        names imply.
(Classification.FindGroup): This was called GetGroup, but GetGroup
        takes an index, while FindGroup takes a value.
(Classification.__deepcopy__): Copy all the groups, BUT NOT THE LAYER
        REFERENCE. Currently there is a cyclic reference between the layer
        and its classification. If the classification doesn't need to know
        its owning layer we can change this, since it may make sense to be
        able to use the same classification with different layers.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26