/[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 1351 - (show annotations)
Tue Jul 1 16:17:02 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 24139 byte(s)
(Classificatio.__init__): Remove unused lock variable.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26