/[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 397 by jonathan, Tue Feb 11 14:23:32 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, 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.DefaultData = ClassData()          self.groups = []
61          self.field = field          self.__sendMessages = False
62          #self.SetField(field)  
63            self.__ToggleMessages(False)
64      def SetField(self, field):          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 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.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
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 SetDefaultData(self, data):      def GetFieldType(self):
108          """Set the data to be used when a value can't be classified.          """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            self.__SendMessage(LAYER_LEGEND_CHANGED)
125    
126             data -- data that the value maps to. See class description.      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.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.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
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.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
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.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
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 AddRange(self, min, max, data):      def AddGroup(self, item):
189          """Add a new range to the classification.          """Add a new ClassGroup item to the classification.
190    
191             A range allows a value to be classified if it falls between          item -- this must be a valid ClassGroup object
            min and max. Specifically, min <= value < max  
           
            min -- the lower bound.  
   
            max -- the upper bound.  
   
            data -- data that the value maps to. See class description.  
192          """          """
193    
194          if min >= max:          assert(isinstance(item, ClassGroup))
             raise ValueError(_("Range minimum >= maximum!"))  
         self.ranges.append([min, max, data])  
         self.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
195    
196      def AddPoint(self, value, data):          if len(self.groups) > 0 and isinstance(item, ClassGroupDefault):
197          """Associate a single value with data.              self.groups[0] = item
198                #self.SetDefaultGroup(item)
199            else:
200                self.groups.append(item)
201    
202             When this value is to be classified data will be returned.          self.__SendMessage(LAYER_LEGEND_CHANGED)
203    
204             value -- classification value.      def GetGroup(self, value):
205            """Return the associated group, or the default group.
206    
207             data  -- data that the value maps to. See class description.             Groups are checked in the order the were added to the
208          """             Classification.
   
         self.points[value] = data  
         self.layer.changed(LAYER_LEGEND_CHANGED, self.layer)  
   
     def GetProperties(self, value):  
         """Return the associated data, or the default data.  
   
            The following search technique is used:  
                (1) if the field is None, return the default data  
                (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:
             #  
             # first check the discrete values  
             #  
             if self.points.has_key(value):  
                 return self.points[value]  
   
             #  
             # now check the ranges  
             #  
             for p in self.ranges:  
                 if (p[RANGE_MIN] <= value) and (value < p[RANGE_MAX]):  
                     return p[RANGE_DATA]  
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.DefaultData          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 = []
234    
235          #          def build_color_item(text, color):
236          # XXX: shouldn't print anything if there are no classifications              if color is Color.None:
237          #                  return ("%s: %s" % (text, _("None")), None)
238    
239                        return ("%s: (%.3f, %.3f, %.3f)" %
240          def color_string(color):                      (text, color.red, color.green, color.blue),
241              if color is None:                      color)
242                  return "None"  
243              return "(%.3f, %.3f, %.3f)" % (color.red, color.green, color.blue)          def build_item(group, string):
244                label = group.GetLabel()
245                if label == "":
246                    label = string
247                else:
248                    label += " (%s)" % string
249    
250          def build_item(data):              props = group.GetProperties()
251              i = []              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              v = data.GetStroke()          return (_("Classification"), items)
270              i.append((_("Stroke: %s") % color_string(v), v))  
271              v = data.GetStrokeWidth()  class ClassIterator:
272              i.append((_("Stroke Width: %s") % v))      """Allows the Groups in a Classifcation to be interated over.
             v = data.GetFill()  
             i.append((_("Fill: %s") % color_string(v), v))  
             return i  
273    
274          items.append((_("'DEFAULT'"), build_item(self.DefaultData)))      The items are returned in the following order:
275            default data, singletons, ranges, maps
276        """
277    
278          for name, data in self.points.items():      def __init__(self, data): #default, points, ranges, maps):
279              items.append((_("%s") % name, build_item(data)))          """Constructor.
280    
281          for p in self.ranges:          default -- the default group
282              data = p[RANGE_DATA]  
283              items.append((_("%s-%s") % (p[RANGE_MIN], p[RANGE_MAX])),          points -- a list of singleton groups
                          build_item(data))  
284    
285          return (_("Classifications"), items)          ranges -- a list of range groups
286    
287            maps -- a list of map groups
288            """
289    
290  class ClassData:          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    
     def __init__(self):  
334          self.stroke = None          self.stroke = None
335          self.stroke_width = 0          self.strokeWidth = 0
336          self.fill = None          self.fill = None
337          self.label = ""  
338                if props is not None:
339      def GetStroke(self):              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          return self.stroke
356    
357      def SetStroke(self, stroke):      def SetLineColor(self, color):
358          self.stroke = stroke          """Set the line color.
359    
360            color -- the color of the line. This must be a Color object.
361            """
362    
363      def GetStrokeWidth(self):          assert(isinstance(color, Color))
364          return self.stroke_width          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      def SetStrokeWidth(self, stroke_width):          self.strokeWidth = lineWidth
         self.stroke_width = stroke_width  
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))
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        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            ClassGroup.__init__(self, label)
535    
536            self.min = self.max = 0
537            self.prop = None
538    
539            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):
546            """Return the range's minimum value."""
547            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        def GetMax(self):
559            """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.func is None:
623                self.func = func_id
624    
625        def Map(self, value):
626            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
636        #
637        def func_id(value):
638            return value
639    

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26