/[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 544 - (show annotations)
Thu Mar 20 09:43:48 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: 22901 byte(s)
(ClassGroup.GetDisplayText): New.  Returns a string which is appropriately
describes the group.

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
410
411 def GetLineWidth(self):
412 """Return the line width."""
413 return self.strokeWidth
414
415 def SetLineWidth(self, lineWidth):
416 """Set the line width.
417
418 lineWidth -- the new line width. This must be > 0.
419 """
420 assert(isinstance(lineWidth, IntType))
421 if (lineWidth < 1):
422 raise ValueError(_("lineWidth < 1"))
423
424 self.strokeWidth = lineWidth
425
426 def GetFill(self):
427 """Return the fill color as a Color object."""
428 return self.fill
429
430 def SetFill(self, fill):
431 """Set the fill color.
432
433 fill -- the color of the fill. This must be a Color object.
434 """
435
436 assert(isinstance(fill, Color))
437 self.fill = fill
438
439 def __eq__(self, other):
440 """Return true if 'props' has the same attributes as this class"""
441
442 return isinstance(other, ClassGroupProperties) \
443 and self.stroke == other.GetLineColor() \
444 and self.strokeWidth == other.GetLineWidth() \
445 and self.fill == other.GetFill()
446
447 def __ne__(self, other):
448 return not self.__eq__(other)
449
450 def __copy__(self):
451 return ClassGroupProperties(self)
452
453 class ClassGroup:
454 """A base class for all Groups within a Classification"""
455
456 def __init__(self, label = ""):
457 """Constructor.
458
459 label -- A string representing the Group's label
460 """
461
462 self.label = None
463
464 self.SetLabel(label)
465
466 def GetLabel(self):
467 """Return the Group's label."""
468 return self.label
469
470 def SetLabel(self, label):
471 """Set the Group's label.
472
473 label -- a string representing the Group's label. This must
474 not be None.
475 """
476 assert(isinstance(label, StringType))
477 self.label = label
478
479 def GetDisplayText(self):
480 assert(False, "GetDisplay must be overridden by subclass!")
481 return ""
482
483 def Matches(self, value):
484 """Determines if this Group is associated with the given value.
485
486 Returns False. This needs to be overridden by all subclasses.
487 """
488 assert(False, "GetMatches must be overridden by subclass!")
489 return False
490
491 def GetProperties(self):
492 """Return the properties associated with the given value.
493
494 Returns None. This needs to be overridden by all subclasses.
495 """
496 assert(False, "GetProperties must be overridden by subclass!")
497 return None
498
499
500 class ClassGroupSingleton(ClassGroup):
501 """A Group that is associated with a single value."""
502
503 def __init__(self, value = 0, prop = None, label = ""):
504 """Constructor.
505
506 value -- the associated value.
507
508 prop -- a ClassGroupProperites object. If prop is None a default
509 set of properties is created.
510
511 label -- a label for this group.
512 """
513 ClassGroup.__init__(self, label)
514
515 self.prop = None
516 self.value = None
517
518 self.SetValue(value)
519 self.SetProperties(prop)
520
521 def __copy__(self):
522 return ClassGroupSingleton(self.GetValue(),
523 self.GetProperties(),
524 self.GetLabel())
525
526 def __deepcopy__(self, memo):
527 return ClassGroupSingleton(copy.copy(self.GetValue()),
528 copy.copy(self.GetProperties()),
529 copy.copy(self.GetLabel()))
530
531 def GetValue(self):
532 """Return the associated value."""
533 return self.value
534
535 def SetValue(self, value):
536 """Associate this Group with the given value."""
537 self.value = value
538
539 def Matches(self, value):
540 """Determine if the given value matches the associated Group value."""
541
542 """Returns True if the value matches, False otherwise."""
543
544 return self.value == value
545
546 def GetProperties(self):
547 """Return the Properties associated with this Group."""
548
549 return self.prop
550
551 def SetProperties(self, prop):
552 """Set the properties associated with this Group.
553
554 prop -- a ClassGroupProperties object. if prop is None,
555 a default set of properties is created.
556 """
557
558 if prop is None: prop = ClassGroupProperties()
559 assert(isinstance(prop, ClassGroupProperties))
560 self.prop = prop
561
562 def GetDisplayText(self):
563 label = self.GetLabel()
564
565 if label != "": return label
566
567 return str(self.GetValue())
568
569 def __eq__(self, other):
570 return isinstance(other, ClassGroupSingleton) \
571 and self.GetProperties() == other.GetProperties() \
572 and self.GetValue() == other.GetValue()
573
574 def __ne__(self, other):
575 return not self.__eq__(other)
576
577 class ClassGroupDefault(ClassGroup):
578 """The default Group. When values do not match any other
579 Group within a Classification, the properties from this
580 class are used."""
581
582 def __init__(self, prop = None, label = ""):
583 """Constructor.
584
585 prop -- a ClassGroupProperites object. If prop is None a default
586 set of properties is created.
587
588 label -- a label for this group.
589 """
590
591 ClassGroup.__init__(self, label)
592 self.SetProperties(prop)
593
594 def __copy__(self):
595 return ClassGroupDefault(self.GetProperties(), self.GetLabel())
596
597 def __deepcopy__(self, memo):
598 return ClassGroupDefault(copy.copy(self.GetProperties()),
599 copy.copy(self.GetLabel()))
600
601 def Matches(self, value):
602 return True
603
604 def GetProperties(self):
605 """Return the Properties associated with this Group."""
606 return self.prop
607
608 def SetProperties(self, prop):
609 """Set the properties associated with this Group.
610
611 prop -- a ClassGroupProperties object. if prop is None,
612 a default set of properties is created.
613 """
614
615 if prop is None: prop = ClassGroupProperties()
616 assert(isinstance(prop, ClassGroupProperties))
617 self.prop = prop
618
619 def GetDisplayText(self):
620 label = self.GetLabel()
621
622 if label != "": return label
623
624 return "DEFAULT"
625
626 def __eq__(self, other):
627 return isinstance(other, ClassGroupDefault) \
628 and self.GetProperties() == other.GetProperties()
629
630 def __ne__(self, other):
631 return not self.__eq__(other)
632
633 class ClassGroupRange(ClassGroup):
634 """A Group that represents a range of values that map to the same
635 set of properties."""
636
637 def __init__(self, min = 0, max = 1, prop = None, label = ""):
638 """Constructor.
639
640 The minumum value must be strictly less than the maximum.
641
642 min -- the minimum range value
643
644 max -- the maximum range value
645
646 prop -- a ClassGroupProperites object. If prop is None a default
647 set of properties is created.
648
649 label -- a label for this group.
650 """
651
652 ClassGroup.__init__(self, label)
653
654 self.min = self.max = 0
655 self.prop = None
656
657 self.SetRange(min, max)
658 self.SetProperties(prop)
659
660 def __copy__(self):
661 return ClassGroupRange(self.GetMin(),
662 self.GetMax(),
663 self.GetProperties(),
664 self.GetLabel())
665
666 def __deepcopy__(self, memo):
667 return ClassGroupRange(copy.copy(self.GetMin()),
668 copy.copy(self.GetMax()),
669 copy.copy(self.GetProperties()),
670 copy.copy(self.GetLabel()))
671
672 def GetMin(self):
673 """Return the range's minimum value."""
674 return self.min
675
676 def SetMin(self, min):
677 """Set the range's minimum value.
678
679 min -- the new minimum. Note that this must be less than the current
680 maximum value. Use SetRange() to change both min and max values.
681 """
682
683 self.SetRange(min, self.max)
684
685 def GetMax(self):
686 """Return the range's maximum value."""
687 return self.max
688
689 def SetMax(self, max):
690 """Set the range's maximum value.
691
692 max -- the new maximum. Note that this must be greater than the current
693 minimum value. Use SetRange() to change both min and max values.
694 """
695 self.SetRange(self.min, max)
696
697 def SetRange(self, min, max):
698 """Set a new range.
699
700 Note that min must be strictly less than max.
701
702 min -- the new minimum value
703 min -- the new maximum value
704 """
705
706 if min >= max:
707 raise ValueError(_("ClassGroupRange: %i(min) >= %i(max)!") %
708 (min, max))
709 self.min = min
710 self.max = max
711
712 def GetRange(self):
713 """Return the range as a tuple (min, max)"""
714 return (self.min, self.max)
715
716 def Matches(self, value):
717 """Determine if the given value lies with the current range.
718
719 The following check is used: min <= value < max.
720 """
721
722 return self.min <= value < self.max
723
724 def GetProperties(self):
725 """Return the Properties associated with this Group."""
726 return self.prop
727
728 def SetProperties(self, prop):
729 """Set the properties associated with this Group.
730
731 prop -- a ClassGroupProperties object. if prop is None,
732 a default set of properties is created.
733 """
734 if prop is None: prop = ClassGroupProperties()
735 assert(isinstance(prop, ClassGroupProperties))
736 self.prop = prop
737
738 def GetDisplayText(self):
739 label = self.GetLabel()
740
741 if label != "": return label
742
743 return _("%s - %s") % (self.GetMin(), self.GetMax())
744
745 def __eq__(self, other):
746 return isinstance(other, ClassGroupRange) \
747 and self.GetProperties() == other.GetProperties() \
748 and self.GetRange() == other.GetRange()
749
750 def __ne__(self, other):
751 return not self.__eq__(other)
752
753 class ClassGroupMap(ClassGroup):
754 """Currently, this class is not used."""
755
756 FUNC_ID = "id"
757
758 def __init__(self, map_type = FUNC_ID, func = None, prop = None, label=""):
759 ClassGroup.__init__(self, label)
760
761 self.map_type = map_type
762 self.func = func
763
764 if self.func is None:
765 self.func = func_id
766
767 def Map(self, value):
768 return self.func(value)
769
770 def GetProperties(self):
771 return None
772
773 def GetPropertiesFromValue(self, value):
774 pass
775
776 def GetDisplayText(self):
777 return "Map: " + self.map_type
778
779 #
780 # built-in mappings
781 #
782 def func_id(value):
783 return value
784

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26