/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2587 - (hide annotations)
Wed Mar 23 15:30:27 2005 UTC (19 years, 11 months ago) by jonathan
Original Path: trunk/thuban/Thuban/Model/layer.py
File MIME type: text/x-python
File size: 16556 byte(s)
Add support for adjusting the opacity of a raster layer.

1 bh 2228 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4 jonathan 412 # Jonathan Coles <[email protected]>
5 silke 2339 # Silke Reimer <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     __version__ = "$Revision$"
11    
12 bh 1599 import os
13 bh 1219 import warnings
14 bh 171
15 jan 374 from Thuban import _
16 bh 6
17 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 jonathan 1427 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
19 bh 6
20 jonathan 437 import classification
21 bh 6
22 jonathan 1338 from color import Transparent, Black
23 bh 6 from base import TitledObject, Modifiable
24 bh 1558 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
25 bh 6
26 jonathan 1158 import resource
27 bh 723
28 jonathan 2551 from color import Color
29 jonathan 1158
30 bh 6 shapetype_names = {SHAPETYPE_POINT: "Point",
31     SHAPETYPE_ARC: "Arc",
32     SHAPETYPE_POLYGON: "Polygon"}
33    
34     class BaseLayer(TitledObject, Modifiable):
35    
36     """Base class for the layers."""
37    
38 jonathan 929 def __init__(self, title, visible = True, projection = None):
39 bh 6 """Initialize the layer.
40    
41     title -- the title
42     visible -- boolean. If true the layer is visible.
43     """
44     TitledObject.__init__(self, title)
45     Modifiable.__init__(self)
46     self.visible = visible
47 jonathan 929 self.projection = projection
48 bh 6
49     def Visible(self):
50     """Return true if layer is visible"""
51     return self.visible
52    
53     def SetVisible(self, visible):
54     """Set the layer's visibility."""
55     self.visible = visible
56     self.issue(LAYER_VISIBILITY_CHANGED, self)
57 bh 276
58 jonathan 929 def HasClassification(self):
59 bernhard 2343 """Determine if this layer supports classifications."""
60 jonathan 929 return False
61 bh 276
62 jonathan 1273 def HasShapes(self):
63     """Determine if this layer supports shapes."""
64     return False
65    
66 jonathan 929 def GetProjection(self):
67     """Return the layer's projection."""
68     return self.projection
69    
70     def SetProjection(self, projection):
71 bernhard 2343 """Set the layer's projection."""
72 jonathan 929 self.projection = projection
73     self.changed(LAYER_PROJECTION_CHANGED, self)
74    
75 jonathan 2551 def Type(self):
76     return "Unknown"
77    
78 bh 6 class Layer(BaseLayer):
79    
80     """Represent the information of one geodata file (currently a shapefile)
81    
82     All children of the layer have the same type.
83    
84     A layer has fill and stroke colors. Colors should be instances of
85 jonathan 1338 Color. They can also be Transparent, indicating no fill or no stroke.
86 bh 6
87     The layer objects send the following events, all of which have the
88     layer object as parameter:
89    
90     TITLE_CHANGED -- The title has changed.
91     LAYER_PROJECTION_CHANGED -- the projection has changed.
92     """
93    
94 bh 723 def __init__(self, title, data, projection = None,
95 jonathan 1338 fill = Transparent,
96     stroke = Black,
97 jonathan 464 lineWidth = 1,
98 jonathan 771 visible = True):
99 bh 6 """Initialize the layer.
100    
101     title -- the title
102 bh 723 data -- datastore object for the shape data shown by the layer
103 bh 6 projection -- the projection object. Its Inverse method is
104     assumed to map the layer's coordinates to lat/long
105     coordinates
106 jonathan 1338 fill -- the fill color or Transparent if the shapes are
107 jonathan 610 not filled
108 jonathan 1338 stroke -- the stroke color or Transparent if the shapes
109 jonathan 610 are not stroked
110 bh 6 visible -- boolean. If true the layer is visible.
111    
112     colors are expected to be instances of Color class
113     """
114 jonathan 929 BaseLayer.__init__(self, title,
115     visible = visible,
116     projection = projection)
117 bh 276
118 bh 723 self.__classification = None
119 jonathan 1427 self.store = None
120 jonathan 481
121 bh 723 self.SetShapeStore(data)
122 jonathan 492
123 bh 1452 self.classification_column = None
124     self.SetClassificationColumn(None)
125 jonathan 492 self.SetClassification(None)
126    
127 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
128     self.__classification.SetDefaultLineWidth(lineWidth)
129 jonathan 412 self.__classification.SetDefaultFill(fill)
130 jonathan 364
131 jonathan 389 self.UnsetModified()
132    
133 bh 723 def SetShapeStore(self, store):
134 jonathan 1427 # Set the classification to None if there is a classification
135     # and the new shapestore doesn't have a table with a suitable
136     # column, i.e one with the same name and type as before
137     # FIXME: Maybe we should keep it the same if the type is
138     # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
139     if self.__classification is not None:
140 bh 1452 columnname = self.classification_column
141     columntype = self.GetFieldType(columnname)
142 jonathan 1427 table = store.Table()
143 bh 1452 if (columnname is not None
144     and (not table.HasColumn(columnname)
145     or table.Column(columnname).type != columntype)):
146 jonathan 1427 self.SetClassification(None)
147    
148 bh 723 self.store = store
149 bh 179
150 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
151 bh 723
152     def ShapeStore(self):
153     return self.store
154    
155 bh 258 def Destroy(self):
156 bh 260 BaseLayer.Destroy(self)
157 jonathan 1427 if self.__classification is not None:
158 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
159     self._classification_changed)
160 bh 258
161 bh 6 def BoundingBox(self):
162 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
163    
164     If the layer has no shapes, return None.
165     """
166 bh 1535 return self.store.BoundingBox()
167 bh 6
168     def LatLongBoundingBox(self):
169 bh 179 """Return the layer's bounding box in lat/long coordinates.
170 bh 6
171 bh 179 Return None, if the layer doesn't contain any shapes.
172     """
173     bbox = self.BoundingBox()
174 bh 1983 if bbox is not None and self.projection is not None:
175     bbox = self.projection.InverseBBox(bbox)
176     return bbox
177 bh 179
178 jonathan 2551 def Type(self):
179     return self.ShapeType();
180    
181 jonathan 828 def ShapesBoundingBox(self, shapes):
182     """Return a bounding box in lat/long coordinates for the given
183     list of shape ids.
184    
185     If shapes is None or empty, return None.
186     """
187    
188     if shapes is None or len(shapes) == 0: return None
189    
190 bh 1983 xs = []
191     ys = []
192 jonathan 828
193     for id in shapes:
194 bh 1983 bbox = self.Shape(id).compute_bbox()
195     if self.projection is not None:
196     bbox = self.projection.InverseBBox(bbox)
197     left, bottom, right, top = bbox
198     xs.append(left); xs.append(right)
199     ys.append(bottom); ys.append(top)
200 jonathan 828
201 bh 1983 return (min(xs), min(ys), max(xs), max(ys))
202 jonathan 828
203    
204 jonathan 464 def GetFieldType(self, fieldName):
205 jonathan 1427 if self.store:
206     table = self.store.Table()
207     if table.HasColumn(fieldName):
208     return table.Column(fieldName).type
209 bh 839 return None
210 jonathan 464
211 jonathan 1273 def HasShapes(self):
212     return True
213    
214 bh 6 def NumShapes(self):
215     """Return the number of shapes in the layer"""
216 bh 1535 return self.store.NumShapes()
217 bh 6
218     def ShapeType(self):
219     """Return the type of the shapes in the layer.
220 bh 1535
221     The return value is one of the SHAPETYPE_* constants defined in
222     Thuban.Model.data.
223 bh 6 """
224 bh 1535 return self.store.ShapeType()
225 bh 6
226     def Shape(self, index):
227     """Return the shape with index index"""
228 bh 1535 return self.store.Shape(index)
229 jonathan 364
230 bh 1587 def ShapesInRegion(self, bbox):
231 bh 1593 """Return an iterable over the shapes that overlap the bounding box.
232 bh 143
233 bh 1593 The bbox parameter should be the bounding box as a tuple in the
234     form (minx, miny, maxx, maxy) in unprojected coordinates.
235 bh 143 """
236 jonathan 794 if self.projection is not None:
237 silke 2339 # Ensure that region lies within the layer's bounding box
238     # Otherwise projection of the region would lead to incorrect
239     # values.
240     clipbbox = self.ClipBoundingBox(bbox)
241     bbox = self.projection.ForwardBBox(clipbbox)
242 bh 1587 return self.store.ShapesInRegion(bbox)
243 bh 143
244 bh 1452 def GetClassificationColumn(self):
245     return self.classification_column
246 jonathan 1427
247 bh 1452 def SetClassificationColumn(self, column):
248     """Set the column to classifiy on, or None. If column is not None
249     and the column does not exist in the table, raise a ValueError.
250 jonathan 1427 """
251 bh 1452 if column:
252     columnType = self.GetFieldType(column)
253     if columnType is None:
254 jonathan 1427 raise ValueError()
255 bh 1452 changed = self.classification_column != column
256     self.classification_column = column
257     if changed:
258     self.changed(LAYER_CHANGED, self)
259 jonathan 1427
260 jonathan 929 def HasClassification(self):
261     return True
262 jonathan 725
263 jonathan 412 def GetClassification(self):
264     return self.__classification
265    
266     def SetClassification(self, clazz):
267 jonathan 1338 """Set the classification used by this layer to 'clazz'
268 jonathan 481
269 jonathan 1338 If 'clazz' is None a default classification is created.
270    
271 jonathan 1427 This issues a LAYER_CHANGED event.
272 jonathan 492 """
273 jonathan 481
274 jonathan 1427 if self.__classification is not None:
275 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
276     self._classification_changed)
277 jonathan 529
278 jonathan 1338 if clazz is None:
279     clazz = classification.Classification()
280 jonathan 529
281 jonathan 1427 self.__classification = clazz
282 bh 1452 self.__classification.Subscribe(CLASS_CHANGED,
283     self._classification_changed)
284 jonathan 1338
285 bh 1452 self._classification_changed()
286 jonathan 492
287 bh 1452 def _classification_changed(self):
288 jonathan 492 """Called from the classification object when it has changed."""
289 jonathan 558 self.changed(LAYER_CHANGED, self)
290 jonathan 492
291 bh 217 def TreeInfo(self):
292     items = []
293    
294 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
295 jan 1012
296 bh 217 if self.Visible():
297 jan 374 items.append(_("Shown"))
298 bh 217 else:
299 jan 374 items.append(_("Hidden"))
300     items.append(_("Shapes: %d") % self.NumShapes())
301 bh 217
302     bbox = self.LatLongBoundingBox()
303     if bbox is not None:
304 bh 2228 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
305 bh 217 else:
306 jan 374 items.append(_("Extent (lat-lon):"))
307     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
308 bh 217
309 jonathan 736 if self.projection and len(self.projection.params) > 0:
310     items.append((_("Projection"),
311     [str(param) for param in self.projection.params]))
312    
313 jonathan 412 items.append(self.__classification)
314 bh 217
315 jan 374 return (_("Layer '%s'") % self.Title(), items)
316 jonathan 382
317 silke 2339 def ClipBoundingBox(self, bbox):
318     """ Clip bbox to layer's bounding box.
319 jonathan 736
320 silke 2339 Returns that part of bbox that lies within the layers bounding box.
321     If bbox is completely outside of the layers bounding box, bbox is
322     returned. It is assumed that bbox has sensible values, i.e. bminx
323     < bmaxx and bminy < bmaxy.
324     """
325     bminx, bminy, bmaxx, bmaxy = bbox
326     lminx, lminy, lmaxx, lmaxy = self.LatLongBoundingBox()
327     if bminx > lmaxx or bmaxx < lminx:
328     left, right = bminx, bmaxx
329     else:
330     left = max(lminx, bminx)
331     right = min(lmaxx, bmaxx)
332     if bminy > lmaxy or bmaxy < lminy:
333     bottom, top = bminy, bmaxy
334     else:
335     bottom = max(lminy, bminy)
336     top = min(lmaxy, bmaxy)
337    
338     return (left, bottom, right, top)
339    
340    
341 jonathan 1158 if resource.has_gdal_support():
342     import gdal
343     from gdalconst import GA_ReadOnly
344    
345 jonathan 929 class RasterLayer(BaseLayer):
346    
347 jonathan 2562 MASK_NONE = 0
348     MASK_BIT = 1
349     MASK_ALPHA = 2
350    
351 jonathan 929 def __init__(self, title, filename, projection = None, visible = True):
352     """Initialize the Raster Layer.
353    
354     title -- title for the layer.
355    
356     filename -- file name of the source image.
357    
358     projection -- Projection object describing the projection which
359     the source image is in.
360    
361     visible -- True is the layer should initially be visible.
362 jonathan 961
363     Throws IOError if the filename is invalid or points to a file that
364     is not in a format GDAL can use.
365 jonathan 929 """
366    
367     BaseLayer.__init__(self, title, visible = visible)
368    
369     self.projection = projection
370 bh 1599 self.filename = os.path.abspath(filename)
371 jonathan 929
372     self.bbox = -1
373    
374 jonathan 2562 self.mask_type = self.MASK_BIT
375 jonathan 2587 self.opacity = 1
376 jonathan 2551
377     self.image_info = None
378    
379 jonathan 1158 if resource.has_gdal_support():
380     #
381     # temporarily open the file so that GDAL can test if it's valid.
382     #
383     dataset = gdal.Open(self.filename, GA_ReadOnly)
384 jonathan 961
385 jonathan 1158 if dataset is None:
386     raise IOError()
387 jonathan 961
388 jonathan 2551 #
389     # while we have the file, extract some basic information
390     # that we can display later
391     #
392     self.image_info = {}
393    
394     self.image_info["nBands"] = dataset.RasterCount
395     self.image_info["Size"] = (dataset.RasterXSize, dataset.RasterYSize)
396     self.image_info["Driver"] = dataset.GetDriver().ShortName
397    
398     # store some information about the individual bands
399     # [min_value, max_value]
400     a = self.image_info["BandData"] = []
401    
402     for i in range(1, dataset.RasterCount+1):
403     band = dataset.GetRasterBand(i)
404     a.append(band.ComputeRasterMinMax())
405    
406 jonathan 929 self.UnsetModified()
407    
408     def BoundingBox(self):
409     """Return the layer's bounding box in the intrinsic coordinate system.
410    
411 jonathan 1338 If the there is no support for images, or the file cannot
412     be read, or there is no geographics information available, return None.
413 jonathan 929 """
414 jonathan 1158 if not resource.has_gdal_support():
415     return None
416    
417 jonathan 929 if self.bbox == -1:
418     dataset = gdal.Open(self.filename, GA_ReadOnly)
419     if dataset is None:
420     self.bbox = None
421     else:
422 jonathan 961 geotransform = dataset.GetGeoTransform()
423     if geotransform is None:
424     return None
425    
426     x = 0
427     y = dataset.RasterYSize
428     left = geotransform[0] + \
429     geotransform[1] * x + \
430     geotransform[2] * y
431    
432     bottom = geotransform[3] + \
433     geotransform[4] * x + \
434     geotransform[5] * y
435    
436     x = dataset.RasterXSize
437     y = 0
438     right = geotransform[0] + \
439     geotransform[1] * x + \
440     geotransform[2] * y
441    
442     top = geotransform[3] + \
443     geotransform[4] * x + \
444     geotransform[5] * y
445    
446 jonathan 929 self.bbox = (left, bottom, right, top)
447    
448     return self.bbox
449    
450     def LatLongBoundingBox(self):
451     bbox = self.BoundingBox()
452 jonathan 961 if bbox is None:
453 jonathan 929 return None
454    
455 jonathan 961 if self.projection is not None:
456 bh 1983 bbox = self.projection.InverseBBox(bbox)
457 jonathan 961
458 bh 1983 return bbox
459 jonathan 961
460 jonathan 2551 def Type(self):
461     return "Image"
462    
463 jonathan 929 def GetImageFilename(self):
464     return self.filename
465    
466 jonathan 2562 def MaskType(self):
467 jonathan 2551 """Return True if the mask should be used when rendering the layer."""
468 jonathan 2562 return self.mask_type
469 jonathan 2551
470 jonathan 2562 def SetMaskType(self, type):
471     """Set the type of mask to use.
472 jonathan 2551
473 jonathan 2562 type can be one of MASK_NONE, MASK_BIT, MASK_ALPHA
474    
475 jonathan 2551 If the state changes, a LAYER_CHANGED message is sent.
476     """
477 jonathan 2562 if type not in (self.MASK_NONE, self.MASK_BIT, self.MASK_ALPHA):
478     raise ValueError("type is invalid")
479    
480     if type != self.mask_type:
481     self.mask_type = type
482 jonathan 2551 self.changed(LAYER_CHANGED, self)
483    
484 jonathan 2587 def Opacity(self):
485     """Return the level of opacity used in alpha blending.
486 jonathan 2562 """
487 jonathan 2587 return self.opacity
488 jonathan 2562
489 jonathan 2587 def SetOpacity(self, op):
490 jonathan 2562 """Set the level of alpha opacity.
491    
492     0 <= op <= 1.
493    
494     The layer is fully opaque when op = 1.
495     """
496     if not (0 <= op <= 1):
497     raise ValueError("op out of range")
498    
499 jonathan 2587 if op != self.opacity:
500     self.opacity = op
501     self.changed(LAYER_CHANGED, self)
502 jonathan 2562
503 jonathan 2551 def ImageInfo(self):
504     return self.image_info
505    
506 jonathan 929 def TreeInfo(self):
507     items = []
508    
509 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
510    
511 jonathan 929 if self.Visible():
512     items.append(_("Shown"))
513     else:
514     items.append(_("Hidden"))
515    
516     bbox = self.LatLongBoundingBox()
517     if bbox is not None:
518     items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
519     else:
520     items.append(_("Extent (lat-lon):"))
521    
522     if self.projection and len(self.projection.params) > 0:
523     items.append((_("Projection"),
524     [str(param) for param in self.projection.params]))
525    
526     return (_("Layer '%s'") % self.Title(), items)
527    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26