/[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 385 by jonathan, Mon Feb 3 11:43:56 2003 UTC revision 453 by bh, Tue Mar 4 11:31:39 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  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 32  RANGE_DATA = 2 Line 41  RANGE_DATA = 2
41    
42  class Classification:  class Classification:
43    
44        def __init__(self, layer = None, field = None):
     def __init__(self, field = None):  
45          """Initialize a classification.          """Initialize a classification.
46    
47               layer -- the layer object who owns this classification
48    
49             field -- the name of the data table field that             field -- the name of the data table field that
50                      is to be used to classify layer properties                      is to be used to classify layer properties
51          """          """
52    
53          self.points = {}          self.layer = None # stop message sending
54    
55            self.SetDefaultGroup(ClassGroupDefault())
56            self.SetField(field)
57    
58            self.layer = layer
59            self.points = []
60          self.ranges = []          self.ranges = []
61          self.setField(field)          self.maps   = []
62          self.setNull(None)  
63                def __iter__(self):
64      def setField(self, field):          return ClassIterator(self.DefaultGroup,
65                                 self.points,
66                                 self.ranges,
67                                 self.maps)
68    
69        def __SendMessage(self, message):
70            if self.layer is not None:
71                self.layer.changed(message, self.layer)
72        
73        def SetField(self, field):
74          """Set the name of the data table field to use.          """Set the name of the data table field to use.
75                    
76             field -- if None then all values map to NullData             field -- if None then all values map to the default data
77          """          """
78    
79          self.field = field          if field == "":
80                field = None
     def setNull(self, data):  
         """Set the data to be used when a value can't be classified.  
81    
82             data -- data that the value maps to. See class description.          self.field = field
83          """          self.__SendMessage(LAYER_LEGEND_CHANGED)
   
         self.NullData = data  
84    
85      def getNull(self):      def GetField(self):
86          return self.NullData          return self.field
87    
88      def addRange(self, min, max, data):      def SetLayer(self, layer):
89          """Add a new range to the classification.          assert(isinstance(layer, Thuban.Model.layer.Layer))
90            self.layer = layer
91            self.__SendMessage(LAYER_LEGEND_CHANGED)
92    
93             A range allows a value to be classified if it falls between      def GetLayer(self):
94             min and max. Specifically, min <= value < max          return layer.self
           
            min -- the lower bound.  
95    
96             max -- the upper bound.      def SetDefaultGroup(self, group):
97            """Set the group to be used when a value can't be classified.
98    
99             data -- data that the value maps to. See class description.             group -- group that the value maps to. See class description.
100          """          """
101    
102          if min >= max:          assert(isinstance(group, ClassGroupDefault))
103              raise ValueError(_("Range minimum >= maximum!"))          self.DefaultGroup = group
         self.ranges.append([min, max, data])  
   
     def addPoint(self, value, data):  
         """Associate a single value with data.  
104    
105             When this value is to be classified data will be returned.      def GetDefaultGroup(self):
106            return self.DefaultGroup
107    
108             value -- classification value.      #
109        # these SetDefault* methods are really only provided for
110        # some backward compatibility. they should be considered
111        # for removal once all the classification code is finished.
112        #
113    
114        def SetDefaultFill(self, fill):
115            assert(isinstance(fill, Color))
116            self.DefaultGroup.GetProperties().SetFill(fill)
117            self.__SendMessage(LAYER_LEGEND_CHANGED)
118            
119        def GetDefaultFill(self):
120            return self.DefaultGroup.GetProperties().GetFill()
121            
122        def SetDefaultStroke(self, stroke):
123            assert(isinstance(stroke, Color))
124            self.DefaultGroup.GetProperties().SetStroke(stroke)
125            self.__SendMessage(LAYER_LEGEND_CHANGED)
126            
127        def GetDefaultStroke(self):
128            return self.DefaultGroup.GetProperties().GetStroke()
129            
130        def SetDefaultStrokeWidth(self, strokeWidth):
131            assert(isinstance(strokeWidth, IntType))
132            self.DefaultGroup.GetProperties().SetStrokeWidth(strokeWidth)
133            self.__SendMessage(LAYER_LEGEND_CHANGED)
134            
135        def GetDefaultStrokeWidth(self):
136            return self.DefaultGroup.GetProperties().GetStrokeWidth()
137            
138        def AddGroup(self, item):
139            assert(isinstance(item, ClassGroup))
140    
141             data  -- data that the value maps to. See class description.          if isinstance(item, ClassGroupDefault):
142          """              self.SetDefaultGroup(item)
143            elif isinstance(item, ClassGroupSingleton):
144                self.points.append(item)
145            elif isinstance(item, ClassGroupRange):
146                self.ranges.append(item)
147            elif isinstance(item, ClassGroupMap):
148                self.maps.append(item)
149            else:
150                raise ValueError(_("Unrecognized ClassGroup"))
151    
152          self.points[value] = data          self.__SendMessage(LAYER_LEGEND_CHANGED)
153    
154      def getProperties(self, value):      def GetGroup(self, value):
155          """Return the associated data, or the NullData.          """Return the associated data, or the default data.
156    
157             The following search technique is used:             The following search technique is used:
158                 (1) if the field is None, return NullData                 (1) if the field is None, return the default data
159                 (2) check if the value exists as a single value                 (2) check if the value exists as a single value
160                 (3) check if the value falls within a range. Ranges                 (3) check if the value falls within a range. Ranges
161                     are checked in the order they were added to                     are checked in the order they were added to
162                     the classification.                     the classification.
163    
164             value -- the value to classify. If there is no mapping             value -- the value to classify. If there is no mapping,
165                      return the NullData (which may be None)                      or value is None, return the default properties
166          """          """
167    
168          if self.field 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]  
169    
170                for p in self:
171                    if p.Matches(value):
172                        return p
173    #           #
174    #           # check the discrete values
175    #           #
176    #           if self.points.has_key(value):
177    #               return self.points[value]
178    #           #for p in self.points:
179    #               #if p.Value
180    
181    #           #
182    #           # check the ranges
183    #           #
184    #           for p in self.ranges:
185    #               if p.InRange(value):
186    #                   return p
187    
188    #           #
189    #           # check the maps
190    #           #
191    #           for p in self.maps:
192    #               try:
193    #                   return p.Map(value)
194    #               except: pass
195    
196          return self.NullData          return self.DefaultGroup
197    
198        def GetProperties(self, value):
199            return self.GetGroup(value).GetProperties()
200    
201      def TreeInfo(self):      def TreeInfo(self):
202          items = []          items = []
203    
204          #          def build_color_item(text, color):
205          # shouldn't print anything if there are no classifications              if color is Color.None:
206          #                  return ("%s: %s" % (text, _("None")), None)
207    
208                        return ("%s: (%.3f, %.3f, %.3f)" %
209          def color_string(color):                      (text, color.red, color.green, color.blue),
210              if color is None:                      color)
211                  return "None"  
212              return "(%.3f, %.3f, %.3f)" % (color.red, color.green, color.blue)          def build_item(group, string):
213                label = group.GetLabel()
214                if label == "":
215                    label = string
216                else:
217                    label += " (%s)" % string
218    
219          if self.NullData is not None:              props = group.GetProperties()
220              i = []              i = []
221              for key, value in self.NullData.items():              v = props.GetStroke()
222                  if isinstance(value, Color):              i.append(build_color_item(_("Stroke"), v))
223                      i.append((_("%s: %s") % (key, color_string(value)), value))              v = props.GetStrokeWidth()
224                  else:              i.append(_("Stroke Width: %s") % v)
225                      i.append(_("%s: %s") % (key, value))              v = props.GetFill()
226              items.append((_("'NULL'"), i))              i.append(build_color_item(_("Fill"), v))
227                return (label, i)
228    
229            for p in self:
230                if isinstance(p, ClassGroupDefault):
231                    items.append(build_item(self.DefaultGroup, _("'DEFAULT'")))
232                elif isinstance(p, ClassGroupSingleton):
233                    items.append(build_item(p, str(p.GetValue())))
234                elif isinstance(p, ClassGroupRange):
235                    items.append(build_item(p, "%s - %s" %
236                                               (p.GetMin(), p.GetMax())))
237    
238          for name, data in self.points.items():  #       for p in self.points.values():
239              i = []  #           items.append(build_item(p, str(p.GetValue())))
             for key, value in data.items():  
                 if isinstance(value, Color):  
                     i.append((_("%s: %s") % (key, color_string(value)), value))  
                 else:  
                     i.append(_("%s: %s") % (key, value))  
             items.append((_("%s") % name, i))  
240    
241          for p in self.ranges:  #       for p in self.ranges:
242              i = []  #           items.append(build_item(p, "%s - %s" % (p.GetMin(), p.GetMax())))
243              data = p[RANGE_DATA]  
244              for key, value in data.items():          return (_("Classification"), items)
245                  if isinstance(value, Color):  
246                      i.append((_("%s: %s") % (key, color_string(value)), value))  class ClassIterator:
247                  else:  
248                      i.append(_("%s: %s") % (key, value))      def __init__(self, default, points, ranges, maps):
249              items.append((_("%s-%s") % (p[RANGE_MIN], p[RANGE_MAX], i)))          self.data = [default, points, ranges, maps]
250                  self.data_iter = iter(self.data)
251          return (_("Classifications"), items)          self.iter = None
252    
253        def __iter__(self):
254            return self
255    
256        def next(self):
257            if self.iter is None:
258                try:
259                    self.data_item = self.data_iter.next()
260                    self.iter = iter(self.data_item)
261                except TypeError:
262                    return self.data_item
263    
264            try:
265                return self.iter.next()
266            except StopIteration:
267                self.iter = None
268                return self.next()
269          
270    class ClassGroupProperties:
271    
272        def __init__(self, prop = None):
273    
274            if prop is not None:
275                self.SetStroke(prop.GetStroke())
276                self.SetStrokeWidth(prop.GetStrokeWidth())
277                self.SetFill(prop.GetFill())
278            else:
279                self.SetStroke(Color.None)
280                self.SetStrokeWidth(1)
281                self.SetFill(Color.None)
282    
283        def GetStroke(self):
284            return self.stroke
285    
286        def SetStroke(self, stroke):
287            assert(isinstance(stroke, Color))
288            self.stroke = stroke
289    
290        def GetStrokeWidth(self):
291            return self.stroke_width
292    
293        def SetStrokeWidth(self, stroke_width):
294            assert(isinstance(stroke_width, IntType))
295            if (stroke_width < 1):
296                raise ValueError(_("stroke_width < 1"))
297    
298            self.stroke_width = stroke_width
299    
300        def GetFill(self):
301            return self.fill
302    
303        def SetFill(self, fill):
304            assert(isinstance(fill, Color))
305            self.fill = fill
306    
307    
308    class ClassGroup:
309    
310        def __init__(self, label = ""):
311            self.label = label
312    
313        def GetLabel(self):
314            return self.label
315    
316        def SetLabel(self, label):
317            self.label = label
318    
319        def Matches(self, value):
320            """This needs to be implemented by all subclasses."""
321            pass
322    
323        def GetProperties(self, value):
324            """This needs to be implemented by all subclasses."""
325            pass
326    
327        
328    class ClassGroupSingleton(ClassGroup):
329    
330        def __init__(self, value = 0, prop = None, label = ""):
331            ClassGroup.__init__(self, label)
332    
333            self.SetValue(value)
334            self.SetProperties(prop)
335    
336        def __copy__(self):
337            return ClassGroupSingleton(self.value, self.prop, self.label)
338    
339        def GetValue(self):
340            return self.value
341    
342        def SetValue(self, value):
343            self.value = value
344    
345        def Matches(self, value):
346            return self.value == value
347    
348        def GetProperties(self, value = None):
349            if value is None: return self.prop
350    
351            if self.Matches(value):
352                return self.prop
353            else:
354                return None
355    
356        def SetProperties(self, prop):
357            if prop is None: prop = ClassGroupProperties()
358            assert(isinstance(prop, ClassGroupProperties))
359            self.prop = prop
360    
361    
362    class ClassGroupDefault(ClassGroupSingleton):
363        def __init__(self, prop = None, label = ""):
364            ClassGroupSingleton.__init__(self, 0, prop, label)
365    
366        def __copy__(self):
367            return ClassGroupDefault(self.prop, self.label)
368    
369        def GetProperties(self, value = None):
370            return self.prop
371    
372    class ClassGroupRange(ClassGroup):
373    
374        def __init__(self, min = 0, max = 1, prop = None, label = ""):
375            ClassGroup.__init__(self, label)
376    
377            self.SetRange(min, max)
378            self.SetProperties(prop)
379    
380        def __copy__(self):
381            return ClassGroupRange(self.min, self.max, self.prop, self.label)
382    
383        def GetMin(self):
384            return self.min
385    
386        def SetMin(self, min):
387            self.SetRange(min, self.max)
388    
389        def GetMax(self):
390            return self.max
391    
392        def SetMax(self, max):
393            self.SetRange(self.min, max)
394    
395        def SetRange(self, min, max):
396            if min >= max:
397                raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
398                                 (min, max))
399            self.min = min
400            self.max = max
401    
402        def GetRange(self):
403            return (self.min, self.max)
404    
405        def Matches(self, value):
406            return self.min <= value < self.max
407    
408        def GetProperties(self, value = None):
409            if value is None: return self.prop
410    
411            if self.Matches(value):
412                return self.prop
413            else:
414                return None
415    
416        def SetProperties(self, prop):
417            if prop is None: prop = ClassGroupProperties()
418            assert(isinstance(prop, ClassGroupProperties))
419            self.prop = prop
420    
421    class ClassGroupMap(ClassGroup):
422    
423        FUNC_ID = "id"
424    
425        def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
426            ClassGroup.__init__(self, prop)
427    
428            self.map_type = map_type
429            self.func = func
430    
431            if self.func is None:
432                self.func = func_id
433    
434        def Map(self, value):
435            return self.func(value)
436    
437        #
438        # built-in mappings
439        #
440        def func_id(value):
441            return value
442    

Legend:
Removed from v.385  
changed lines
  Added in v.453

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26