/[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 1249 - (show annotations)
Fri Jun 20 09:27:19 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: 24595 byte(s)
Remove "from __future__"
        import statement since python 2.2 is the earliest supported
        version.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26