/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/classification.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/classification.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1909 - (hide annotations)
Fri Oct 31 18:16:34 2003 UTC (21 years, 4 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 19732 byte(s)
(Classification.SetDefaultGroup):
Send a CLASS_CHANGED message
(Classification.RemoveGroup): Send a CLASS_CHANGED message and do
not return the removed group since it wasn't used.

1 bh 453 # Copyright (c) 2001, 2003 by Intevation GmbH
2 jonathan 371 # Authors:
3     # Jonathan Coles <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     __version__ = "$Revision$"
9    
10     """
11     A Classification provides a mapping from an input value
12     to data. This mapping can be specified in two ways.
13     First, specific values can be associated with data.
14     Second, ranges can be associated with data such that if
15     an input value falls with a range that data is returned.
16 jonathan 388 If no mapping can be found then default data will
17 jonathan 371 be returned. Input values must be hashable objects
18    
19 jonathan 613 See the description of FindGroup() for more information
20 jonathan 371 on the mapping algorithm.
21     """
22    
23 jonathan 1249 import copy, operator, types
24 jonathan 397
25 jonathan 613 from Thuban import _
26    
27     from messages import \
28     LAYER_PROJECTION_CHANGED, \
29     LAYER_LEGEND_CHANGED, \
30 jonathan 1426 LAYER_VISIBILITY_CHANGED,\
31     CLASS_CHANGED
32 jonathan 388
33 jonathan 1336 from Thuban.Model.color import Color, Transparent, Black
34 jonathan 873 from Thuban.Model.range import Range
35 jan 374
36 jonathan 436 import Thuban.Model.layer
37    
38 jonathan 1426 from Thuban.Lib.connector import Publisher
39    
40     class Classification(Publisher):
41 jonathan 613 """Encapsulates the classification of layer.
42    
43     The Classification divides some kind of data into Groups which
44     are associated with properties. Later the properties can be
45     retrieved by matching data values to the appropriate group.
46     """
47 jonathan 371
48 jonathan 1426 def __init__(self):
49     """Initialize a classification."""
50 jonathan 371
51 jonathan 613 self.__groups = []
52 jonathan 436
53     self.SetDefaultGroup(ClassGroupDefault())
54 jonathan 491
55 jonathan 428 def __iter__(self):
56 jonathan 613 return ClassIterator(self.__groups)
57 jonathan 428
58 jonathan 613 def __deepcopy__(self, memo):
59     clazz = Classification()
60    
61     clazz.__groups[0] = copy.deepcopy(self.__groups[0])
62    
63     for i in range(1, len(self.__groups)):
64     clazz.__groups.append(copy.deepcopy(self.__groups[i]))
65    
66     return clazz
67    
68 jonathan 491 def __SendNotification(self):
69     """Notify the layer that this class has changed."""
70 jonathan 1426 self.issue(CLASS_CHANGED)
71 jonathan 410
72 jonathan 428 #
73     # these SetDefault* methods are really only provided for
74     # some backward compatibility. they should be considered
75     # for removal once all the classification code is finished.
76     #
77    
78 jonathan 388 def SetDefaultFill(self, fill):
79 jonathan 462 """Set the default fill color.
80    
81     fill -- a Color object.
82     """
83     self.GetDefaultGroup().GetProperties().SetFill(fill)
84 jonathan 491 self.__SendNotification()
85 jonathan 388
86     def GetDefaultFill(self):
87 jonathan 462 """Return the default fill color."""
88     return self.GetDefaultGroup().GetProperties().GetFill()
89 jonathan 388
90 jonathan 462 def SetDefaultLineColor(self, color):
91     """Set the default line color.
92    
93     color -- a Color object.
94     """
95     self.GetDefaultGroup().GetProperties().SetLineColor(color)
96 jonathan 491 self.__SendNotification()
97 jonathan 388
98 jonathan 462 def GetDefaultLineColor(self):
99     """Return the default line color."""
100     return self.GetDefaultGroup().GetProperties().GetLineColor()
101 jonathan 388
102 jonathan 462 def SetDefaultLineWidth(self, lineWidth):
103     """Set the default line width.
104    
105     lineWidth -- an integer > 0.
106     """
107 jonathan 873 assert isinstance(lineWidth, types.IntType)
108 jonathan 462 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
109 jonathan 491 self.__SendNotification()
110 jonathan 388
111 jonathan 462 def GetDefaultLineWidth(self):
112     """Return the default line width."""
113     return self.GetDefaultGroup().GetProperties().GetLineWidth()
114 jonathan 388
115 jonathan 462
116 jonathan 613 #
117     # The methods that manipulate self.__groups have to be kept in
118     # sync. We store the default group in index 0 to make it
119     # convienent to iterate over the classification's groups, but
120     # from the user's perspective the first (non-default) group is
121     # at index 0 and the DefaultGroup is a special entity.
122     #
123    
124     def SetDefaultGroup(self, group):
125     """Set the group to be used when a value can't be classified.
126    
127     group -- group that the value maps to.
128     """
129     assert isinstance(group, ClassGroupDefault)
130     if len(self.__groups) > 0:
131     self.__groups[0] = group
132     else:
133     self.__groups.append(group)
134 bh 1909 self.__SendNotification()
135 jonathan 613
136     def GetDefaultGroup(self):
137     """Return the default group."""
138     return self.__groups[0]
139    
140     def AppendGroup(self, item):
141     """Append a new ClassGroup item to the classification.
142    
143 jonathan 462 item -- this must be a valid ClassGroup object
144     """
145    
146 jonathan 613 self.InsertGroup(self.GetNumGroups(), item)
147 jonathan 371
148 jonathan 613 def InsertGroup(self, index, group):
149     assert isinstance(group, ClassGroup)
150     self.__groups.insert(index + 1, group)
151 jonathan 491 self.__SendNotification()
152 jonathan 371
153 jonathan 613 def RemoveGroup(self, index):
154 bh 1909 """Remove the classification group with the given index"""
155     self.__groups.pop(index + 1)
156     self.__SendNotification()
157 jonathan 613
158     def ReplaceGroup(self, index, group):
159     assert isinstance(group, ClassGroup)
160     self.__groups[index + 1] = group
161     self.__SendNotification()
162    
163     def GetGroup(self, index):
164     return self.__groups[index + 1]
165    
166     def GetNumGroups(self):
167     """Return the number of non-default groups in the classification."""
168     return len(self.__groups) - 1
169    
170     def FindGroup(self, value):
171 jonathan 462 """Return the associated group, or the default group.
172 jonathan 371
173 jonathan 613 Groups are checked in the order the were added to the
174     Classification.
175 jonathan 371
176 jonathan 613 value -- the value to classify. If there is no mapping,
177     the field is None or value is None,
178     return the default properties
179 jonathan 371 """
180    
181 jonathan 1426 if value is not None:
182 jonathan 613 for i in range(1, len(self.__groups)):
183     group = self.__groups[i]
184 jonathan 462 if group.Matches(value):
185     return group
186 jonathan 371
187 jonathan 462 return self.GetDefaultGroup()
188 jonathan 371
189 jonathan 462 def GetProperties(self, value):
190 jonathan 613 """Return the properties associated with the given value.
191    
192     Use this function rather than Classification.FindGroup().GetProperties()
193     since the returned group may be a ClassGroupMap which doesn't support
194     a call to GetProperties().
195     """
196 jonathan 371
197 jonathan 613 group = self.FindGroup(value)
198 jonathan 462 if isinstance(group, ClassGroupMap):
199     return group.GetPropertiesFromValue(value)
200     else:
201     return group.GetProperties()
202 jonathan 436
203 jonathan 381 def TreeInfo(self):
204     items = []
205 jonathan 378
206 jonathan 410 def build_color_item(text, color):
207 jonathan 1336 if color is Transparent:
208 jonathan 410 return ("%s: %s" % (text, _("None")), None)
209 jonathan 381
210 jonathan 410 return ("%s: (%.3f, %.3f, %.3f)" %
211     (text, color.red, color.green, color.blue),
212     color)
213 jonathan 381
214 jonathan 436 def build_item(group, string):
215     label = group.GetLabel()
216 jonathan 410 if label == "":
217     label = string
218     else:
219     label += " (%s)" % string
220    
221 jonathan 436 props = group.GetProperties()
222 jonathan 381 i = []
223 jonathan 462 v = props.GetLineColor()
224     i.append(build_color_item(_("Line Color"), v))
225     v = props.GetLineWidth()
226     i.append(_("Line Width: %s") % v)
227 jonathan 436 v = props.GetFill()
228 jonathan 410 i.append(build_color_item(_("Fill"), v))
229     return (label, i)
230 jonathan 388
231 jonathan 428 for p in self:
232 jonathan 613 items.append(build_item(p, p.GetDisplayText()))
233 jonathan 388
234 jonathan 436 return (_("Classification"), items)
235 jonathan 381
236 jonathan 428 class ClassIterator:
237 jonathan 462 """Allows the Groups in a Classifcation to be interated over.
238 jonathan 388
239 jonathan 462 The items are returned in the following order:
240     default data, singletons, ranges, maps
241     """
242 jonathan 428
243 jonathan 462 def __init__(self, data): #default, points, ranges, maps):
244     """Constructor.
245    
246     default -- the default group
247    
248     points -- a list of singleton groups
249    
250     ranges -- a list of range groups
251    
252     maps -- a list of map groups
253     """
254    
255 jonathan 1426 self.data = data
256 jonathan 462 self.data_index = 0
257    
258 jonathan 428 def __iter__(self):
259     return self
260    
261     def next(self):
262 jonathan 462 """Return the next item."""
263 jonathan 428
264 jonathan 462 if self.data_index >= len(self.data):
265     raise StopIteration
266     else:
267     d = self.data[self.data_index]
268     self.data_index += 1
269     return d
270    
271 jonathan 436 class ClassGroupProperties:
272 jonathan 462 """Represents the properties of a single Classification Group.
273    
274     These are used when rendering a layer."""
275 jonathan 388
276 jonathan 462 def __init__(self, props = None):
277     """Constructor.
278 jonathan 410
279 jonathan 462 props -- a ClassGroupProperties object. The class is copied if
280     prop is not None. Otherwise, a default set of properties
281 jonathan 1336 is created such that: line color = Black, line width = 1,
282     and fill color = Transparent
283 jonathan 462 """
284    
285     if props is not None:
286     self.SetProperties(props)
287 jonathan 410 else:
288 jonathan 1336 self.SetLineColor(Black)
289 jonathan 462 self.SetLineWidth(1)
290 jonathan 1336 self.SetFill(Transparent)
291 jonathan 410
292 jonathan 462 def SetProperties(self, props):
293     """Set this class's properties to those in class props."""
294    
295 jonathan 602 assert isinstance(props, ClassGroupProperties)
296 jonathan 462 self.SetLineColor(props.GetLineColor())
297     self.SetLineWidth(props.GetLineWidth())
298     self.SetFill(props.GetFill())
299    
300     def GetLineColor(self):
301     """Return the line color as a Color object."""
302 jonathan 678 return self.__stroke
303 jonathan 388
304 jonathan 462 def SetLineColor(self, color):
305     """Set the line color.
306 jonathan 388
307 jonathan 462 color -- the color of the line. This must be a Color object.
308     """
309 jonathan 388
310 jonathan 678 self.__stroke = color
311 jonathan 410
312 jonathan 462 def GetLineWidth(self):
313     """Return the line width."""
314 jonathan 678 return self.__strokeWidth
315 jonathan 388
316 jonathan 462 def SetLineWidth(self, lineWidth):
317     """Set the line width.
318    
319     lineWidth -- the new line width. This must be > 0.
320     """
321 jonathan 873 assert isinstance(lineWidth, types.IntType)
322 jonathan 462 if (lineWidth < 1):
323     raise ValueError(_("lineWidth < 1"))
324    
325 jonathan 678 self.__strokeWidth = lineWidth
326 jonathan 462
327 jonathan 388 def GetFill(self):
328 jonathan 462 """Return the fill color as a Color object."""
329 jonathan 678 return self.__fill
330 jonathan 388
331     def SetFill(self, fill):
332 jonathan 462 """Set the fill color.
333    
334     fill -- the color of the fill. This must be a Color object.
335     """
336    
337 jonathan 678 self.__fill = fill
338 jonathan 388
339 jonathan 479 def __eq__(self, other):
340     """Return true if 'props' has the same attributes as this class"""
341 jonathan 436
342 jonathan 678 #
343     # using 'is' over '==' results in a huge performance gain
344     # in the renderer
345     #
346 jonathan 479 return isinstance(other, ClassGroupProperties) \
347 jonathan 678 and (self.__stroke is other.__stroke or \
348     self.__stroke == other.__stroke) \
349     and (self.__fill is other.__fill or \
350     self.__fill == other.__fill) \
351     and self.__strokeWidth == other.__strokeWidth
352 jonathan 479
353     def __ne__(self, other):
354     return not self.__eq__(other)
355    
356 jonathan 484 def __copy__(self):
357     return ClassGroupProperties(self)
358    
359 jonathan 602 def __deepcopy__(self):
360     return ClassGroupProperties(self)
361    
362 jonathan 681 def __repr__(self):
363     return repr((self.__stroke, self.__strokeWidth, self.__fill))
364    
365 jonathan 436 class ClassGroup:
366 jonathan 462 """A base class for all Groups within a Classification"""
367 jonathan 436
368 jonathan 637 def __init__(self, label = "", props = None, group = None):
369 jonathan 462 """Constructor.
370 jonathan 436
371 jonathan 462 label -- A string representing the Group's label
372     """
373    
374 jonathan 637 if group is not None:
375     self.SetLabel(copy.copy(group.GetLabel()))
376     self.SetProperties(copy.copy(group.GetProperties()))
377     self.SetVisible(group.IsVisible())
378     else:
379     self.SetLabel(label)
380     self.SetProperties(props)
381     self.SetVisible(True)
382 jonathan 462
383 jonathan 388 def GetLabel(self):
384 jonathan 462 """Return the Group's label."""
385 jonathan 388 return self.label
386    
387     def SetLabel(self, label):
388 jonathan 462 """Set the Group's label.
389    
390     label -- a string representing the Group's label. This must
391     not be None.
392     """
393 jonathan 873 assert isinstance(label, types.StringTypes)
394 jonathan 388 self.label = label
395    
396 jonathan 544 def GetDisplayText(self):
397 jonathan 602 assert False, "GetDisplay must be overridden by subclass!"
398 jonathan 544 return ""
399    
400 jonathan 436 def Matches(self, value):
401 jonathan 462 """Determines if this Group is associated with the given value.
402    
403 jonathan 479 Returns False. This needs to be overridden by all subclasses.
404 jonathan 462 """
405 jonathan 602 assert False, "GetMatches must be overridden by subclass!"
406 jonathan 479 return False
407 jonathan 436
408 jonathan 462 def GetProperties(self):
409 jonathan 637 """Return the properties associated with the given value."""
410 jonathan 462
411 jonathan 637 return self.prop
412    
413     def SetProperties(self, prop):
414     """Set the properties associated with this Group.
415    
416     prop -- a ClassGroupProperties object. if prop is None,
417     a default set of properties is created.
418 jonathan 462 """
419 jonathan 637
420     if prop is None: prop = ClassGroupProperties()
421     assert isinstance(prop, ClassGroupProperties)
422     self.prop = prop
423    
424     def IsVisible(self):
425     return self.visible
426    
427     def SetVisible(self, visible):
428     self.visible = visible
429    
430     def __eq__(self, other):
431     return isinstance(other, ClassGroup) \
432 jonathan 681 and self.label == other.label \
433 jonathan 637 and self.GetProperties() == other.GetProperties()
434    
435     def __ne__(self, other):
436     return not self.__eq__(other)
437    
438 jonathan 681 def __repr__(self):
439 jonathan 689 return repr(self.label) + ", " + repr(self.GetProperties())
440 jonathan 410
441 jonathan 436 class ClassGroupSingleton(ClassGroup):
442 jonathan 462 """A Group that is associated with a single value."""
443 jonathan 410
444 jonathan 637 def __init__(self, value = 0, props = None, label = "", group = None):
445 jonathan 462 """Constructor.
446    
447     value -- the associated value.
448    
449     prop -- a ClassGroupProperites object. If prop is None a default
450     set of properties is created.
451    
452     label -- a label for this group.
453     """
454 jonathan 637 ClassGroup.__init__(self, label, props, group)
455 jonathan 410
456 jonathan 436 self.SetValue(value)
457 jonathan 410
458 jonathan 436 def __copy__(self):
459 jonathan 479 return ClassGroupSingleton(self.GetValue(),
460     self.GetProperties(),
461     self.GetLabel())
462 jonathan 436
463 jonathan 484 def __deepcopy__(self, memo):
464 jonathan 637 return ClassGroupSingleton(self.GetValue(), group = self)
465 jonathan 484
466 jonathan 410 def GetValue(self):
467 jonathan 462 """Return the associated value."""
468 jonathan 678 return self.__value
469 jonathan 410
470     def SetValue(self, value):
471 jonathan 462 """Associate this Group with the given value."""
472 jonathan 678 self.__value = value
473 jonathan 410
474 jonathan 436 def Matches(self, value):
475 jonathan 462 """Determine if the given value matches the associated Group value."""
476    
477     """Returns True if the value matches, False otherwise."""
478    
479 jonathan 678 return self.__value == value
480 jonathan 410
481 jonathan 544 def GetDisplayText(self):
482     label = self.GetLabel()
483    
484     if label != "": return label
485    
486     return str(self.GetValue())
487    
488 jonathan 479 def __eq__(self, other):
489 jonathan 637 return ClassGroup.__eq__(self, other) \
490     and isinstance(other, ClassGroupSingleton) \
491 jonathan 678 and self.__value == other.__value
492 jonathan 436
493 jonathan 681 def __repr__(self):
494 jonathan 689 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
495 jonathan 681
496 jonathan 479 class ClassGroupDefault(ClassGroup):
497 jonathan 462 """The default Group. When values do not match any other
498     Group within a Classification, the properties from this
499     class are used."""
500    
501 jonathan 637 def __init__(self, props = None, label = "", group = None):
502 jonathan 462 """Constructor.
503    
504     prop -- a ClassGroupProperites object. If prop is None a default
505     set of properties is created.
506    
507     label -- a label for this group.
508     """
509    
510 jonathan 637 ClassGroup.__init__(self, label, props, group)
511 jonathan 436
512     def __copy__(self):
513 jonathan 479 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
514 jonathan 436
515 jonathan 484 def __deepcopy__(self, memo):
516 jonathan 637 return ClassGroupDefault(label = self.GetLabel(), group = self)
517 jonathan 484
518 jonathan 479 def Matches(self, value):
519     return True
520    
521 jonathan 544 def GetDisplayText(self):
522     label = self.GetLabel()
523    
524     if label != "": return label
525    
526 jonathan 613 return _("DEFAULT")
527 jonathan 544
528 jonathan 479 def __eq__(self, other):
529 jonathan 637 return ClassGroup.__eq__(self, other) \
530     and isinstance(other, ClassGroupDefault) \
531 jonathan 479 and self.GetProperties() == other.GetProperties()
532    
533 jonathan 681 def __repr__(self):
534     return "(" + ClassGroup.__repr__(self) + ")"
535    
536 jonathan 436 class ClassGroupRange(ClassGroup):
537 jonathan 462 """A Group that represents a range of values that map to the same
538     set of properties."""
539 jonathan 436
540 jonathan 1353 def __init__(self, _range = (0,1), props = None, label = "", group=None):
541 jonathan 462 """Constructor.
542    
543     The minumum value must be strictly less than the maximum.
544    
545 jonathan 1353 _range -- either a tuple (min, max) where min < max or
546     a Range object
547 jonathan 462
548     prop -- a ClassGroupProperites object. If prop is None a default
549     set of properties is created.
550    
551     label -- a label for this group.
552     """
553    
554 jonathan 643 ClassGroup.__init__(self, label, props, group)
555 jonathan 1353 self.SetRange(_range)
556 jonathan 410
557 jonathan 436 def __copy__(self):
558 jonathan 1353 return ClassGroupRange(self.__range,
559 jonathan 873 props = self.GetProperties(),
560     label = self.GetLabel())
561 jonathan 436
562 jonathan 484 def __deepcopy__(self, memo):
563 jonathan 1353 return ClassGroupRange(copy.copy(self.__range),
564 jonathan 637 group = self)
565 jonathan 484
566 jonathan 410 def GetMin(self):
567 jonathan 462 """Return the range's minimum value."""
568 jonathan 873 return self.__range.GetRange()[1]
569 jonathan 410
570     def SetMin(self, min):
571 jonathan 462 """Set the range's minimum value.
572    
573     min -- the new minimum. Note that this must be less than the current
574     maximum value. Use SetRange() to change both min and max values.
575     """
576    
577 jonathan 1353 self.SetRange((min, self.__range.GetRange()[2]))
578 jonathan 410
579     def GetMax(self):
580 jonathan 462 """Return the range's maximum value."""
581 jonathan 873 return self.__range.GetRange()[2]
582 jonathan 410
583     def SetMax(self, max):
584 jonathan 462 """Set the range's maximum value.
585    
586     max -- the new maximum. Note that this must be greater than the current
587     minimum value. Use SetRange() to change both min and max values.
588     """
589 jonathan 1353 self.SetRange((self.__range.GetRange()[1], max))
590 jonathan 410
591 jonathan 1353 def SetRange(self, _range):
592 jonathan 462 """Set a new range.
593    
594 jonathan 1353 _range -- Either a tuple (min, max) where min < max or
595     a Range object.
596 jonathan 462
597 jonathan 1353 Raises ValueError on error.
598 jonathan 462 """
599    
600 jonathan 1353 if isinstance(_range, Range):
601     self.__range = _range
602     elif isinstance(_range, types.TupleType) and len(_range) == 2:
603     self.__range = Range(("[", _range[0], _range[1], "["))
604 jonathan 873 else:
605 jonathan 1353 raise ValueError()
606 jonathan 410
607     def GetRange(self):
608 jonathan 873 """Return the range as a string"""
609     return self.__range.string(self.__range.GetRange())
610 jonathan 410
611 jonathan 436 def Matches(self, value):
612 jonathan 462 """Determine if the given value lies with the current range.
613    
614     The following check is used: min <= value < max.
615     """
616    
617 jonathan 873 return operator.contains(self.__range, value)
618 jonathan 410
619 jonathan 544 def GetDisplayText(self):
620     label = self.GetLabel()
621    
622     if label != "": return label
623    
624 jonathan 873 return self.__range.string(self.__range.GetRange())
625 jonathan 544
626 jonathan 479 def __eq__(self, other):
627 jonathan 637 return ClassGroup.__eq__(self, other) \
628     and isinstance(other, ClassGroupRange) \
629 jonathan 873 and self.__range == other.__range
630 jonathan 479
631 jonathan 681 def __repr__(self):
632 jonathan 873 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
633 jonathan 681
634 jonathan 436 class ClassGroupMap(ClassGroup):
635 jonathan 462 """Currently, this class is not used."""
636 jonathan 436
637 jonathan 410 FUNC_ID = "id"
638    
639 jonathan 436 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
640 jonathan 462 ClassGroup.__init__(self, label)
641 jonathan 410
642     self.map_type = map_type
643     self.func = func
644    
645     if self.func is None:
646     self.func = func_id
647    
648     def Map(self, value):
649     return self.func(value)
650    
651 jonathan 462 def GetProperties(self):
652     return None
653    
654     def GetPropertiesFromValue(self, value):
655     pass
656    
657 jonathan 544 def GetDisplayText(self):
658     return "Map: " + self.map_type
659    
660 jonathan 410 #
661     # built-in mappings
662     #
663     def func_id(value):
664     return value
665    

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26