/[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 491 - (show annotations)
Mon Mar 10 10:44:42 2003 UTC (22 years ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 22029 byte(s)
(Classification): Don't use
        layer's message function directly, use the ClassChanged() method
        when then classification changes. SetField/SetFieldType/SetLayer
        must keep the information about field name and field type in
        sync when an owning layer is set or removed.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26