/[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 1176 by jonathan, Thu Jun 12 15:46:22 2003 UTC revision 2374 by jan, Sun Oct 3 21:01:31 2004 UTC
# Line 20  See the description of FindGroup() for m Line 20  See the description of FindGroup() for m
20  on the mapping algorithm.  on the mapping algorithm.
21  """  """
22        
23  # fix for people using python2.1  import copy, operator, types
 from __future__ import nested_scopes  
   
 import copy, operator  
24    
25  from Thuban import _  from Thuban import _
26    
 import types  
   
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  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 47  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    
         self.__setLayerLock = False  
   
53          self.SetDefaultGroup(ClassGroupDefault())          self.SetDefaultGroup(ClassGroupDefault())
54    
         self.SetLayer(layer)  
         self.SetField(field)  
   
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 86  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 SetField(self, field):  
         """Set the name of the data table field to use.  
           
         If there is no layer then the field type is set to None,  
         otherwise the layer is queried to find the type of the  
         field data  
   
         field -- if None then all values map to the default data  
         """  
   
         if field == "":  
             field = None  
   
   
         if field is None:  
             if self.layer is not None:  
                 self.fieldType = None  
         else:  
             if self.layer is not None:  
                 fieldType = self.layer.GetFieldType(field)  
                 if fieldType is None:  
                     raise ValueError("'%s' was not found in the layer's table."  
                                      % self.field)  
   
                 #  
                 # unfortunately we cannot call SetFieldType() because it  
                 # requires the layer to be None  
                 #  
                 self.fieldType = fieldType  
                 #self.SetFieldType(fieldType)  
   
         self.field = field  
   
         self.__SendNotification()  
   
     def GetField(self):  
         """Return the name of the field."""  
         return self.field  
71    
72      def GetFieldType(self):      def __getattr__(self, attr):
73          """Return the field type."""          """Generate the compiled classification on demand"""
74          return self.fieldType          if attr == "_compiled_classification":
75                self._compile_classification()
76      def SetFieldType(self, type):              return self._compiled_classification
77          """Set the type of the field used by this classification.          raise AttributeError(attr)
78    
79          A ValueError is raised if the owning layer is not None and      def _compile_classification(self):
80          'type' is different from the current field type.          """Generate the compiled classification
81          """  
82            The compiled classification is a more compact representation of
83          if type != self.fieldType:          the classification groups that is also more efficient for
84              if self.layer is not None:          performing the classification.
85                  raise ValueError()  
86              else:          The compiled classification is a list of tuples. The first
87                  self.fieldType = type          element of the tuple is a string which describes the rest of the
88                  self.__SendNotification()          tuple. There are two kinds of tuples:
89    
90      def SetLayer(self, layer):            'singletons'
91          """Set the owning Layer of this classification.  
92                The second element of the tuple is a dictionary which
93          A ValueError exception will be thrown either the field or              combines several consecutive ClassGroupSingleton instances.
94          field type mismatch the information in the layer's table.              The dictionary maps the values of the singletons (as
95          """              returned by the GetValue() method) to the corresponding
96                group.
97          # prevent infinite recursion when calling SetClassification()  
98          if self.__setLayerLock: return            'range'
99    
100          self.__setLayerLock = True              The tuple describes a ClassGroupRange instance. The tuples
101                second element is a tuple fo the form (lfunc, min, max,
102          if layer is None:              rfunc, group) where group is the original group object,
103              if self.layer is not None:              lfunc and rfuct are comparison functions and min and max are
104                  l = self.layer              lower and upper bounds of the range. Given a value and such
105                  self.layer = None              a tuple the group matches if and only if
106                  l.SetClassification(None)  
107          else:                  lfunc(min, value) and rfunc(max, value)
108              assert isinstance(layer, Thuban.Model.layer.Layer)  
109                is true.
110              old_layer = self.layer  
111            The compiled classification is bound to
112              self.layer = layer          self._compile_classification.
113            """
114              try:          compiled = []
115                  self.SetField(self.GetField()) # this sync's the fieldType          for group in self.__groups[1:]:
116              except ValueError:              if isinstance(group, ClassGroupSingleton):
117                  self.layer = old_layer                  if not compiled or compiled[-1][0] != "singletons":
118                  self.__setLayerLock = False                      compiled.append(("singletons", {}))
119                  raise ValueError                  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:              else:
132                  self.layer.SetClassification(self)                  raise TypeError("Unknown group type %s", group)
133            self._compiled_classification = compiled
134    
135          self.__setLayerLock = False      def _clear_compiled_classification(self):
136            """Reset the compiled classification.
137    
138      def GetLayer(self):          If will be created on demand when self._compiled_classification
139          """Return the parent layer."""          is accessed again.
         return self.layer  
140    
141            Call this method whenever self.__groups is modified.
142            """
143            try:
144                del self._compiled_classification
145            except:
146                pass
147    
148      #      #
149      # these SetDefault* methods are really only provided for      # these SetDefault* methods are really only provided for
# Line 243  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 263  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 287  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 287  class Classification:
287          items = []          items = []
288    
289          def build_color_item(text, color):          def build_color_item(text, color):
290              if color is Color.Transparent:              if color is Transparent:
291                  return ("%s: %s" % (text, _("None")), None)                  return ("%s: %s" % (text, _("None")), None)
292    
293              return ("%s: (%.3f, %.3f, %.3f)" %              return ("%s: (%.3f, %.3f, %.3f)" %
# Line 353  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:
320      """Allows the Groups in a Classifcation to be interated over.      """Allows the Groups in a Classifcation to be interated over.
321    
# Line 374  class ClassIterator: Line 327  class ClassIterator:
327          """Constructor.          """Constructor.
328    
329          default -- the default group          default -- the default group
330    
331          points -- a list of singleton groups          points -- a list of singleton groups
332    
333          ranges -- a list of range groups          ranges -- a list of range groups
334    
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 399  class ClassIterator: Line 350  class ClassIterator:
350              d = self.data[self.data_index]              d = self.data[self.data_index]
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      
357      These are used when rendering a layer."""      These are used when rendering a layer."""
358    
359        # TODO: Actually, size is only relevant for point objects.
360        # Eventually it should be spearated, e.g. when introducing symbols.
361    
362      def __init__(self, props = None):      def __init__(self, props = None):
363          """Constructor.          """Constructor.
364    
365          props -- a ClassGroupProperties object. The class is copied if          props -- a ClassGroupProperties object. The class is copied if
366                   prop is not None. Otherwise, a default set of properties                   prop is not None. Otherwise, a default set of properties
367                   is created such that: line color = Color.Black, line width = 1,                   is created such that: line color = Black, line width = 1,
368                   and fill color = Color.Transparent                   size = 5 and fill color = Transparent
369          """          """
370    
         #self.stroke = None  
         #self.strokeWidth = 0  
         #self.fill = None  
   
371          if props is not None:          if props is not None:
372              self.SetProperties(props)              self.SetProperties(props)
373          else:          else:
374              self.SetLineColor(Color.Black)              self.SetLineColor(Black)
375              self.SetLineWidth(1)              self.SetLineWidth(1)
376              self.SetFill(Color.Transparent)              self.SetSize(5)
377                self.SetFill(Transparent)
378    
379      def SetProperties(self, props):      def SetProperties(self, props):
380          """Set this class's properties to those in class props."""          """Set this class's properties to those in class props."""
# Line 444  class ClassGroupProperties: Line 382  class ClassGroupProperties:
382          assert isinstance(props, ClassGroupProperties)          assert isinstance(props, ClassGroupProperties)
383          self.SetLineColor(props.GetLineColor())          self.SetLineColor(props.GetLineColor())
384          self.SetLineWidth(props.GetLineWidth())          self.SetLineWidth(props.GetLineWidth())
385            self.SetSize(props.GetSize())
386          self.SetFill(props.GetFill())          self.SetFill(props.GetFill())
387            
388      def GetLineColor(self):      def GetLineColor(self):
389          """Return the line color as a Color object."""          """Return the line color as a Color object."""
390          return self.__stroke          return self.__stroke
# Line 473  class ClassGroupProperties: Line 412  class ClassGroupProperties:
412    
413          self.__strokeWidth = lineWidth          self.__strokeWidth = lineWidth
414    
415        def GetSize(self):
416            """Return the size."""
417            return self.__size
418    
419        def SetSize(self, size):
420            """Set the size.
421    
422            size -- the new size. This must be > 0.
423            """
424            assert isinstance(size, types.IntType)
425            if (size < 1):
426                raise ValueError(_("size < 1"))
427    
428            self.__size = size
429    
430      def GetFill(self):      def GetFill(self):
431          """Return the fill color as a Color object."""          """Return the fill color as a Color object."""
432          return self.__fill          return self.__fill
433    
434      def SetFill(self, fill):      def SetFill(self, fill):
435          """Set the fill color.          """Set the fill color.
436    
# Line 497  class ClassGroupProperties: Line 451  class ClassGroupProperties:
451                   self.__stroke == other.__stroke)        \                   self.__stroke == other.__stroke)        \
452              and (self.__fill is other.__fill or          \              and (self.__fill is other.__fill or          \
453                   self.__fill == other.__fill)            \                   self.__fill == other.__fill)            \
454              and self.__strokeWidth == other.__strokeWidth              and self.__strokeWidth == other.__strokeWidth\
455                and self.__size == other.__size
456    
457      def __ne__(self, other):      def __ne__(self, other):
458          return not self.__eq__(other)          return not self.__eq__(other)
# Line 509  class ClassGroupProperties: Line 464  class ClassGroupProperties:
464          return ClassGroupProperties(self)          return ClassGroupProperties(self)
465    
466      def __repr__(self):      def __repr__(self):
467          return repr((self.__stroke, self.__strokeWidth, self.__fill))          return repr((self.__stroke, self.__strokeWidth, self.__size,
468                        self.__fill))
469    
470  class ClassGroup:  class ClassGroup:
471      """A base class for all Groups within a Classification"""      """A base class for all Groups within a Classification"""
# Line 532  class ClassGroup: Line 488  class ClassGroup:
488      def GetLabel(self):      def GetLabel(self):
489          """Return the Group's label."""          """Return the Group's label."""
490          return self.label          return self.label
491    
492      def SetLabel(self, label):      def SetLabel(self, label):
493          """Set the Group's label.          """Set the Group's label.
494    
# Line 686  class ClassGroupRange(ClassGroup): Line 642  class ClassGroupRange(ClassGroup):
642      """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
643         set of properties."""         set of properties."""
644    
645      def __init__(self, min = 0, max = 1, props = None, label = "", group=None):      def __init__(self, _range = (0,1), props = None, label = "", group=None):
646          """Constructor.          """Constructor.
647    
648          The minumum value must be strictly less than the maximum.          The minumum value must be strictly less than the maximum.
649    
650          min -- the minimum range value          _range -- either a tuple (min, max) where min < max or
651                      a Range object
         max -- the maximum range value  
652    
653          prop -- a ClassGroupProperites object. If prop is None a default          prop -- a ClassGroupProperites object. If prop is None a default
654                   set of properties is created.                   set of properties is created.
# Line 702  class ClassGroupRange(ClassGroup): Line 657  class ClassGroupRange(ClassGroup):
657          """          """
658    
659          ClassGroup.__init__(self, label, props, group)          ClassGroup.__init__(self, label, props, group)
660            self.SetRange(_range)
         #self.__min = self.__max = 0  
         #self.__range = Range("[" + repr(float(min)) + ";" +  
                                    #repr(float(max)) + "[")  
         self.SetRange(min, max)  
661    
662      def __copy__(self):      def __copy__(self):
663          return ClassGroupRange(min = self.__range,          return ClassGroupRange(self.__range,
                                max = None,  
664                                 props = self.GetProperties(),                                 props = self.GetProperties(),
665                                 label = self.GetLabel())                                 label = self.GetLabel())
666    
667      def __deepcopy__(self, memo):      def __deepcopy__(self, memo):
668          return ClassGroupRange(min = copy.copy(self.__range),          return ClassGroupRange(copy.copy(self.__range),
                                max = copy.copy(self.GetMax()),  
669                                 group = self)                                 group = self)
670    
671      def GetMin(self):      def GetMin(self):
# Line 730  class ClassGroupRange(ClassGroup): Line 679  class ClassGroupRange(ClassGroup):
679                 maximum value. Use SetRange() to change both min and max values.                 maximum value. Use SetRange() to change both min and max values.
680          """          """
681            
682          self.SetRange(min, self.__range.GetRange()[2])          self.SetRange((min, self.__range.GetRange()[2]))
683    
684      def GetMax(self):      def GetMax(self):
685          """Return the range's maximum value."""          """Return the range's maximum value."""
# Line 742  class ClassGroupRange(ClassGroup): Line 691  class ClassGroupRange(ClassGroup):
691          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
692                 minimum value. Use SetRange() to change both min and max values.                 minimum value. Use SetRange() to change both min and max values.
693          """          """
694          self.SetRange(self.__range.GetRange()[1], max)          self.SetRange((self.__range.GetRange()[1], max))
695    
696      def SetRange(self, min, max = None):      def SetRange(self, _range):
697          """Set a new range.          """Set a new range.
698    
699          Note that min must be strictly less than max.          _range -- Either a tuple (min, max) where min < max or
700                      a Range object.
701    
702          min -- the new minimum value          Raises ValueError on error.
         min -- the new maximum value  
703          """          """
704    
705          if isinstance(min, Range):          if isinstance(_range, Range):
706              self.__range = min              self.__range = _range
707            elif isinstance(_range, types.TupleType) and len(_range) == 2:
708                self.__range = Range(("[", _range[0], _range[1], "["))
709          else:          else:
710              if max is None:              raise ValueError()
                 raise ValueError()  
   
             self.__range = Range(("[", min, max, "["))  
711    
712      def GetRange(self):      def GetRange(self):
713          """Return the range as a string"""          """Return the range as a string"""
         #return (self.__min, self.__max)  
714          return self.__range.string(self.__range.GetRange())          return self.__range.string(self.__range.GetRange())
715    
716        def GetRangeTuple(self):
717            return self.__range.GetRange()
718    
719      def Matches(self, value):      def Matches(self, value):
720          """Determine if the given value lies with the current range.          """Determine if the given value lies with the current range.
721    
# Line 773  class ClassGroupRange(ClassGroup): Line 723  class ClassGroupRange(ClassGroup):
723          """          """
724    
725          return operator.contains(self.__range, value)          return operator.contains(self.__range, value)
         #return self.__min <= value < self.__max  
726    
727      def GetDisplayText(self):      def GetDisplayText(self):
728          label = self.GetLabel()          label = self.GetLabel()
729    
730          if label != "": return label          if label != "": return label
731    
         #return _("%s - %s") % (self.GetMin(), self.GetMax())  
         #return repr(self.__range)  
732          return self.__range.string(self.__range.GetRange())          return self.__range.string(self.__range.GetRange())
733    
734      def __eq__(self, other):      def __eq__(self, other):
735          return ClassGroup.__eq__(self, other) \          return ClassGroup.__eq__(self, other) \
736              and isinstance(other, ClassGroupRange) \              and isinstance(other, ClassGroupRange) \
737              and self.__range == other.__range              and self.__range == other.__range
             #and self.__min == other.__min \  
             #and self.__max == other.__max  
738    
739      def __repr__(self):      def __repr__(self):
740          return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"          return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
         #return "(" + repr(self.__min) + ", " + repr(self.__max) + ", " + \  
                #ClassGroup.__repr__(self) + ")"  
741    
742  class ClassGroupMap(ClassGroup):  class ClassGroupMap(ClassGroup):
743      """Currently, this class is not used."""      """Currently, this class is not used."""

Legend:
Removed from v.1176  
changed lines
  Added in v.2374

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26