/[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 528 - (show annotations)
Wed Mar 12 19:55:13 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: 22147 byte(s)
This comment is really for layer.py, but the motive was the same for
classification.py:

	Handle the cyclic references between
        a layer and its classification better, and be sure to disconnect
        the classification from the layer when the layer is destroyed
        so that we don't maintain a cyclic reference that may not be
        garbage collected.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26