/[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 960 - (show annotations)
Wed May 21 17:23:11 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 24832 byte(s)
(ClassGroupRange.SetRange): Use new Range ___init__ to pass floats.

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 assert isinstance(fill, Color)
201 self.GetDefaultGroup().GetProperties().SetFill(fill)
202 self.__SendNotification()
203
204 def GetDefaultFill(self):
205 """Return the default fill color."""
206 return self.GetDefaultGroup().GetProperties().GetFill()
207
208 def SetDefaultLineColor(self, color):
209 """Set the default line color.
210
211 color -- a Color object.
212 """
213 assert isinstance(color, Color)
214 self.GetDefaultGroup().GetProperties().SetLineColor(color)
215 self.__SendNotification()
216
217 def GetDefaultLineColor(self):
218 """Return the default line color."""
219 return self.GetDefaultGroup().GetProperties().GetLineColor()
220
221 def SetDefaultLineWidth(self, lineWidth):
222 """Set the default line width.
223
224 lineWidth -- an integer > 0.
225 """
226 assert isinstance(lineWidth, types.IntType)
227 self.GetDefaultGroup().GetProperties().SetLineWidth(lineWidth)
228 self.__SendNotification()
229
230 def GetDefaultLineWidth(self):
231 """Return the default line width."""
232 return self.GetDefaultGroup().GetProperties().GetLineWidth()
233
234
235 #
236 # The methods that manipulate self.__groups have to be kept in
237 # sync. We store the default group in index 0 to make it
238 # convienent to iterate over the classification's groups, but
239 # from the user's perspective the first (non-default) group is
240 # at index 0 and the DefaultGroup is a special entity.
241 #
242
243 def SetDefaultGroup(self, group):
244 """Set the group to be used when a value can't be classified.
245
246 group -- group that the value maps to.
247 """
248
249 assert isinstance(group, ClassGroupDefault)
250 if len(self.__groups) > 0:
251 self.__groups[0] = group
252 else:
253 self.__groups.append(group)
254
255 def GetDefaultGroup(self):
256 """Return the default group."""
257 return self.__groups[0]
258
259 def AppendGroup(self, item):
260 """Append a new ClassGroup item to the classification.
261
262 item -- this must be a valid ClassGroup object
263 """
264
265 self.InsertGroup(self.GetNumGroups(), item)
266
267 def InsertGroup(self, index, group):
268
269 assert isinstance(group, ClassGroup)
270
271 self.__groups.insert(index + 1, group)
272
273 self.__SendNotification()
274
275 def RemoveGroup(self, index):
276 return self.__groups.pop(index + 1)
277
278 def ReplaceGroup(self, index, group):
279 assert isinstance(group, ClassGroup)
280
281 self.__groups[index + 1] = group
282
283 self.__SendNotification()
284
285 def GetGroup(self, index):
286 return self.__groups[index + 1]
287
288 def GetNumGroups(self):
289 """Return the number of non-default groups in the classification."""
290 return len(self.__groups) - 1
291
292
293 def FindGroup(self, value):
294 """Return the associated group, or the default group.
295
296 Groups are checked in the order the were added to the
297 Classification.
298
299 value -- the value to classify. If there is no mapping,
300 the field is None or value is None,
301 return the default properties
302 """
303
304 if self.GetField() is not None and value is not None:
305
306 for i in range(1, len(self.__groups)):
307 group = self.__groups[i]
308 if group.Matches(value):
309 return group
310
311 return self.GetDefaultGroup()
312
313 def GetProperties(self, value):
314 """Return the properties associated with the given value.
315
316 Use this function rather than Classification.FindGroup().GetProperties()
317 since the returned group may be a ClassGroupMap which doesn't support
318 a call to GetProperties().
319 """
320
321 group = self.FindGroup(value)
322 if isinstance(group, ClassGroupMap):
323 return group.GetPropertiesFromValue(value)
324 else:
325 return group.GetProperties()
326
327 def TreeInfo(self):
328 items = []
329
330 def build_color_item(text, color):
331 if color is Color.Transparent:
332 return ("%s: %s" % (text, _("None")), None)
333
334 return ("%s: (%.3f, %.3f, %.3f)" %
335 (text, color.red, color.green, color.blue),
336 color)
337
338 def build_item(group, string):
339 label = group.GetLabel()
340 if label == "":
341 label = string
342 else:
343 label += " (%s)" % string
344
345 props = group.GetProperties()
346 i = []
347 v = props.GetLineColor()
348 i.append(build_color_item(_("Line Color"), v))
349 v = props.GetLineWidth()
350 i.append(_("Line Width: %s") % v)
351 v = props.GetFill()
352 i.append(build_color_item(_("Fill"), v))
353 return (label, i)
354
355 for p in self:
356 items.append(build_item(p, p.GetDisplayText()))
357
358 # if isinstance(p, ClassGroupDefault):
359 # items.append(build_item(self.GetDefaultGroup(), _("'DEFAULT'")))
360 # elif isinstance(p, ClassGroupSingleton):
361 # items.append(build_item(p, str(p.GetValue())))
362 # elif isinstance(p, ClassGroupRange):
363 # items.append(build_item(p, "%s - %s" %
364 # (p.GetMin(), p.GetMax())))
365
366 return (_("Classification"), items)
367
368 class ClassIterator:
369 """Allows the Groups in a Classifcation to be interated over.
370
371 The items are returned in the following order:
372 default data, singletons, ranges, maps
373 """
374
375 def __init__(self, data): #default, points, ranges, maps):
376 """Constructor.
377
378 default -- the default group
379
380 points -- a list of singleton groups
381
382 ranges -- a list of range groups
383
384 maps -- a list of map groups
385 """
386
387 self.data = data #[default, points, ranges, maps]
388 self.data_index = 0
389 #self.data_iter = iter(self.data)
390 #self.iter = None
391
392 def __iter__(self):
393 return self
394
395 def next(self):
396 """Return the next item."""
397
398 if self.data_index >= len(self.data):
399 raise StopIteration
400 else:
401 d = self.data[self.data_index]
402 self.data_index += 1
403 return d
404
405 # if self.iter is None:
406 # try:
407 # self.data_item = self.data_iter.next()
408 # self.iter = iter(self.data_item)
409 # except TypeError:
410 # return self.data_item
411
412 # try:
413 # return self.iter.next()
414 # except StopIteration:
415 # self.iter = None
416 # return self.next()
417
418 class ClassGroupProperties:
419 """Represents the properties of a single Classification Group.
420
421 These are used when rendering a layer."""
422
423 def __init__(self, props = None):
424 """Constructor.
425
426 props -- a ClassGroupProperties object. The class is copied if
427 prop is not None. Otherwise, a default set of properties
428 is created such that: line color = Color.Black, line width = 1,
429 and fill color = Color.Transparent
430 """
431
432 #self.stroke = None
433 #self.strokeWidth = 0
434 #self.fill = None
435
436 if props is not None:
437 self.SetProperties(props)
438 else:
439 self.SetLineColor(Color.Black)
440 self.SetLineWidth(1)
441 self.SetFill(Color.Transparent)
442
443 def SetProperties(self, props):
444 """Set this class's properties to those in class props."""
445
446 assert isinstance(props, ClassGroupProperties)
447 self.SetLineColor(props.GetLineColor())
448 self.SetLineWidth(props.GetLineWidth())
449 self.SetFill(props.GetFill())
450
451 def GetLineColor(self):
452 """Return the line color as a Color object."""
453 return self.__stroke
454
455 def SetLineColor(self, color):
456 """Set the line color.
457
458 color -- the color of the line. This must be a Color object.
459 """
460
461 assert isinstance(color, Color)
462 self.__stroke = color
463
464 def GetLineWidth(self):
465 """Return the line width."""
466 return self.__strokeWidth
467
468 def SetLineWidth(self, lineWidth):
469 """Set the line width.
470
471 lineWidth -- the new line width. This must be > 0.
472 """
473 assert isinstance(lineWidth, types.IntType)
474 if (lineWidth < 1):
475 raise ValueError(_("lineWidth < 1"))
476
477 self.__strokeWidth = lineWidth
478
479 def GetFill(self):
480 """Return the fill color as a Color object."""
481 return self.__fill
482
483 def SetFill(self, fill):
484 """Set the fill color.
485
486 fill -- the color of the fill. This must be a Color object.
487 """
488
489 assert isinstance(fill, Color)
490 self.__fill = fill
491
492 def __eq__(self, other):
493 """Return true if 'props' has the same attributes as this class"""
494
495 #
496 # using 'is' over '==' results in a huge performance gain
497 # in the renderer
498 #
499 return isinstance(other, ClassGroupProperties) \
500 and (self.__stroke is other.__stroke or \
501 self.__stroke == other.__stroke) \
502 and (self.__fill is other.__fill or \
503 self.__fill == other.__fill) \
504 and self.__strokeWidth == other.__strokeWidth
505
506 def __ne__(self, other):
507 return not self.__eq__(other)
508
509 def __copy__(self):
510 return ClassGroupProperties(self)
511
512 def __deepcopy__(self):
513 return ClassGroupProperties(self)
514
515 def __repr__(self):
516 return repr((self.__stroke, self.__strokeWidth, self.__fill))
517
518 class ClassGroup:
519 """A base class for all Groups within a Classification"""
520
521 def __init__(self, label = "", props = None, group = None):
522 """Constructor.
523
524 label -- A string representing the Group's label
525 """
526
527 if group is not None:
528 self.SetLabel(copy.copy(group.GetLabel()))
529 self.SetProperties(copy.copy(group.GetProperties()))
530 self.SetVisible(group.IsVisible())
531 else:
532 self.SetLabel(label)
533 self.SetProperties(props)
534 self.SetVisible(True)
535
536 def GetLabel(self):
537 """Return the Group's label."""
538 return self.label
539
540 def SetLabel(self, label):
541 """Set the Group's label.
542
543 label -- a string representing the Group's label. This must
544 not be None.
545 """
546 assert isinstance(label, types.StringTypes)
547 self.label = label
548
549 def GetDisplayText(self):
550 assert False, "GetDisplay must be overridden by subclass!"
551 return ""
552
553 def Matches(self, value):
554 """Determines if this Group is associated with the given value.
555
556 Returns False. This needs to be overridden by all subclasses.
557 """
558 assert False, "GetMatches must be overridden by subclass!"
559 return False
560
561 def GetProperties(self):
562 """Return the properties associated with the given value."""
563
564 return self.prop
565
566 def SetProperties(self, prop):
567 """Set the properties associated with this Group.
568
569 prop -- a ClassGroupProperties object. if prop is None,
570 a default set of properties is created.
571 """
572
573 if prop is None: prop = ClassGroupProperties()
574 assert isinstance(prop, ClassGroupProperties)
575 self.prop = prop
576
577 def IsVisible(self):
578 return self.visible
579
580 def SetVisible(self, visible):
581 self.visible = visible
582
583 def __eq__(self, other):
584 return isinstance(other, ClassGroup) \
585 and self.label == other.label \
586 and self.GetProperties() == other.GetProperties()
587
588 def __ne__(self, other):
589 return not self.__eq__(other)
590
591 def __repr__(self):
592 return repr(self.label) + ", " + repr(self.GetProperties())
593
594 class ClassGroupSingleton(ClassGroup):
595 """A Group that is associated with a single value."""
596
597 def __init__(self, value = 0, props = None, label = "", group = None):
598 """Constructor.
599
600 value -- the associated value.
601
602 prop -- a ClassGroupProperites object. If prop is None a default
603 set of properties is created.
604
605 label -- a label for this group.
606 """
607 ClassGroup.__init__(self, label, props, group)
608
609 self.SetValue(value)
610
611 def __copy__(self):
612 return ClassGroupSingleton(self.GetValue(),
613 self.GetProperties(),
614 self.GetLabel())
615
616 def __deepcopy__(self, memo):
617 return ClassGroupSingleton(self.GetValue(), group = self)
618
619 def GetValue(self):
620 """Return the associated value."""
621 return self.__value
622
623 def SetValue(self, value):
624 """Associate this Group with the given value."""
625 self.__value = value
626
627 def Matches(self, value):
628 """Determine if the given value matches the associated Group value."""
629
630 """Returns True if the value matches, False otherwise."""
631
632 return self.__value == value
633
634 def GetDisplayText(self):
635 label = self.GetLabel()
636
637 if label != "": return label
638
639 return str(self.GetValue())
640
641 def __eq__(self, other):
642 return ClassGroup.__eq__(self, other) \
643 and isinstance(other, ClassGroupSingleton) \
644 and self.__value == other.__value
645
646 def __repr__(self):
647 return "(" + repr(self.__value) + ", " + ClassGroup.__repr__(self) + ")"
648
649 class ClassGroupDefault(ClassGroup):
650 """The default Group. When values do not match any other
651 Group within a Classification, the properties from this
652 class are used."""
653
654 def __init__(self, props = None, label = "", group = None):
655 """Constructor.
656
657 prop -- a ClassGroupProperites object. If prop is None a default
658 set of properties is created.
659
660 label -- a label for this group.
661 """
662
663 ClassGroup.__init__(self, label, props, group)
664
665 def __copy__(self):
666 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
667
668 def __deepcopy__(self, memo):
669 return ClassGroupDefault(label = self.GetLabel(), group = self)
670
671 def Matches(self, value):
672 return True
673
674 def GetDisplayText(self):
675 label = self.GetLabel()
676
677 if label != "": return label
678
679 return _("DEFAULT")
680
681 def __eq__(self, other):
682 return ClassGroup.__eq__(self, other) \
683 and isinstance(other, ClassGroupDefault) \
684 and self.GetProperties() == other.GetProperties()
685
686 def __repr__(self):
687 return "(" + ClassGroup.__repr__(self) + ")"
688
689 class ClassGroupRange(ClassGroup):
690 """A Group that represents a range of values that map to the same
691 set of properties."""
692
693 def __init__(self, min = 0, max = 1, props = None, label = "", group=None):
694 """Constructor.
695
696 The minumum value must be strictly less than the maximum.
697
698 min -- the minimum range value
699
700 max -- the maximum range value
701
702 prop -- a ClassGroupProperites object. If prop is None a default
703 set of properties is created.
704
705 label -- a label for this group.
706 """
707
708 ClassGroup.__init__(self, label, props, group)
709
710 #self.__min = self.__max = 0
711 #self.__range = Range("[" + repr(float(min)) + ";" +
712 #repr(float(max)) + "[")
713 self.SetRange(min, max)
714
715 def __copy__(self):
716 return ClassGroupRange(min = self.__range,
717 max = None,
718 props = self.GetProperties(),
719 label = self.GetLabel())
720
721 def __deepcopy__(self, memo):
722 return ClassGroupRange(min = copy.copy(self.__range),
723 max = copy.copy(self.GetMax()),
724 group = self)
725
726 def GetMin(self):
727 """Return the range's minimum value."""
728 return self.__range.GetRange()[1]
729
730 def SetMin(self, min):
731 """Set the range's minimum value.
732
733 min -- the new minimum. Note that this must be less than the current
734 maximum value. Use SetRange() to change both min and max values.
735 """
736
737 self.SetRange(min, self.__range.GetRange()[2])
738
739 def GetMax(self):
740 """Return the range's maximum value."""
741 return self.__range.GetRange()[2]
742
743 def SetMax(self, max):
744 """Set the range's maximum value.
745
746 max -- the new maximum. Note that this must be greater than the current
747 minimum value. Use SetRange() to change both min and max values.
748 """
749 self.SetRange(self.__range.GetRange()[1], max)
750
751 def SetRange(self, min, max = None):
752 """Set a new range.
753
754 Note that min must be strictly less than max.
755
756 min -- the new minimum value
757 min -- the new maximum value
758 """
759
760 if isinstance(min, Range):
761 self.__range = min
762 else:
763 if max is None:
764 raise ValueError()
765
766 self.__range = Range(("[", min, max, "["))
767
768 def GetRange(self):
769 """Return the range as a string"""
770 #return (self.__min, self.__max)
771 return self.__range.string(self.__range.GetRange())
772
773 def Matches(self, value):
774 """Determine if the given value lies with the current range.
775
776 The following check is used: min <= value < max.
777 """
778
779 return operator.contains(self.__range, value)
780 #return self.__min <= value < self.__max
781
782 def GetDisplayText(self):
783 label = self.GetLabel()
784
785 if label != "": return label
786
787 #return _("%s - %s") % (self.GetMin(), self.GetMax())
788 #return repr(self.__range)
789 return self.__range.string(self.__range.GetRange())
790
791 def __eq__(self, other):
792 return ClassGroup.__eq__(self, other) \
793 and isinstance(other, ClassGroupRange) \
794 and self.__range == other.__range
795 #and self.__min == other.__min \
796 #and self.__max == other.__max
797
798 def __repr__(self):
799 return "(" + str(self.__range) + ClassGroup.__repr__(self) + ")"
800 #return "(" + repr(self.__min) + ", " + repr(self.__max) + ", " + \
801 #ClassGroup.__repr__(self) + ")"
802
803 class ClassGroupMap(ClassGroup):
804 """Currently, this class is not used."""
805
806 FUNC_ID = "id"
807
808 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
809 ClassGroup.__init__(self, label)
810
811 self.map_type = map_type
812 self.func = func
813
814 if self.func is None:
815 self.func = func_id
816
817 def Map(self, value):
818 return self.func(value)
819
820 def GetProperties(self):
821 return None
822
823 def GetPropertiesFromValue(self, value):
824 pass
825
826 def GetDisplayText(self):
827 return "Map: " + self.map_type
828
829 #
830 # built-in mappings
831 #
832 def func_id(value):
833 return value
834

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26