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

Legend:
Removed from v.371  
changed lines
  Added in v.491

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26