/[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 679 - (show annotations)
Tue Apr 15 19:46:37 2003 UTC (21 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 23649 byte(s)
fix missed renaming

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 #
495 # using 'is' over '==' results in a huge performance gain
496 # in the renderer
497 #
498 return isinstance(other, ClassGroupProperties) \
499 and (self.__stroke is other.__stroke or \
500 self.__stroke == other.__stroke) \
501 and (self.__fill is other.__fill or \
502 self.__fill == other.__fill) \
503 and self.__strokeWidth == other.__strokeWidth
504
505 def __ne__(self, other):
506 return not self.__eq__(other)
507
508 def __copy__(self):
509 return ClassGroupProperties(self)
510
511 def __deepcopy__(self):
512 return ClassGroupProperties(self)
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, StringType)
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.GetProperties() == other.GetProperties()
582
583 def __ne__(self, other):
584 return not self.__eq__(other)
585
586
587 class ClassGroupSingleton(ClassGroup):
588 """A Group that is associated with a single value."""
589
590 def __init__(self, value = 0, props = None, label = "", group = None):
591 """Constructor.
592
593 value -- the associated value.
594
595 prop -- a ClassGroupProperites object. If prop is None a default
596 set of properties is created.
597
598 label -- a label for this group.
599 """
600 ClassGroup.__init__(self, label, props, group)
601
602 self.SetValue(value)
603
604 def __copy__(self):
605 return ClassGroupSingleton(self.GetValue(),
606 self.GetProperties(),
607 self.GetLabel())
608
609 def __deepcopy__(self, memo):
610 return ClassGroupSingleton(self.GetValue(), group = self)
611
612 def GetValue(self):
613 """Return the associated value."""
614 return self.__value
615
616 def SetValue(self, value):
617 """Associate this Group with the given value."""
618 self.__value = value
619
620 def Matches(self, value):
621 """Determine if the given value matches the associated Group value."""
622
623 """Returns True if the value matches, False otherwise."""
624
625 return self.__value == value
626
627 def GetDisplayText(self):
628 label = self.GetLabel()
629
630 if label != "": return label
631
632 return str(self.GetValue())
633
634 def __eq__(self, other):
635 return ClassGroup.__eq__(self, other) \
636 and isinstance(other, ClassGroupSingleton) \
637 and self.__value == other.__value
638
639 class ClassGroupDefault(ClassGroup):
640 """The default Group. When values do not match any other
641 Group within a Classification, the properties from this
642 class are used."""
643
644 def __init__(self, props = None, label = "", group = None):
645 """Constructor.
646
647 prop -- a ClassGroupProperites object. If prop is None a default
648 set of properties is created.
649
650 label -- a label for this group.
651 """
652
653 ClassGroup.__init__(self, label, props, group)
654
655 def __copy__(self):
656 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
657
658 def __deepcopy__(self, memo):
659 return ClassGroupDefault(label = self.GetLabel(), group = self)
660
661 def Matches(self, value):
662 return True
663
664 def GetDisplayText(self):
665 label = self.GetLabel()
666
667 if label != "": return label
668
669 return _("DEFAULT")
670
671 def __eq__(self, other):
672 return ClassGroup.__eq__(self, other) \
673 and isinstance(other, ClassGroupDefault) \
674 and self.GetProperties() == other.GetProperties()
675
676 class ClassGroupRange(ClassGroup):
677 """A Group that represents a range of values that map to the same
678 set of properties."""
679
680 def __init__(self, min = 0, max = 1, props = None, label = "", group=None):
681 """Constructor.
682
683 The minumum value must be strictly less than the maximum.
684
685 min -- the minimum range value
686
687 max -- the maximum range value
688
689 prop -- a ClassGroupProperites object. If prop is None a default
690 set of properties is created.
691
692 label -- a label for this group.
693 """
694
695 ClassGroup.__init__(self, label, props, group)
696
697 self.__min = self.__max = 0
698
699 self.SetRange(min, max)
700
701 def __copy__(self):
702 return ClassGroupRange(self.GetMin(),
703 self.GetMax(),
704 self.GetProperties(),
705 self.GetLabel())
706
707 def __deepcopy__(self, memo):
708 return ClassGroupRange(copy.copy(self.GetMin()),
709 copy.copy(self.GetMax()),
710 group = self)
711
712 def GetMin(self):
713 """Return the range's minimum value."""
714 return self.__min
715
716 def SetMin(self, min):
717 """Set the range's minimum value.
718
719 min -- the new minimum. Note that this must be less than the current
720 maximum value. Use SetRange() to change both min and max values.
721 """
722
723 self.SetRange(min, self.__max)
724
725 def GetMax(self):
726 """Return the range's maximum value."""
727 return self.__max
728
729 def SetMax(self, max):
730 """Set the range's maximum value.
731
732 max -- the new maximum. Note that this must be greater than the current
733 minimum value. Use SetRange() to change both min and max values.
734 """
735 self.SetRange(self.__min, max)
736
737 def SetRange(self, min, max):
738 """Set a new range.
739
740 Note that min must be strictly less than max.
741
742 min -- the new minimum value
743 min -- the new maximum value
744 """
745
746 if min >= max:
747 raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
748 (min, max))
749 self.__min = min
750 self.__max = max
751
752 def GetRange(self):
753 """Return the range as a tuple (min, max)"""
754 return (self.__min, self.__max)
755
756 def Matches(self, value):
757 """Determine if the given value lies with the current range.
758
759 The following check is used: min <= value < max.
760 """
761
762 return self.__min <= value < self.__max
763
764 def GetDisplayText(self):
765 label = self.GetLabel()
766
767 if label != "": return label
768
769 return _("%s - %s") % (self.GetMin(), self.GetMax())
770
771 def __eq__(self, other):
772 return ClassGroup.__eq__(self, other) \
773 and isinstance(other, ClassGroupRange) \
774 and self.__min == other.__min \
775 and self.__max == other.__max
776
777 class ClassGroupMap(ClassGroup):
778 """Currently, this class is not used."""
779
780 FUNC_ID = "id"
781
782 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
783 ClassGroup.__init__(self, label)
784
785 self.map_type = map_type
786 self.func = func
787
788 if self.func is None:
789 self.func = func_id
790
791 def Map(self, value):
792 return self.func(value)
793
794 def GetProperties(self):
795 return None
796
797 def GetPropertiesFromValue(self, value):
798 pass
799
800 def GetDisplayText(self):
801 return "Map: " + self.map_type
802
803 #
804 # built-in mappings
805 #
806 def func_id(value):
807 return value
808

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26