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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26