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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1176 - (show annotations)
Thu Jun 12 15:46:22 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 24674 byte(s)
Removed assert statements that tested if the variable was an instance of Color.

1 # Copyright (c) 2001, 2003 by Intevation GmbH
2 # 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 If no mapping can be found then default data will
17 be returned. Input values must be hashable objects
18
19 See the description of FindGroup() for more information
20 on the mapping algorithm.
21 """
22
23 # fix for people using python2.1
24 from __future__ import nested_scopes
25
26 import copy, operator
27
28 from Thuban import _
29
30 import types
31
32 from messages import \
33 LAYER_PROJECTION_CHANGED, \
34 LAYER_LEGEND_CHANGED, \
35 LAYER_VISIBILITY_CHANGED
36
37 from Thuban.Model.color import Color
38 from Thuban.Model.range import Range
39
40 import Thuban.Model.layer
41
42 class Classification:
43 """Encapsulates the classification of layer.
44
45 The Classification divides some kind of data into Groups which
46 are associated with properties. Later the properties can be
47 retrieved by matching data values to the appropriate group.
48 """
49
50 def __init__(self, layer = None, field = None):
51 """Initialize a classification.
52
53 layer -- the Layer object who owns this classification
54
55 field -- the name of the data table field that
56 is to be used to classify layer properties
57 """
58
59 self.layer = None
60 self.field = None
61 self.fieldType = None
62 self.__groups = []
63
64 self.__setLayerLock = False
65
66 self.SetDefaultGroup(ClassGroupDefault())
67
68 self.SetLayer(layer)
69 self.SetField(field)
70
71 def __iter__(self):
72 return ClassIterator(self.__groups)
73
74 def __deepcopy__(self, memo):
75 clazz = Classification()
76
77 # note: the only thing that isn't copied is the layer reference
78 clazz.field = self.field
79 clazz.fieldType = self.fieldType
80 clazz.__groups[0] = copy.deepcopy(self.__groups[0])
81
82 for i in range(1, len(self.__groups)):
83 clazz.__groups.append(copy.deepcopy(self.__groups[i]))
84
85 return clazz
86
87 def __SendNotification(self):
88 """Notify the layer that this class has changed."""
89 if self.layer is not None:
90 self.layer.ClassChanged()
91
92 def SetField(self, field):
93 """Set the name of the data table field to use.
94
95 If there is no layer then the field type is set to None,
96 otherwise the layer is queried to find the type of the
97 field data
98
99 field -- if None then all values map to the default data
100 """
101
102 if field == "":
103 field = None
104
105
106 if field is None:
107 if self.layer is not None:
108 self.fieldType = None
109 else:
110 if self.layer is not None:
111 fieldType = self.layer.GetFieldType(field)
112 if fieldType is None:
113 raise ValueError("'%s' was not found in the layer's table."
114 % self.field)
115
116 #
117 # unfortunately we cannot call SetFieldType() because it
118 # requires the layer to be None
119 #
120 self.fieldType = fieldType
121 #self.SetFieldType(fieldType)
122
123 self.field = field
124
125 self.__SendNotification()
126
127 def GetField(self):
128 """Return the name of the field."""
129 return self.field
130
131 def GetFieldType(self):
132 """Return the field type."""
133 return self.fieldType
134
135 def SetFieldType(self, type):
136 """Set the type of the field used by this classification.
137
138 A ValueError is raised if the owning layer is not None and
139 'type' is different from the current field type.
140 """
141
142 if type != self.fieldType:
143 if self.layer is not None:
144 raise ValueError()
145 else:
146 self.fieldType = type
147 self.__SendNotification()
148
149 def SetLayer(self, layer):
150 """Set the owning Layer of this classification.
151
152 A ValueError exception will be thrown either the field or
153 field type mismatch the information in the layer's table.
154 """
155
156 # prevent infinite recursion when calling SetClassification()
157 if self.__setLayerLock: return
158
159 self.__setLayerLock = True
160
161 if layer is None:
162 if self.layer is not None:
163 l = self.layer
164 self.layer = None
165 l.SetClassification(None)
166 else:
167 assert isinstance(layer, Thuban.Model.layer.Layer)
168
169 old_layer = self.layer
170
171 self.layer = layer
172
173 try:
174 self.SetField(self.GetField()) # this sync's the fieldType
175 except ValueError:
176 self.layer = old_layer
177 self.__setLayerLock = False
178 raise ValueError
179 else:
180 self.layer.SetClassification(self)
181
182 self.__setLayerLock = False
183
184 def GetLayer(self):
185 """Return the parent layer."""
186 return self.layer
187
188
189 #
190 # these SetDefault* methods are really only provided for
191 # some backward compatibility. they should be considered
192 # for removal once all the classification code is finished.
193 #
194
195 def SetDefaultFill(self, fill):
196 """Set the default fill color.
197
198 fill -- a Color object.
199 """
200 self.GetDefaultGroup().GetProperties().SetFill(fill)
201 self.__SendNotification()
202
203 def GetDefaultFill(self):
204 """Return the default fill color."""
205 return self.GetDefaultGroup().GetProperties().GetFill()
206
207 def SetDefaultLineColor(self, color):
208 """Set the default line color.
209
210 color -- a Color object.
211 """
212 self.GetDefaultGroup().GetProperties().SetLineColor(color)
213 self.__SendNotification()
214
215 def GetDefaultLineColor(self):
216 """Return the default line color."""
217 return self.GetDefaultGroup().GetProperties().GetLineColor()
218
219 def SetDefaultLineWidth(self, lineWidth):
220 """Set the default line width.
221
222 lineWidth -- an integer > 0.
223 """
224 assert isinstance(lineWidth, types.IntType)
225 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
226 self.__SendNotification()
227
228 def GetDefaultLineWidth(self):
229 """Return the default line width."""
230 return self.GetDefaultGroup().GetProperties().GetLineWidth()
231
232
233 #
234 # The methods that manipulate self.__groups have to be kept in
235 # sync. We store the default group in index 0 to make it
236 # convienent to iterate over the classification's groups, but
237 # from the user's perspective the first (non-default) group is
238 # at index 0 and the DefaultGroup is a special entity.
239 #
240
241 def SetDefaultGroup(self, group):
242 """Set the group to be used when a value can't be classified.
243
244 group -- group that the value maps to.
245 """
246
247 assert isinstance(group, ClassGroupDefault)
248 if len(self.__groups) > 0:
249 self.__groups[0] = group
250 else:
251 self.__groups.append(group)
252
253 def GetDefaultGroup(self):
254 """Return the default group."""
255 return self.__groups[0]
256
257 def AppendGroup(self, item):
258 """Append a new ClassGroup item to the classification.
259
260 item -- this must be a valid ClassGroup object
261 """
262
263 self.InsertGroup(self.GetNumGroups(), item)
264
265 def InsertGroup(self, index, group):
266
267 assert isinstance(group, ClassGroup)
268
269 self.__groups.insert(index + 1, group)
270
271 self.__SendNotification()
272
273 def RemoveGroup(self, index):
274 return self.__groups.pop(index + 1)
275
276 def ReplaceGroup(self, index, group):
277 assert isinstance(group, ClassGroup)
278
279 self.__groups[index + 1] = group
280
281 self.__SendNotification()
282
283 def GetGroup(self, index):
284 return self.__groups[index + 1]
285
286 def GetNumGroups(self):
287 """Return the number of non-default groups in the classification."""
288 return len(self.__groups) - 1
289
290
291 def FindGroup(self, value):
292 """Return the associated group, or the default group.
293
294 Groups are checked in the order the were added to the
295 Classification.
296
297 value -- the value to classify. If there is no mapping,
298 the field is None or value is None,
299 return the default properties
300 """
301
302 if self.GetField() is not None and value is not None:
303
304 for i in range(1, len(self.__groups)):
305 group = self.__groups[i]
306 if group.Matches(value):
307 return group
308
309 return self.GetDefaultGroup()
310
311 def GetProperties(self, value):
312 """Return the properties associated with the given value.
313
314 Use this function rather than Classification.FindGroup().GetProperties()
315 since the returned group may be a ClassGroupMap which doesn't support
316 a call to GetProperties().
317 """
318
319 group = self.FindGroup(value)
320 if isinstance(group, ClassGroupMap):
321 return group.GetPropertiesFromValue(value)
322 else:
323 return group.GetProperties()
324
325 def TreeInfo(self):
326 items = []
327
328 def build_color_item(text, color):
329 if color is Color.Transparent:
330 return ("%s: %s" % (text, _("None")), None)
331
332 return ("%s: (%.3f, %.3f, %.3f)" %
333 (text, color.red, color.green, color.blue),
334 color)
335
336 def build_item(group, string):
337 label = group.GetLabel()
338 if label == "":
339 label = string
340 else:
341 label += " (%s)" % string
342
343 props = group.GetProperties()
344 i = []
345 v = props.GetLineColor()
346 i.append(build_color_item(_("Line Color"), v))
347 v = props.GetLineWidth()
348 i.append(_("Line Width: %s") % v)
349 v = props.GetFill()
350 i.append(build_color_item(_("Fill"), v))
351 return (label, i)
352
353 for p in self:
354 items.append(build_item(p, p.GetDisplayText()))
355
356 # if isinstance(p, ClassGroupDefault):
357 # items.append(build_item(self.GetDefaultGroup(), _("'DEFAULT'")))
358 # elif isinstance(p, ClassGroupSingleton):
359 # items.append(build_item(p, str(p.GetValue())))
360 # elif isinstance(p, ClassGroupRange):
361 # items.append(build_item(p, "%s - %s" %
362 # (p.GetMin(), p.GetMax())))
363
364 return (_("Classification"), items)
365
366 class ClassIterator:
367 """Allows the Groups in a Classifcation to be interated over.
368
369 The items are returned in the following order:
370 default data, singletons, ranges, maps
371 """
372
373 def __init__(self, data): #default, points, ranges, maps):
374 """Constructor.
375
376 default -- the default group
377
378 points -- a list of singleton groups
379
380 ranges -- a list of range groups
381
382 maps -- a list of map groups
383 """
384
385 self.data = data #[default, points, ranges, maps]
386 self.data_index = 0
387 #self.data_iter = iter(self.data)
388 #self.iter = None
389
390 def __iter__(self):
391 return self
392
393 def next(self):
394 """Return the next item."""
395
396 if self.data_index >= len(self.data):
397 raise StopIteration
398 else:
399 d = self.data[self.data_index]
400 self.data_index += 1
401 return d
402
403 # if self.iter is None:
404 # try:
405 # self.data_item = self.data_iter.next()
406 # self.iter = iter(self.data_item)
407 # except TypeError:
408 # return self.data_item
409
410 # try:
411 # return self.iter.next()
412 # except StopIteration:
413 # self.iter = None
414 # return self.next()
415
416 class ClassGroupProperties:
417 """Represents the properties of a single Classification Group.
418
419 These are used when rendering a layer."""
420
421 def __init__(self, props = None):
422 """Constructor.
423
424 props -- a ClassGroupProperties object. The class is copied if
425 prop is not None. Otherwise, a default set of properties
426 is created such that: line color = Color.Black, line width = 1,
427 and fill color = Color.Transparent
428 """
429
430 #self.stroke = None
431 #self.strokeWidth = 0
432 #self.fill = None
433
434 if props is not None:
435 self.SetProperties(props)
436 else:
437 self.SetLineColor(Color.Black)
438 self.SetLineWidth(1)
439 self.SetFill(Color.Transparent)
440
441 def SetProperties(self, props):
442 """Set this class's properties to those in class props."""
443
444 assert isinstance(props, ClassGroupProperties)
445 self.SetLineColor(props.GetLineColor())
446 self.SetLineWidth(props.GetLineWidth())
447 self.SetFill(props.GetFill())
448
449 def GetLineColor(self):
450 """Return the line color as a Color object."""
451 return self.__stroke
452
453 def SetLineColor(self, color):
454 """Set the line color.
455
456 color -- the color of the line. This must be a Color object.
457 """
458
459 self.__stroke = color
460
461 def GetLineWidth(self):
462 """Return the line width."""
463 return self.__strokeWidth
464
465 def SetLineWidth(self, lineWidth):
466 """Set the line width.
467
468 lineWidth -- the new line width. This must be > 0.
469 """
470 assert isinstance(lineWidth, types.IntType)
471 if (lineWidth < 1):
472 raise ValueError(_("lineWidth < 1"))
473
474 self.__strokeWidth = lineWidth
475
476 def GetFill(self):
477 """Return the fill color as a Color object."""
478 return self.__fill
479
480 def SetFill(self, fill):
481 """Set the fill color.
482
483 fill -- the color of the fill. This must be a Color object.
484 """
485
486 self.__fill = fill
487
488 def __eq__(self, other):
489 """Return true if 'props' has the same attributes as this class"""
490
491 #
492 # using 'is' over '==' results in a huge performance gain
493 # in the renderer
494 #
495 return isinstance(other, ClassGroupProperties) \
496 and (self.__stroke is other.__stroke or \
497 self.__stroke == other.__stroke) \
498 and (self.__fill is other.__fill or \
499 self.__fill == other.__fill) \
500 and self.__strokeWidth == other.__strokeWidth
501
502 def __ne__(self, other):
503 return not self.__eq__(other)
504
505 def __copy__(self):
506 return ClassGroupProperties(self)
507
508 def __deepcopy__(self):
509 return ClassGroupProperties(self)
510
511 def __repr__(self):
512 return repr((self.__stroke, self.__strokeWidth, self.__fill))
513
514 class ClassGroup:
515 """A base class for all Groups within a Classification"""
516
517 def __init__(self, label = "", props = None, group = None):
518 """Constructor.
519
520 label -- A string representing the Group's label
521 """
522
523 if group is not None:
524 self.SetLabel(copy.copy(group.GetLabel()))
525 self.SetProperties(copy.copy(group.GetProperties()))
526 self.SetVisible(group.IsVisible())
527 else:
528 self.SetLabel(label)
529 self.SetProperties(props)
530 self.SetVisible(True)
531
532 def GetLabel(self):
533 """Return the Group's label."""
534 return self.label
535
536 def SetLabel(self, label):
537 """Set the Group's label.
538
539 label -- a string representing the Group's label. This must
540 not be None.
541 """
542 assert isinstance(label, types.StringTypes)
543 self.label = label
544
545 def GetDisplayText(self):
546 assert False, "GetDisplay must be overridden by subclass!"
547 return ""
548
549 def Matches(self, value):
550 """Determines if this Group is associated with the given value.
551
552 Returns False. This needs to be overridden by all subclasses.
553 """
554 assert False, "GetMatches must be overridden by subclass!"
555 return False
556
557 def GetProperties(self):
558 """Return the properties associated with the given value."""
559
560 return self.prop
561
562 def SetProperties(self, prop):
563 """Set the properties associated with this Group.
564
565 prop -- a ClassGroupProperties object. if prop is None,
566 a default set of properties is created.
567 """
568
569 if prop is None: prop = ClassGroupProperties()
570 assert isinstance(prop, ClassGroupProperties)
571 self.prop = prop
572
573 def IsVisible(self):
574 return self.visible
575
576 def SetVisible(self, visible):
577 self.visible = visible
578
579 def __eq__(self, other):
580 return isinstance(other, ClassGroup) \
581 and self.label == other.label \
582 and self.GetProperties() == other.GetProperties()
583
584 def __ne__(self, other):
585 return not self.__eq__(other)
586
587 def __repr__(self):
588 return repr(self.label) + ", " + repr(self.GetProperties())
589
590 class ClassGroupSingleton(ClassGroup):
591 """A Group that is associated with a single value."""
592
593 def __init__(self, value = 0, props = None, label = "", group = None):
594 """Constructor.
595
596 value -- the associated value.
597
598 prop -- a ClassGroupProperites object. If prop is None a default
599 set of properties is created.
600
601 label -- a label for this group.
602 """
603 ClassGroup.__init__(self, label, props, group)
604
605 self.SetValue(value)
606
607 def __copy__(self):
608 return ClassGroupSingleton(self.GetValue(),
609 self.GetProperties(),
610 self.GetLabel())
611
612 def __deepcopy__(self, memo):
613 return ClassGroupSingleton(self.GetValue(), group = self)
614
615 def GetValue(self):
616 """Return the associated value."""
617 return self.__value
618
619 def SetValue(self, value):
620 """Associate this Group with the given value."""
621 self.__value = value
622
623 def Matches(self, value):
624 """Determine if the given value matches the associated Group value."""
625
626 """Returns True if the value matches, False otherwise."""
627
628 return self.__value == value
629
630 def GetDisplayText(self):
631 label = self.GetLabel()
632
633 if label != "": return label
634
635 return str(self.GetValue())
636
637 def __eq__(self, other):
638 return ClassGroup.__eq__(self, other) \
639 and isinstance(other, ClassGroupSingleton) \
640 and self.__value == other.__value
641
642 def __repr__(self):
643 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
644
645 class ClassGroupDefault(ClassGroup):
646 """The default Group. When values do not match any other
647 Group within a Classification, the properties from this
648 class are used."""
649
650 def __init__(self, props = None, label = "", group = None):
651 """Constructor.
652
653 prop -- a ClassGroupProperites object. If prop is None a default
654 set of properties is created.
655
656 label -- a label for this group.
657 """
658
659 ClassGroup.__init__(self, label, props, group)
660
661 def __copy__(self):
662 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
663
664 def __deepcopy__(self, memo):
665 return ClassGroupDefault(label = self.GetLabel(), group = self)
666
667 def Matches(self, value):
668 return True
669
670 def GetDisplayText(self):
671 label = self.GetLabel()
672
673 if label != "": return label
674
675 return _("DEFAULT")
676
677 def __eq__(self, other):
678 return ClassGroup.__eq__(self, other) \
679 and isinstance(other, ClassGroupDefault) \
680 and self.GetProperties() == other.GetProperties()
681
682 def __repr__(self):
683 return "(" + ClassGroup.__repr__(self) + ")"
684
685 class ClassGroupRange(ClassGroup):
686 """A Group that represents a range of values that map to the same
687 set of properties."""
688
689 def __init__(self, min = 0, max = 1, props = None, label = "", group=None):
690 """Constructor.
691
692 The minumum value must be strictly less than the maximum.
693
694 min -- the minimum range value
695
696 max -- the maximum range value
697
698 prop -- a ClassGroupProperites object. If prop is None a default
699 set of properties is created.
700
701 label -- a label for this group.
702 """
703
704 ClassGroup.__init__(self, label, props, group)
705
706 #self.__min = self.__max = 0
707 #self.__range = Range("[" + repr(float(min)) + ";" +
708 #repr(float(max)) + "[")
709 self.SetRange(min, max)
710
711 def __copy__(self):
712 return ClassGroupRange(min = self.__range,
713 max = None,
714 props = self.GetProperties(),
715 label = self.GetLabel())
716
717 def __deepcopy__(self, memo):
718 return ClassGroupRange(min = copy.copy(self.__range),
719 max = copy.copy(self.GetMax()),
720 group = self)
721
722 def GetMin(self):
723 """Return the range's minimum value."""
724 return self.__range.GetRange()[1]
725
726 def SetMin(self, min):
727 """Set the range's minimum value.
728
729 min -- the new minimum. Note that this must be less than the current
730 maximum value. Use SetRange() to change both min and max values.
731 """
732
733 self.SetRange(min, self.__range.GetRange()[2])
734
735 def GetMax(self):
736 """Return the range's maximum value."""
737 return self.__range.GetRange()[2]
738
739 def SetMax(self, max):
740 """Set the range's maximum value.
741
742 max -- the new maximum. Note that this must be greater than the current
743 minimum value. Use SetRange() to change both min and max values.
744 """
745 self.SetRange(self.__range.GetRange()[1], max)
746
747 def SetRange(self, min, max = None):
748 """Set a new range.
749
750 Note that min must be strictly less than max.
751
752 min -- the new minimum value
753 min -- the new maximum value
754 """
755
756 if isinstance(min, Range):
757 self.__range = min
758 else:
759 if max is None:
760 raise ValueError()
761
762 self.__range = Range(("[", min, max, "["))
763
764 def GetRange(self):
765 """Return the range as a string"""
766 #return (self.__min, self.__max)
767 return self.__range.string(self.__range.GetRange())
768
769 def Matches(self, value):
770 """Determine if the given value lies with the current range.
771
772 The following check is used: min <= value < max.
773 """
774
775 return operator.contains(self.__range, value)
776 #return self.__min <= value < self.__max
777
778 def GetDisplayText(self):
779 label = self.GetLabel()
780
781 if label != "": return label
782
783 #return _("%s - %s") % (self.GetMin(), self.GetMax())
784 #return repr(self.__range)
785 return self.__range.string(self.__range.GetRange())
786
787 def __eq__(self, other):
788 return ClassGroup.__eq__(self, other) \
789 and isinstance(other, ClassGroupRange) \
790 and self.__range == other.__range
791 #and self.__min == other.__min \
792 #and self.__max == other.__max
793
794 def __repr__(self):
795 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
796 #return "(" + repr(self.__min) + ", " + repr(self.__max) + ", " + \
797 #ClassGroup.__repr__(self) + ")"
798
799 class ClassGroupMap(ClassGroup):
800 """Currently, this class is not used."""
801
802 FUNC_ID = "id"
803
804 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
805 ClassGroup.__init__(self, label)
806
807 self.map_type = map_type
808 self.func = func
809
810 if self.func is None:
811 self.func = func_id
812
813 def Map(self, value):
814 return self.func(value)
815
816 def GetProperties(self):
817 return None
818
819 def GetPropertiesFromValue(self, value):
820 pass
821
822 def GetDisplayText(self):
823 return "Map: " + self.map_type
824
825 #
826 # built-in mappings
827 #
828 def func_id(value):
829 return value
830

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26