/[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 484 - (show annotations)
Fri Mar 7 18:20:10 2003 UTC (22 years ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 21237 byte(s)
Implemented __copy__ and __deepcopy__ for ClassGroup* and ClassGroupProperites
so they can easily be copied by the classifier dialog.
(ClassGroupProperites.__init__): The default line color should
        have been Color.Black.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26