/[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 1426 - (hide annotations)
Wed Jul 16 13:22:20 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 19605 byte(s)
Use new CLASS_CHANGED message.
(Classification): Inherit from Publisher.
(Classification.__init__): Remove the layer parameter.
        Classifications no longer need to have a parent layer.
(Classification.GetField, Classification.GetFieldType,
        Classification.SetFieldInfo): Removed. The field name is stored
        in the layer, and the type can be retreived by calling
        Layer.GetFieldType().
(Classification._set_layer, Classification.GetLayer): Removed.
        Classifications no longer have a parent layer.

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    
130     assert isinstance(group, ClassGroupDefault)
131     if len(self.__groups) > 0:
132     self.__groups[0] = group
133     else:
134     self.__groups.append(group)
135    
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     return self.__groups.pop(index + 1)
155    
156     def ReplaceGroup(self, index, group):
157     assert isinstance(group, ClassGroup)
158     self.__groups[index + 1] = group
159     self.__SendNotification()
160    
161     def GetGroup(self, index):
162     return self.__groups[index + 1]
163    
164     def GetNumGroups(self):
165     """Return the number of non-default groups in the classification."""
166     return len(self.__groups) - 1
167    
168     def FindGroup(self, value):
169 jonathan 462 """Return the associated group, or the default group.
170 jonathan 371
171 jonathan 613 Groups are checked in the order the were added to the
172     Classification.
173 jonathan 371
174 jonathan 613 value -- the value to classify. If there is no mapping,
175     the field is None or value is None,
176     return the default properties
177 jonathan 371 """
178    
179 jonathan 1426 if value is not None:
180 jonathan 613 for i in range(1, len(self.__groups)):
181     group = self.__groups[i]
182 jonathan 462 if group.Matches(value):
183     return group
184 jonathan 371
185 jonathan 462 return self.GetDefaultGroup()
186 jonathan 371
187 jonathan 462 def GetProperties(self, value):
188 jonathan 613 """Return the properties associated with the given value.
189    
190     Use this function rather than Classification.FindGroup().GetProperties()
191     since the returned group may be a ClassGroupMap which doesn't support
192     a call to GetProperties().
193     """
194 jonathan 371
195 jonathan 613 group = self.FindGroup(value)
196 jonathan 462 if isinstance(group, ClassGroupMap):
197     return group.GetPropertiesFromValue(value)
198     else:
199     return group.GetProperties()
200 jonathan 436
201 jonathan 381 def TreeInfo(self):
202     items = []
203 jonathan 378
204 jonathan 410 def build_color_item(text, color):
205 jonathan 1336 if color is Transparent:
206 jonathan 410 return ("%s: %s" % (text, _("None")), None)
207 jonathan 381
208 jonathan 410 return ("%s: (%.3f, %.3f, %.3f)" %
209     (text, color.red, color.green, color.blue),
210     color)
211 jonathan 381
212 jonathan 436 def build_item(group, string):
213     label = group.GetLabel()
214 jonathan 410 if label == "":
215     label = string
216     else:
217     label += " (%s)" % string
218    
219 jonathan 436 props = group.GetProperties()
220 jonathan 381 i = []
221 jonathan 462 v = props.GetLineColor()
222     i.append(build_color_item(_("Line Color"), v))
223     v = props.GetLineWidth()
224     i.append(_("Line Width: %s") % v)
225 jonathan 436 v = props.GetFill()
226 jonathan 410 i.append(build_color_item(_("Fill"), v))
227     return (label, i)
228 jonathan 388
229 jonathan 428 for p in self:
230 jonathan 613 items.append(build_item(p, p.GetDisplayText()))
231 jonathan 388
232 jonathan 436 return (_("Classification"), items)
233 jonathan 381
234 jonathan 428 class ClassIterator:
235 jonathan 462 """Allows the Groups in a Classifcation to be interated over.
236 jonathan 388
237 jonathan 462 The items are returned in the following order:
238     default data, singletons, ranges, maps
239     """
240 jonathan 428
241 jonathan 462 def __init__(self, data): #default, points, ranges, maps):
242     """Constructor.
243    
244     default -- the default group
245    
246     points -- a list of singleton groups
247    
248     ranges -- a list of range groups
249    
250     maps -- a list of map groups
251     """
252    
253 jonathan 1426 self.data = data
254 jonathan 462 self.data_index = 0
255    
256 jonathan 428 def __iter__(self):
257     return self
258    
259     def next(self):
260 jonathan 462 """Return the next item."""
261 jonathan 428
262 jonathan 462 if self.data_index >= len(self.data):
263     raise StopIteration
264     else:
265     d = self.data[self.data_index]
266     self.data_index += 1
267     return d
268    
269 jonathan 436 class ClassGroupProperties:
270 jonathan 462 """Represents the properties of a single Classification Group.
271    
272     These are used when rendering a layer."""
273 jonathan 388
274 jonathan 462 def __init__(self, props = None):
275     """Constructor.
276 jonathan 410
277 jonathan 462 props -- a ClassGroupProperties object. The class is copied if
278     prop is not None. Otherwise, a default set of properties
279 jonathan 1336 is created such that: line color = Black, line width = 1,
280     and fill color = Transparent
281 jonathan 462 """
282    
283     if props is not None:
284     self.SetProperties(props)
285 jonathan 410 else:
286 jonathan 1336 self.SetLineColor(Black)
287 jonathan 462 self.SetLineWidth(1)
288 jonathan 1336 self.SetFill(Transparent)
289 jonathan 410
290 jonathan 462 def SetProperties(self, props):
291     """Set this class's properties to those in class props."""
292    
293 jonathan 602 assert isinstance(props, ClassGroupProperties)
294 jonathan 462 self.SetLineColor(props.GetLineColor())
295     self.SetLineWidth(props.GetLineWidth())
296     self.SetFill(props.GetFill())
297    
298     def GetLineColor(self):
299     """Return the line color as a Color object."""
300 jonathan 678 return self.__stroke
301 jonathan 388
302 jonathan 462 def SetLineColor(self, color):
303     """Set the line color.
304 jonathan 388
305 jonathan 462 color -- the color of the line. This must be a Color object.
306     """
307 jonathan 388
308 jonathan 678 self.__stroke = color
309 jonathan 410
310 jonathan 462 def GetLineWidth(self):
311     """Return the line width."""
312 jonathan 678 return self.__strokeWidth
313 jonathan 388
314 jonathan 462 def SetLineWidth(self, lineWidth):
315     """Set the line width.
316    
317     lineWidth -- the new line width. This must be > 0.
318     """
319 jonathan 873 assert isinstance(lineWidth, types.IntType)
320 jonathan 462 if (lineWidth < 1):
321     raise ValueError(_("lineWidth < 1"))
322    
323 jonathan 678 self.__strokeWidth = lineWidth
324 jonathan 462
325 jonathan 388 def GetFill(self):
326 jonathan 462 """Return the fill color as a Color object."""
327 jonathan 678 return self.__fill
328 jonathan 388
329     def SetFill(self, fill):
330 jonathan 462 """Set the fill color.
331    
332     fill -- the color of the fill. This must be a Color object.
333     """
334    
335 jonathan 678 self.__fill = fill
336 jonathan 388
337 jonathan 479 def __eq__(self, other):
338     """Return true if 'props' has the same attributes as this class"""
339 jonathan 436
340 jonathan 678 #
341     # using 'is' over '==' results in a huge performance gain
342     # in the renderer
343     #
344 jonathan 479 return isinstance(other, ClassGroupProperties) \
345 jonathan 678 and (self.__stroke is other.__stroke or \
346     self.__stroke == other.__stroke) \
347     and (self.__fill is other.__fill or \
348     self.__fill == other.__fill) \
349     and self.__strokeWidth == other.__strokeWidth
350 jonathan 479
351     def __ne__(self, other):
352     return not self.__eq__(other)
353    
354 jonathan 484 def __copy__(self):
355     return ClassGroupProperties(self)
356    
357 jonathan 602 def __deepcopy__(self):
358     return ClassGroupProperties(self)
359    
360 jonathan 681 def __repr__(self):
361     return repr((self.__stroke, self.__strokeWidth, self.__fill))
362    
363 jonathan 436 class ClassGroup:
364 jonathan 462 """A base class for all Groups within a Classification"""
365 jonathan 436
366 jonathan 637 def __init__(self, label = "", props = None, group = None):
367 jonathan 462 """Constructor.
368 jonathan 436
369 jonathan 462 label -- A string representing the Group's label
370     """
371    
372 jonathan 637 if group is not None:
373     self.SetLabel(copy.copy(group.GetLabel()))
374     self.SetProperties(copy.copy(group.GetProperties()))
375     self.SetVisible(group.IsVisible())
376     else:
377     self.SetLabel(label)
378     self.SetProperties(props)
379     self.SetVisible(True)
380 jonathan 462
381 jonathan 388 def GetLabel(self):
382 jonathan 462 """Return the Group's label."""
383 jonathan 388 return self.label
384    
385     def SetLabel(self, label):
386 jonathan 462 """Set the Group's label.
387    
388     label -- a string representing the Group's label. This must
389     not be None.
390     """
391 jonathan 873 assert isinstance(label, types.StringTypes)
392 jonathan 388 self.label = label
393    
394 jonathan 544 def GetDisplayText(self):
395 jonathan 602 assert False, "GetDisplay must be overridden by subclass!"
396 jonathan 544 return ""
397    
398 jonathan 436 def Matches(self, value):
399 jonathan 462 """Determines if this Group is associated with the given value.
400    
401 jonathan 479 Returns False. This needs to be overridden by all subclasses.
402 jonathan 462 """
403 jonathan 602 assert False, "GetMatches must be overridden by subclass!"
404 jonathan 479 return False
405 jonathan 436
406 jonathan 462 def GetProperties(self):
407 jonathan 637 """Return the properties associated with the given value."""
408 jonathan 462
409 jonathan 637 return self.prop
410    
411     def SetProperties(self, prop):
412     """Set the properties associated with this Group.
413    
414     prop -- a ClassGroupProperties object. if prop is None,
415     a default set of properties is created.
416 jonathan 462 """
417 jonathan 637
418     if prop is None: prop = ClassGroupProperties()
419     assert isinstance(prop, ClassGroupProperties)
420     self.prop = prop
421    
422     def IsVisible(self):
423     return self.visible
424    
425     def SetVisible(self, visible):
426     self.visible = visible
427    
428     def __eq__(self, other):
429     return isinstance(other, ClassGroup) \
430 jonathan 681 and self.label == other.label \
431 jonathan 637 and self.GetProperties() == other.GetProperties()
432    
433     def __ne__(self, other):
434     return not self.__eq__(other)
435    
436 jonathan 681 def __repr__(self):
437 jonathan 689 return repr(self.label) + ", " + repr(self.GetProperties())
438 jonathan 410
439 jonathan 436 class ClassGroupSingleton(ClassGroup):
440 jonathan 462 """A Group that is associated with a single value."""
441 jonathan 410
442 jonathan 637 def __init__(self, value = 0, props = None, label = "", group = None):
443 jonathan 462 """Constructor.
444    
445     value -- the associated value.
446    
447     prop -- a ClassGroupProperites object. If prop is None a default
448     set of properties is created.
449    
450     label -- a label for this group.
451     """
452 jonathan 637 ClassGroup.__init__(self, label, props, group)
453 jonathan 410
454 jonathan 436 self.SetValue(value)
455 jonathan 410
456 jonathan 436 def __copy__(self):
457 jonathan 479 return ClassGroupSingleton(self.GetValue(),
458     self.GetProperties(),
459     self.GetLabel())
460 jonathan 436
461 jonathan 484 def __deepcopy__(self, memo):
462 jonathan 637 return ClassGroupSingleton(self.GetValue(), group = self)
463 jonathan 484
464 jonathan 410 def GetValue(self):
465 jonathan 462 """Return the associated value."""
466 jonathan 678 return self.__value
467 jonathan 410
468     def SetValue(self, value):
469 jonathan 462 """Associate this Group with the given value."""
470 jonathan 678 self.__value = value
471 jonathan 410
472 jonathan 436 def Matches(self, value):
473 jonathan 462 """Determine if the given value matches the associated Group value."""
474    
475     """Returns True if the value matches, False otherwise."""
476    
477 jonathan 678 return self.__value == value
478 jonathan 410
479 jonathan 544 def GetDisplayText(self):
480     label = self.GetLabel()
481    
482     if label != "": return label
483    
484     return str(self.GetValue())
485    
486 jonathan 479 def __eq__(self, other):
487 jonathan 637 return ClassGroup.__eq__(self, other) \
488     and isinstance(other, ClassGroupSingleton) \
489 jonathan 678 and self.__value == other.__value
490 jonathan 436
491 jonathan 681 def __repr__(self):
492 jonathan 689 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
493 jonathan 681
494 jonathan 479 class ClassGroupDefault(ClassGroup):
495 jonathan 462 """The default Group. When values do not match any other
496     Group within a Classification, the properties from this
497     class are used."""
498    
499 jonathan 637 def __init__(self, props = None, label = "", group = None):
500 jonathan 462 """Constructor.
501    
502     prop -- a ClassGroupProperites object. If prop is None a default
503     set of properties is created.
504    
505     label -- a label for this group.
506     """
507    
508 jonathan 637 ClassGroup.__init__(self, label, props, group)
509 jonathan 436
510     def __copy__(self):
511 jonathan 479 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
512 jonathan 436
513 jonathan 484 def __deepcopy__(self, memo):
514 jonathan 637 return ClassGroupDefault(label = self.GetLabel(), group = self)
515 jonathan 484
516 jonathan 479 def Matches(self, value):
517     return True
518    
519 jonathan 544 def GetDisplayText(self):
520     label = self.GetLabel()
521    
522     if label != "": return label
523    
524 jonathan 613 return _("DEFAULT")
525 jonathan 544
526 jonathan 479 def __eq__(self, other):
527 jonathan 637 return ClassGroup.__eq__(self, other) \
528     and isinstance(other, ClassGroupDefault) \
529 jonathan 479 and self.GetProperties() == other.GetProperties()
530    
531 jonathan 681 def __repr__(self):
532     return "(" + ClassGroup.__repr__(self) + ")"
533    
534 jonathan 436 class ClassGroupRange(ClassGroup):
535 jonathan 462 """A Group that represents a range of values that map to the same
536     set of properties."""
537 jonathan 436
538 jonathan 1353 def __init__(self, _range = (0,1), props = None, label = "", group=None):
539 jonathan 462 """Constructor.
540    
541     The minumum value must be strictly less than the maximum.
542    
543 jonathan 1353 _range -- either a tuple (min, max) where min < max or
544     a Range object
545 jonathan 462
546     prop -- a ClassGroupProperites object. If prop is None a default
547     set of properties is created.
548    
549     label -- a label for this group.
550     """
551    
552 jonathan 643 ClassGroup.__init__(self, label, props, group)
553 jonathan 1353 self.SetRange(_range)
554 jonathan 410
555 jonathan 436 def __copy__(self):
556 jonathan 1353 return ClassGroupRange(self.__range,
557 jonathan 873 props = self.GetProperties(),
558     label = self.GetLabel())
559 jonathan 436
560 jonathan 484 def __deepcopy__(self, memo):
561 jonathan 1353 return ClassGroupRange(copy.copy(self.__range),
562 jonathan 637 group = self)
563 jonathan 484
564 jonathan 410 def GetMin(self):
565 jonathan 462 """Return the range's minimum value."""
566 jonathan 873 return self.__range.GetRange()[1]
567 jonathan 410
568     def SetMin(self, min):
569 jonathan 462 """Set the range's minimum value.
570    
571     min -- the new minimum. Note that this must be less than the current
572     maximum value. Use SetRange() to change both min and max values.
573     """
574    
575 jonathan 1353 self.SetRange((min, self.__range.GetRange()[2]))
576 jonathan 410
577     def GetMax(self):
578 jonathan 462 """Return the range's maximum value."""
579 jonathan 873 return self.__range.GetRange()[2]
580 jonathan 410
581     def SetMax(self, max):
582 jonathan 462 """Set the range's maximum value.
583    
584     max -- the new maximum. Note that this must be greater than the current
585     minimum value. Use SetRange() to change both min and max values.
586     """
587 jonathan 1353 self.SetRange((self.__range.GetRange()[1], max))
588 jonathan 410
589 jonathan 1353 def SetRange(self, _range):
590 jonathan 462 """Set a new range.
591    
592 jonathan 1353 _range -- Either a tuple (min, max) where min < max or
593     a Range object.
594 jonathan 462
595 jonathan 1353 Raises ValueError on error.
596 jonathan 462 """
597    
598 jonathan 1353 if isinstance(_range, Range):
599     self.__range = _range
600     elif isinstance(_range, types.TupleType) and len(_range) == 2:
601     self.__range = Range(("[", _range[0], _range[1], "["))
602 jonathan 873 else:
603 jonathan 1353 raise ValueError()
604 jonathan 410
605     def GetRange(self):
606 jonathan 873 """Return the range as a string"""
607     return self.__range.string(self.__range.GetRange())
608 jonathan 410
609 jonathan 436 def Matches(self, value):
610 jonathan 462 """Determine if the given value lies with the current range.
611    
612     The following check is used: min <= value < max.
613     """
614    
615 jonathan 873 return operator.contains(self.__range, value)
616 jonathan 410
617 jonathan 544 def GetDisplayText(self):
618     label = self.GetLabel()
619    
620     if label != "": return label
621    
622 jonathan 873 return self.__range.string(self.__range.GetRange())
623 jonathan 544
624 jonathan 479 def __eq__(self, other):
625 jonathan 637 return ClassGroup.__eq__(self, other) \
626     and isinstance(other, ClassGroupRange) \
627 jonathan 873 and self.__range == other.__range
628 jonathan 479
629 jonathan 681 def __repr__(self):
630 jonathan 873 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
631 jonathan 681
632 jonathan 436 class ClassGroupMap(ClassGroup):
633 jonathan 462 """Currently, this class is not used."""
634 jonathan 436
635 jonathan 410 FUNC_ID = "id"
636    
637 jonathan 436 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
638 jonathan 462 ClassGroup.__init__(self, label)
639 jonathan 410
640     self.map_type = map_type
641     self.func = func
642    
643     if self.func is None:
644     self.func = func_id
645    
646     def Map(self, value):
647     return self.func(value)
648    
649 jonathan 462 def GetProperties(self):
650     return None
651    
652     def GetPropertiesFromValue(self, value):
653     pass
654    
655 jonathan 544 def GetDisplayText(self):
656     return "Map: " + self.map_type
657    
658 jonathan 410 #
659     # built-in mappings
660     #
661     def func_id(value):
662     return value
663    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26