/[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 643 - (show annotations)
Thu Apr 10 17:55:57 2003 UTC (21 years, 11 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 23343 byte(s)
(ClassGroupRange.__init__): Should pass group to ClassGroup constructor.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26