/[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 410 by jonathan, Wed Feb 19 16:51:12 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 16  an input value falls with a range that d Line 16  an input value falls with a range that d
16  If no mapping can be found then default 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  # fix for people using python2.1
24  from __future__ import nested_scopes  from __future__ import nested_scopes
25    
26    from types import *
27    
28  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \
29       LAYER_VISIBILITY_CHANGED       LAYER_VISIBILITY_CHANGED
30    
31  from Thuban import _  from Thuban import _
32  from Thuban.Model.color import Color  from Thuban.Model.color import Color
33    
34  from wxPython.wx import *  import Thuban.Model.layer
35    
36    
37  # constants  # constants
38  RANGE_MIN  = 0  RANGE_MIN  = 0
# Line 37  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, layer = None, field = None):
49          """Initialize a classification.          """Initialize a classification.
50    
51             layer -- the layer object who owns this classification             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.layer = layer          self.layer = None
58          self.points = {}          self.field = None
59          self.ranges = []          self.fieldType = None
60          self.maps   = []          self.groups = []
61          self.DefaultData = ClassDataDefault()          self.__sendMessages = False
62          self.field = field  
63          #self.SetField(field)          self.__ToggleMessages(False)
64            self.SetDefaultGroup(ClassGroupDefault())
65      def SendMessage(self, message):          self.SetLayer(layer)
66          if self.layer is not None:          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)              self.layer.changed(message, self.layer)
80            
81      def SetField(self, field):      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 the default data             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          self.SendMessage(LAYER_LEGEND_CHANGED)  
92            if self.layer is not None:
93                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):      def GetField(self):
104            """Return the name of the field."""
105          return self.field          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):      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          self.layer = layer
122          self.SendMessage(LAYER_LEGEND_CHANGED)          self.SetField(self.GetField()) # XXX: this sync's the fieldType
123    
124            self.__SendMessage(LAYER_LEGEND_CHANGED)
125    
126      def GetLayer(self):      def GetLayer(self):
127          return layer.self          """Return the parent layer."""
128            return self.layer
129    
130      def SetDefaultData(self, data):      def SetDefaultGroup(self, group):
131          """Set the data to be used when a value can't be classified.          """Set the group to be used when a value can't be classified.
132    
133             data -- data that the value maps to. See class description.             group -- group that the value maps to.
134          """          """
135    
136          self.DefaultData = data          assert(isinstance(group, ClassGroupDefault))
137            self.AddGroup(group)
138    
139      def GetDefaultData(self):      def GetDefaultGroup(self):
140          return self.DefaultData          """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      def SetDefaultFill(self, fill):      def SetDefaultFill(self, fill):
150          self.DefaultData.SetFill(fill)          """Set the default fill color.
151          self.SendMessage(LAYER_LEGEND_CHANGED)  
152            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):      def GetDefaultFill(self):
159          return self.DefaultData.GetFill()          """Return the default fill color."""
160            return self.GetDefaultGroup().GetProperties().GetFill()
161                    
162      def SetDefaultStroke(self, stroke):      def SetDefaultLineColor(self, color):
163          self.DefaultData.SetStroke(stroke)          """Set the default line color.
164          self.SendMessage(LAYER_LEGEND_CHANGED)  
165            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 GetDefaultStroke(self):      def GetDefaultLineColor(self):
172          return self.DefaultData.GetStroke()          """Return the default line color."""
173            return self.GetDefaultGroup().GetProperties().GetLineColor()
174                    
175      def SetDefaultStrokeWidth(self, strokeWidth):      def SetDefaultLineWidth(self, lineWidth):
176          self.DefaultData.SetStrokeWidth(strokeWidth)          """Set the default line width.
177          self.SendMessage(LAYER_LEGEND_CHANGED)  
178            lineWidth -- an integer > 0.
179            """
180            assert(isinstance(lineWidth, IntType))
181            self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
182            self.__SendMessage(LAYER_LEGEND_CHANGED)
183                    
184      def GetDefaultStrokeWidth(self):      def GetDefaultLineWidth(self):
185          return self.DefaultData.GetStrokeWidth()          """Return the default line width."""
186            return self.GetDefaultGroup().GetProperties().GetLineWidth()
187                    
188      def AddClassData(self, item):      def AddGroup(self, item):
189          type = item.GetType()          """Add a new ClassGroup item to the classification.
190    
191            item -- this must be a valid ClassGroup object
192            """
193    
194          if type == ClassData.POINT:          assert(isinstance(item, ClassGroup))
195              self.points[item.GetValue()] = item  
196          elif type == ClassData.RANGE:          if len(self.groups) > 0 and isinstance(item, ClassGroupDefault):
197              self.ranges.append(item)              self.groups[0] = item
198          elif type == ClassData.MAP:              #self.SetDefaultGroup(item)
             self.maps.append(item)  
         elif type == ClassData.DEFAULT:  
             self.DefaultData = item  
199          else:          else:
200              raise ValueError(_("Unrecognized ClassData type %s") % type)              self.groups.append(item)
201    
202          self.SendMessage(LAYER_LEGEND_CHANGED)          self.__SendMessage(LAYER_LEGEND_CHANGED)
203    
204      def GetProperties(self, value):      def GetGroup(self, value):
205          """Return the associated data, or the default data.          """Return the associated group, or the default group.
206    
207             The following search technique is used:             Groups are checked in the order the were added to the
208                 (1) if the field is None, return the default data             Classification.
                (2) check if the value exists as a single value  
                (3) check if the value falls within a range. Ranges  
                    are checked in the order they were added to  
                    the classification.  
209    
210             value -- the value to classify. If there is no mapping,             value -- the value to classify. If there is no mapping,
211                      or value is None, return the default data                      or value is None, return the default properties
                     (which may be None)  
212          """          """
213    
214          if self.field is not None and value is not None:          if self.field is not None and value is not None:
             #  
             # check the discrete values  
             #  
             if self.points.has_key(value):  
                 return self.points[value]  
   
             #  
             # check the ranges  
             #  
             for p in self.ranges:  
                 if p.InRange(value):  
                     return p  
   
             #  
             # check the maps  
             #  
             for p in self.maps:  
                 try:  
                     return p.Map(value)  
                 except: pass  
215    
216          return self.DefaultData              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):      def TreeInfo(self):
233          items = []          items = []
# Line 176  class Classification: Line 240  class Classification:
240                      (text, color.red, color.green, color.blue),                      (text, color.red, color.green, color.blue),
241                      color)                      color)
242    
243          def build_item(data, string):          def build_item(group, string):
244              label = data.GetLabel()              label = group.GetLabel()
245              if label == "":              if label == "":
246                  label = string                  label = string
247              else:              else:
248                  label += " (%s)" % string                  label += " (%s)" % string
249    
250                props = group.GetProperties()
251              i = []              i = []
252              v = data.GetStroke()              v = props.GetLineColor()
253              i.append(build_color_item(_("Stroke"), v))              i.append(build_color_item(_("Line Color"), v))
254              v = data.GetStrokeWidth()              v = props.GetLineWidth()
255              i.append(_("Stroke Width: %s") % v)              i.append(_("Line Width: %s") % v)
256              v = data.GetFill()              v = props.GetFill()
257              i.append(build_color_item(_("Fill"), v))              i.append(build_color_item(_("Fill"), v))
258              return (label, i)              return (label, i)
259    
260          items.append(build_item(self.DefaultData, _("'DEFAULT'")))          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          for p in self.points.values():      The items are returned in the following order:
275              items.append(build_item(p, str(p.GetValue())))          default data, singletons, ranges, maps
276        """
277    
278          for p in self.ranges:      def __init__(self, data): #default, points, ranges, maps):
279              items.append(build_item(p, "%s - %s" % (p.GetMin(), p.GetMax())))          """Constructor.
280    
281          return (_("Classifications"), items)          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  class ClassData:      def __iter__(self):
296            return self
297    
298      INVALID = -1      def next(self):
299      DEFAULT = 0          """Return the next item."""
300      POINT = 1  
301      RANGE = 2          if self.data_index >= len(self.data):
302      MAP   = 3              raise StopIteration
   
     def __init__(self, type = INVALID, classData = None):  
   
         if classData is not None:  
             self.SetStroke(classData.GetStroke())  
             self.SetStrokeWidth(classData.GetStrokeWidth())  
             self.SetFill(classData.GetFill())  
303          else:          else:
304              self.SetStroke(Color.None)              d = self.data[self.data_index]
305              self.SetStrokeWidth(1)              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)              self.SetFill(Color.None)
344    
345          self.type = type      def SetProperties(self, props):
346          self.label = ""          """Set this class's properties to those in class props."""
       
     def GetType(self):  
         return self.type  
347    
348      def GetStroke(self):          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          return self.stroke
356    
357      def SetStroke(self, stroke):      def SetLineColor(self, color):
358          assert(isinstance(stroke, Color))          """Set the line color.
359          self.stroke = stroke  
360            color -- the color of the line. This must be a Color object.
361      def GetStrokeWidth(self):          """
362          return self.stroke_width  
363            assert(isinstance(color, Color))
364      def SetStrokeWidth(self, stroke_width):          self.stroke = color
365          if (stroke_width < 1):  
366              raise ValueError(_("stroke_width < 1"))      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          self.stroke_width = stroke_width          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):      def GetFill(self):
382            """Return the fill color as a Color object."""
383          return self.fill          return self.fill
384    
385      def SetFill(self, fill):      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          assert(isinstance(fill, Color))          assert(isinstance(fill, Color))
392          self.fill = fill          self.fill = fill
393    
394    
395    class ClassGroup:
396        """A base class for all Groups within a Classification"""
397    
398        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):      def GetLabel(self):
409            """Return the Group's label."""
410          return self.label          return self.label
411    
412      def SetLabel(self, label):      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          self.label = label
420    
421  class ClassDataDefault(ClassData):      def Matches(self, value):
422      def __init__(self, classData = None):          """Determines if this Group is associated with the given value.
423          ClassData.__init__(self, ClassData.DEFAULT, classData)  
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 ClassDataPoint(ClassData):  class ClassGroupSingleton(ClassGroup):
437        """A Group that is associated with a single value."""
438    
439      def __init__(self, value = 0, classData = None):      def __init__(self, value = 0, prop = None, label = ""):
440          ClassData.__init__(self, ClassData.POINT, classData)          """Constructor.
441    
442          self.value = value          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):      def GetValue(self):
461            """Return the associated value."""
462          return self.value          return self.value
463    
464      def SetValue(self, value):      def SetValue(self, value):
465            """Associate this Group with the given value."""
466          self.value = value          self.value = value
467    
468  class ClassDataRange(ClassData):      def Matches(self, value):
469            """Determine if the given value matches the associated Group value."""
470    
471      def __init__(self, min = 0, max = 1, classData = None):          """Returns True if the value matches, False otherwise."""
         ClassData.__init__(self, ClassData.RANGE, classData)  
472    
473          if min >= max:          return self.value == value
474              raise ValueError(_("ClassDataRange: %i(min) >= %i(max)!") %  
475                               (min, max))      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            ClassGroup.__init__(self, label)
535    
536            self.min = self.max = 0
537            self.prop = None
538    
539          self.SetRange(min, max)          self.SetRange(min, max)
540            self.SetProperties(prop)
541    
542        def __copy__(self):
543            return ClassGroupRange(self.min, self.max, self.prop, self.label)
544    
545      def GetMin(self):      def GetMin(self):
546            """Return the range's minimum value."""
547          return self.min          return self.min
548    
549      def SetMin(self, min):      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)          self.SetRange(min, self.max)
557    
558      def GetMax(self):      def GetMax(self):
559            """Return the range's maximum value."""
560          return self.max          return self.max
561    
562      def SetMax(self, max):      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)          self.SetRange(self.min, max)
569    
570      def SetRange(self, min, max):      def SetRange(self, min, max):
571          self.min = min          """Set a new range.
572          self.max = max  
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:          if min >= max:
580              raise ValueError(_("ClassDataRange: %i(min) >= %i(max)!") %              raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
581                               (min, max))                               (min, max))
582            self.min = min
583            self.max = max
584    
585      def GetRange(self):      def GetRange(self):
586            """Return the range as a tuple (min, max)"""
587          return (self.min, self.max)          return (self.min, self.max)
588    
589      def InRange(self, value):      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          return self.min <= value < self.max
596    
597  class ClassDataMap(ClassData):      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"      FUNC_ID = "id"
615    
616      def __init__(self, map_type = FUNC_ID, func = None, classData = None):      def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
617          ClassData.__init__(self, ClassData.MAP, classData)          ClassGroup.__init__(self, label)
618    
619          self.map_type = map_type          self.map_type = map_type
620          self.func = func          self.func = func
# Line 326  class ClassDataMap(ClassData): Line 625  class ClassDataMap(ClassData):
625      def Map(self, value):      def Map(self, value):
626          return self.func(value)          return self.func(value)
627    
628        def GetProperties(self):
629            return None
630    
631        def GetPropertiesFromValue(self, value):
632            pass
633    
634      #      #
635      # built-in mappings      # built-in mappings
636      #      #

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26