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

Legend:
Removed from v.378  
changed lines
  Added in v.462

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26