/[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 1336 - (show annotations)
Tue Jul 1 16:09:26 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: 24176 byte(s)
 Fixes RTbug #1973, 1971.
(Classification.SetField, Classification.SetFieldType):
        Replaced with SetFieldInfo.
(Classification.SetFieldInfo): New. Does a better job of
        what SetField and SetFieldType used to do by combining
        their function since they should really always be done
        at the same time.
(Classification.SetLayer): Renamed to _set_layer.
(Classification._set_layer): Should only be called from
        Layer's SetClassification. This does not cause a recursive
        call as SetLayer did because we know that Layer knows about
        the classification.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26