/[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 1426 - (show annotations)
Wed Jul 16 13:22:20 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/classification.py
File MIME type: text/x-python
File size: 19605 byte(s)
Use new CLASS_CHANGED message.
(Classification): Inherit from Publisher.
(Classification.__init__): Remove the layer parameter.
        Classifications no longer need to have a parent layer.
(Classification.GetField, Classification.GetFieldType,
        Classification.SetFieldInfo): Removed. The field name is stored
        in the layer, and the type can be retreived by calling
        Layer.GetFieldType().
(Classification._set_layer, Classification.GetLayer): Removed.
        Classifications no longer have a parent layer.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26