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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1351 by jonathan, Tue Jul 1 16:17:02 2003 UTC revision 1912 by bh, Mon Nov 3 13:55:41 2003 UTC
# Line 27  from Thuban import _ Line 27  from Thuban import _
27  from messages import \  from messages import \
28      LAYER_PROJECTION_CHANGED, \      LAYER_PROJECTION_CHANGED, \
29      LAYER_LEGEND_CHANGED, \      LAYER_LEGEND_CHANGED, \
30      LAYER_VISIBILITY_CHANGED      LAYER_VISIBILITY_CHANGED,\
31        CLASS_CHANGED
32    
33  from Thuban.Model.color import Color, Transparent, Black  from Thuban.Model.color import Color, Transparent, Black
34  from Thuban.Model.range import Range  from Thuban.Model.range import Range
35    
36  import Thuban.Model.layer  import Thuban.Model.layer
37    
38  class Classification:  from Thuban.Lib.connector import Publisher
39    
40    class Classification(Publisher):
41      """Encapsulates the classification of layer.      """Encapsulates the classification of layer.
42            
43      The Classification divides some kind of data into Groups which      The Classification divides some kind of data into Groups which
# Line 42  class Classification: Line 45  class Classification:
45      retrieved by matching data values to the appropriate group.      retrieved by matching data values to the appropriate group.
46      """      """
47    
48      def __init__(self, layer = None, field = None):      def __init__(self):
49          """Initialize a classification.          """Initialize a classification."""
   
         layer -- the Layer object who owns this classification  
   
         field -- the name of the data table field that  
                  is to be used to classify layer properties  
         """  
50    
         self.layer = None  
         self.field = None  
         self.fieldType = None  
51          self.__groups = []          self.__groups = []
52    
53          self.SetDefaultGroup(ClassGroupDefault())          self.SetDefaultGroup(ClassGroupDefault())
54    
         self.SetFieldInfo(field, None)  
   
         self._set_layer(layer)  
   
55      def __iter__(self):      def __iter__(self):
56          return ClassIterator(self.__groups)          return ClassIterator(self.__groups)
57    
58      def __deepcopy__(self, memo):      def __deepcopy__(self, memo):
59          clazz = Classification()          clazz = Classification()
60    
         # note: the only thing that isn't copied is the layer reference  
         clazz.field = self.field  
         clazz.fieldType = self.fieldType  
61          clazz.__groups[0] = copy.deepcopy(self.__groups[0])          clazz.__groups[0] = copy.deepcopy(self.__groups[0])
62    
63          for i in range(1, len(self.__groups)):          for i in range(1, len(self.__groups)):
# Line 80  class Classification: Line 67  class Classification:
67    
68      def __SendNotification(self):      def __SendNotification(self):
69          """Notify the layer that this class has changed."""          """Notify the layer that this class has changed."""
70          if self.layer is not None:          self.issue(CLASS_CHANGED)
             self.layer.ClassChanged()  
       
     def GetField(self):  
         """Return the name of the field."""  
         return self.field  
   
     def GetFieldType(self):  
         """Return the field type."""  
         return self.fieldType  
   
     def SetFieldInfo(self, name, type):  
         """Set the classified field name to 'name' and it's field  
         type to 'type'  
   
         If this classification has an owning layer a ValueError  
         exception will be thrown either the field or field type  
         mismatch the information in the layer's table.  
   
         If the field info is successful set and the class has  
         an owning layer, the layer will be informed that the  
         classification has changed.  
         """  
   
         if name == "":  
             name = None  
   
         if self.layer is None:  
             self.field = name  
             self.fieldType = type  
         elif name is None:  
             self.field = None  
             self.fieldType = None  
         else:  
             #  
             # verify that the field exists in the layer and that  
             # the type is correct.  
             #  
             fieldType = self.layer.GetFieldType(name)  
             if fieldType is None:  
                 raise ValueError("'%s' was not found in the layer's table."  
                                  % self.field)  
             elif type is not None and fieldType != type:  
                 raise ValueError("type doesn't match layer's field type for %s"  
                                  % self.field)  
71    
72              self.field = name      def __getattr__(self, attr):
73              self.fieldType = fieldType          """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          self.__SendNotification()      def _clear_compiled_classification(self):
136            """Reset the compiled classification.
137    
138      def _set_layer(self, layer):          If will be created on demand when self._compiled_classification
139          """Internal: Set the owning Layer of this classification.          is accessed again.
   
         A ValueError exception will be thrown either the field or  
         field type mismatch the information in the layer's table.  
140    
141          If the layer is successful set, the layer will be informed          Call this method whenever self.__groups is modified.
         that the classification has changed.  
142          """          """
143            try:
144          if layer is None:              del self._compiled_classification
145              self.layer = None          except:
146          else:              pass
             old_layer = self.layer  
             self.layer = layer  
   
             try:  
                 # this sync's the fieldType  
                 # and sends a notification to the layer  
                 self.SetFieldInfo(self.GetField(), None)  
             except ValueError:  
                 self.layer = old_layer  
                 raise ValueError  
   
     def GetLayer(self):  
         """Return the parent layer."""  
         return self.layer  
147    
148      #      #
149      # these SetDefault* methods are really only provided for      # these SetDefault* methods are really only provided for
# Line 216  class Classification: Line 202  class Classification:
202    
203          group -- group that the value maps to.          group -- group that the value maps to.
204          """          """
   
205          assert isinstance(group, ClassGroupDefault)          assert isinstance(group, ClassGroupDefault)
206          if len(self.__groups) > 0:          if len(self.__groups) > 0:
207              self.__groups[0] = group              self.__groups[0] = group
208          else:          else:
209              self.__groups.append(group)              self.__groups.append(group)
210            self.__SendNotification()
211    
212      def GetDefaultGroup(self):      def GetDefaultGroup(self):
213          """Return the default group."""          """Return the default group."""
# Line 236  class Classification: Line 222  class Classification:
222          self.InsertGroup(self.GetNumGroups(), item)          self.InsertGroup(self.GetNumGroups(), item)
223    
224      def InsertGroup(self, index, group):      def InsertGroup(self, index, group):
           
225          assert isinstance(group, ClassGroup)          assert isinstance(group, ClassGroup)
   
226          self.__groups.insert(index + 1, group)          self.__groups.insert(index + 1, group)
227            self._clear_compiled_classification()
228          self.__SendNotification()          self.__SendNotification()
229    
230      def RemoveGroup(self, index):      def RemoveGroup(self, index):
231          return self.__groups.pop(index + 1)          """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):      def ReplaceGroup(self, index, group):
237          assert isinstance(group, ClassGroup)          assert isinstance(group, ClassGroup)
   
238          self.__groups[index + 1] = group          self.__groups[index + 1] = group
239            self._clear_compiled_classification()
240          self.__SendNotification()          self.__SendNotification()
241    
242      def GetGroup(self, index):      def GetGroup(self, index):
# Line 260  class Classification: Line 246  class Classification:
246          """Return the number of non-default groups in the classification."""          """Return the number of non-default groups in the classification."""
247          return len(self.__groups) - 1          return len(self.__groups) - 1
248    
   
249      def FindGroup(self, value):      def FindGroup(self, value):
250          """Return the associated group, or the default group.          """Return the group that matches the value.
251    
252          Groups are checked in the order the were added to the          Groups are effectively checked in the order the were added to
253          Classification.          the Classification.
254    
255          value -- the value to classify. If there is no mapping,          value -- the value to classify. If there is no mapping or value
256                   the field is None or value is None,                   is None, return the default properties
                  return the default properties  
257          """          """
258    
259          if self.GetField() is not None and value is not None:          if value is not None:
260                for typ, params in self._compiled_classification:
261              for i in range(1, len(self.__groups)):                  if typ == "singletons":
262                  group = self.__groups[i]                      group = params.get(value)
263                  if group.Matches(value):                      if group is not None:
264                      return group                          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()          return self.GetDefaultGroup()
271    
# Line 326  class Classification: Line 314  class Classification:
314          for p in self:          for p in self:
315              items.append(build_item(p, p.GetDisplayText()))              items.append(build_item(p, p.GetDisplayText()))
316    
 #           if isinstance(p, ClassGroupDefault):  
 #               items.append(build_item(self.GetDefaultGroup(), _("'DEFAULT'")))  
 #           elif isinstance(p, ClassGroupSingleton):  
 #               items.append(build_item(p, str(p.GetValue())))  
 #           elif isinstance(p, ClassGroupRange):  
 #               items.append(build_item(p, "%s - %s" %  
 #                                          (p.GetMin(), p.GetMax())))  
   
317          return (_("Classification"), items)          return (_("Classification"), items)
318    
319  class ClassIterator:  class ClassIterator:
# Line 355  class ClassIterator: Line 335  class ClassIterator:
335          maps -- a list of map groups          maps -- a list of map groups
336          """          """
337    
338          self.data = data #[default, points, ranges, maps]          self.data = data
339          self.data_index = 0          self.data_index = 0
         #self.data_iter = iter(self.data)  
         #self.iter = None  
340    
341      def __iter__(self):      def __iter__(self):
342          return self          return self
# Line 373  class ClassIterator: Line 351  class ClassIterator:
351              self.data_index += 1              self.data_index += 1
352              return d              return d
353                    
 #       if self.iter is None:  
 #           try:  
 #               self.data_item = self.data_iter.next()  
 #               self.iter = iter(self.data_item)  
 #           except TypeError:  
 #               return self.data_item  
   
 #       try:  
 #           return self.iter.next()  
 #       except StopIteration:  
 #           self.iter = None  
 #           return self.next()  
         
354  class ClassGroupProperties:  class ClassGroupProperties:
355      """Represents the properties of a single Classification Group.      """Represents the properties of a single Classification Group.
356        
# Line 400  class ClassGroupProperties: Line 365  class ClassGroupProperties:
365                   and fill color = Transparent                   and fill color = Transparent
366          """          """
367    
         #self.stroke = None  
         #self.strokeWidth = 0  
         #self.fill = None  
   
368          if props is not None:          if props is not None:
369              self.SetProperties(props)              self.SetProperties(props)
370          else:          else:
# Line 659  class ClassGroupRange(ClassGroup): Line 620  class ClassGroupRange(ClassGroup):
620      """A Group that represents a range of values that map to the same      """A Group that represents a range of values that map to the same
621         set of properties."""         set of properties."""
622    
623      def __init__(self, min = 0, max = 1, props = None, label = "", group=None):      def __init__(self, _range = (0,1), props = None, label = "", group=None):
624          """Constructor.          """Constructor.
625    
626          The minumum value must be strictly less than the maximum.          The minumum value must be strictly less than the maximum.
627    
628          min -- the minimum range value          _range -- either a tuple (min, max) where min < max or
629                      a Range object
         max -- the maximum range value  
630    
631          prop -- a ClassGroupProperites object. If prop is None a default          prop -- a ClassGroupProperites object. If prop is None a default
632                   set of properties is created.                   set of properties is created.
# Line 675  class ClassGroupRange(ClassGroup): Line 635  class ClassGroupRange(ClassGroup):
635          """          """
636    
637          ClassGroup.__init__(self, label, props, group)          ClassGroup.__init__(self, label, props, group)
638            self.SetRange(_range)
         #self.__min = self.__max = 0  
         #self.__range = Range("[" + repr(float(min)) + ";" +  
                                    #repr(float(max)) + "[")  
         self.SetRange(min, max)  
639    
640      def __copy__(self):      def __copy__(self):
641          return ClassGroupRange(min = self.__range,          return ClassGroupRange(self.__range,
                                max = None,  
642                                 props = self.GetProperties(),                                 props = self.GetProperties(),
643                                 label = self.GetLabel())                                 label = self.GetLabel())
644    
645      def __deepcopy__(self, memo):      def __deepcopy__(self, memo):
646          return ClassGroupRange(min = copy.copy(self.__range),          return ClassGroupRange(copy.copy(self.__range),
                                max = copy.copy(self.GetMax()),  
647                                 group = self)                                 group = self)
648    
649      def GetMin(self):      def GetMin(self):
# Line 703  class ClassGroupRange(ClassGroup): Line 657  class ClassGroupRange(ClassGroup):
657                 maximum value. Use SetRange() to change both min and max values.                 maximum value. Use SetRange() to change both min and max values.
658          """          """
659            
660          self.SetRange(min, self.__range.GetRange()[2])          self.SetRange((min, self.__range.GetRange()[2]))
661    
662      def GetMax(self):      def GetMax(self):
663          """Return the range's maximum value."""          """Return the range's maximum value."""
# Line 715  class ClassGroupRange(ClassGroup): Line 669  class ClassGroupRange(ClassGroup):
669          max -- the new maximum. Note that this must be greater than the current          max -- the new maximum. Note that this must be greater than the current
670                 minimum value. Use SetRange() to change both min and max values.                 minimum value. Use SetRange() to change both min and max values.
671          """          """
672          self.SetRange(self.__range.GetRange()[1], max)          self.SetRange((self.__range.GetRange()[1], max))
673    
674      def SetRange(self, min, max = None):      def SetRange(self, _range):
675          """Set a new range.          """Set a new range.
676    
677          Note that min must be strictly less than max.          _range -- Either a tuple (min, max) where min < max or
678                      a Range object.
679    
680          min -- the new minimum value          Raises ValueError on error.
         min -- the new maximum value  
681          """          """
682    
683          if isinstance(min, Range):          if isinstance(_range, Range):
684              self.__range = min              self.__range = _range
685            elif isinstance(_range, types.TupleType) and len(_range) == 2:
686                self.__range = Range(("[", _range[0], _range[1], "["))
687          else:          else:
688              if max is None:              raise ValueError()
                 raise ValueError()  
   
             self.__range = Range(("[", min, max, "["))  
689    
690      def GetRange(self):      def GetRange(self):
691          """Return the range as a string"""          """Return the range as a string"""
         #return (self.__min, self.__max)  
692          return self.__range.string(self.__range.GetRange())          return self.__range.string(self.__range.GetRange())
693    
694        def GetRangeTuple(self):
695            return self.__range.GetRange()
696    
697      def Matches(self, value):      def Matches(self, value):
698          """Determine if the given value lies with the current range.          """Determine if the given value lies with the current range.
699    
# Line 746  class ClassGroupRange(ClassGroup): Line 701  class ClassGroupRange(ClassGroup):
701          """          """
702    
703          return operator.contains(self.__range, value)          return operator.contains(self.__range, value)
         #return self.__min <= value < self.__max  
704    
705      def GetDisplayText(self):      def GetDisplayText(self):
706          label = self.GetLabel()          label = self.GetLabel()
707    
708          if label != "": return label          if label != "": return label
709    
         #return _("%s - %s") % (self.GetMin(), self.GetMax())  
         #return repr(self.__range)  
710          return self.__range.string(self.__range.GetRange())          return self.__range.string(self.__range.GetRange())
711    
712      def __eq__(self, other):      def __eq__(self, other):
713          return ClassGroup.__eq__(self, other) \          return ClassGroup.__eq__(self, other) \
714              and isinstance(other, ClassGroupRange) \              and isinstance(other, ClassGroupRange) \
715              and self.__range == other.__range              and self.__range == other.__range
             #and self.__min == other.__min \  
             #and self.__max == other.__max  
716    
717      def __repr__(self):      def __repr__(self):
718          return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"          return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
         #return "(" + repr(self.__min) + ", " + repr(self.__max) + ", " + \  
                #ClassGroup.__repr__(self) + ")"  
719    
720  class ClassGroupMap(ClassGroup):  class ClassGroupMap(ClassGroup):
721      """Currently, this class is not used."""      """Currently, this class is not used."""

Legend:
Removed from v.1351  
changed lines
  Added in v.1912

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26