/[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 602 - (show annotations)
Fri Apr 4 12:12:18 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: 23102 byte(s)
Fix assert calls.
    (ClassGroupProperties.SetLineColor, ClassGroupProperties.SetFill):
    Copy the color parameter rather than hold onto a reference.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26