/[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 609 - (show annotations)
Fri Apr 4 13:55:59 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: 22976 byte(s)
Rename Color.None to
        Color.Transparent.
(ClassGroupProperties.SetLineColori, ClassGroupProperties.SetFill):
        Don't bother copying the color, since Colors are immutable.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26