/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Contents of /trunk/thuban/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2614 - (show annotations)
Fri May 6 14:16:38 2005 UTC (19 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 16609 byte(s)
(RasterLayer): Added opacity and masktype parameters to the constructor, and
set the appropriate variables to those values.

1 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jonathan Coles <[email protected]>
5 # Silke Reimer <[email protected]>
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 import os
13 import warnings
14
15 from Thuban import _
16
17 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
19
20 import classification
21
22 from color import Transparent, Black
23 from base import TitledObject, Modifiable
24 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
25
26 import resource
27
28 from color import Color
29
30 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 def __init__(self, title, visible = True, projection = None):
39 """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 self.projection = projection
48
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
58 def HasClassification(self):
59 """Determine if this layer supports classifications."""
60 return False
61
62 def HasShapes(self):
63 """Determine if this layer supports shapes."""
64 return False
65
66 def GetProjection(self):
67 """Return the layer's projection."""
68 return self.projection
69
70 def SetProjection(self, projection):
71 """Set the layer's projection."""
72 self.projection = projection
73 self.changed(LAYER_PROJECTION_CHANGED, self)
74
75 def Type(self):
76 return "Unknown"
77
78 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 Color. They can also be Transparent, indicating no fill or no stroke.
86
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 def __init__(self, title, data, projection = None,
95 fill = Transparent,
96 stroke = Black,
97 lineWidth = 1,
98 visible = True):
99 """Initialize the layer.
100
101 title -- the title
102 data -- datastore object for the shape data shown by the layer
103 projection -- the projection object. Its Inverse method is
104 assumed to map the layer's coordinates to lat/long
105 coordinates
106 fill -- the fill color or Transparent if the shapes are
107 not filled
108 stroke -- the stroke color or Transparent if the shapes
109 are not stroked
110 visible -- boolean. If true the layer is visible.
111
112 colors are expected to be instances of Color class
113 """
114 BaseLayer.__init__(self, title,
115 visible = visible,
116 projection = projection)
117
118 self.__classification = None
119 self.store = None
120
121 self.SetShapeStore(data)
122
123 self.classification_column = None
124 self.SetClassificationColumn(None)
125 self.SetClassification(None)
126
127 self.__classification.SetDefaultLineColor(stroke)
128 self.__classification.SetDefaultLineWidth(lineWidth)
129 self.__classification.SetDefaultFill(fill)
130
131 self.UnsetModified()
132
133 def SetShapeStore(self, store):
134 # 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 columnname = self.classification_column
141 columntype = self.GetFieldType(columnname)
142 table = store.Table()
143 if (columnname is not None
144 and (not table.HasColumn(columnname)
145 or table.Column(columnname).type != columntype)):
146 self.SetClassification(None)
147
148 self.store = store
149
150 self.changed(LAYER_SHAPESTORE_REPLACED, self)
151
152 def ShapeStore(self):
153 return self.store
154
155 def Destroy(self):
156 BaseLayer.Destroy(self)
157 if self.__classification is not None:
158 self.__classification.Unsubscribe(CLASS_CHANGED,
159 self._classification_changed)
160
161 def BoundingBox(self):
162 """Return the layer's bounding box in the intrinsic coordinate system.
163
164 If the layer has no shapes, return None.
165 """
166 return self.store.BoundingBox()
167
168 def LatLongBoundingBox(self):
169 """Return the layer's bounding box in lat/long coordinates.
170
171 Return None, if the layer doesn't contain any shapes.
172 """
173 bbox = self.BoundingBox()
174 if bbox is not None and self.projection is not None:
175 bbox = self.projection.InverseBBox(bbox)
176 return bbox
177
178 def Type(self):
179 return self.ShapeType();
180
181 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 xs = []
191 ys = []
192
193 for id in shapes:
194 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
201 return (min(xs), min(ys), max(xs), max(ys))
202
203
204 def GetFieldType(self, fieldName):
205 if self.store:
206 table = self.store.Table()
207 if table.HasColumn(fieldName):
208 return table.Column(fieldName).type
209 return None
210
211 def HasShapes(self):
212 return True
213
214 def NumShapes(self):
215 """Return the number of shapes in the layer"""
216 return self.store.NumShapes()
217
218 def ShapeType(self):
219 """Return the type of the shapes in the layer.
220
221 The return value is one of the SHAPETYPE_* constants defined in
222 Thuban.Model.data.
223 """
224 return self.store.ShapeType()
225
226 def Shape(self, index):
227 """Return the shape with index index"""
228 return self.store.Shape(index)
229
230 def ShapesInRegion(self, bbox):
231 """Return an iterable over the shapes that overlap the bounding box.
232
233 The bbox parameter should be the bounding box as a tuple in the
234 form (minx, miny, maxx, maxy) in unprojected coordinates.
235 """
236 if self.projection is not None:
237 # 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 return self.store.ShapesInRegion(bbox)
243
244 def GetClassificationColumn(self):
245 return self.classification_column
246
247 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 """
251 if column:
252 columnType = self.GetFieldType(column)
253 if columnType is None:
254 raise ValueError()
255 changed = self.classification_column != column
256 self.classification_column = column
257 if changed:
258 self.changed(LAYER_CHANGED, self)
259
260 def HasClassification(self):
261 return True
262
263 def GetClassification(self):
264 return self.__classification
265
266 def SetClassification(self, clazz):
267 """Set the classification used by this layer to 'clazz'
268
269 If 'clazz' is None a default classification is created.
270
271 This issues a LAYER_CHANGED event.
272 """
273
274 if self.__classification is not None:
275 self.__classification.Unsubscribe(CLASS_CHANGED,
276 self._classification_changed)
277
278 if clazz is None:
279 clazz = classification.Classification()
280
281 self.__classification = clazz
282 self.__classification.Subscribe(CLASS_CHANGED,
283 self._classification_changed)
284
285 self._classification_changed()
286
287 def _classification_changed(self):
288 """Called from the classification object when it has changed."""
289 self.changed(LAYER_CHANGED, self)
290
291 def TreeInfo(self):
292 items = []
293
294 items.append(_("Filename: %s") % self.ShapeStore().FileName())
295
296 if self.Visible():
297 items.append(_("Shown"))
298 else:
299 items.append(_("Hidden"))
300 items.append(_("Shapes: %d") % self.NumShapes())
301
302 bbox = self.LatLongBoundingBox()
303 if bbox is not None:
304 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
305 else:
306 items.append(_("Extent (lat-lon):"))
307 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
308
309 if self.projection and len(self.projection.params) > 0:
310 items.append((_("Projection"),
311 [str(param) for param in self.projection.params]))
312
313 items.append(self.__classification)
314
315 return (_("Layer '%s'") % self.Title(), items)
316
317 def ClipBoundingBox(self, bbox):
318 """ Clip bbox to layer's bounding box.
319
320 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 if resource.has_gdal_support():
342 import gdal
343 from gdalconst import GA_ReadOnly
344
345 class RasterLayer(BaseLayer):
346
347 MASK_NONE = 0
348 MASK_BIT = 1
349 MASK_ALPHA = 2
350
351 def __init__(self, title, filename, projection = None,
352 visible = True, opacity = 1, masktype = MASK_BIT):
353 """Initialize the Raster Layer.
354
355 title -- title for the layer.
356
357 filename -- file name of the source image.
358
359 projection -- Projection object describing the projection which
360 the source image is in.
361
362 visible -- True is the layer should initially be visible.
363
364 Throws IOError if the filename is invalid or points to a file that
365 is not in a format GDAL can use.
366 """
367
368 BaseLayer.__init__(self, title, visible = visible)
369
370 self.projection = projection
371 self.filename = os.path.abspath(filename)
372
373 self.bbox = -1
374
375 self.mask_type = masktype
376 self.opacity = opacity
377
378 self.image_info = None
379
380 if resource.has_gdal_support():
381 #
382 # temporarily open the file so that GDAL can test if it's valid.
383 #
384 dataset = gdal.Open(self.filename, GA_ReadOnly)
385
386 if dataset is None:
387 raise IOError()
388
389 #
390 # while we have the file, extract some basic information
391 # that we can display later
392 #
393 self.image_info = {}
394
395 self.image_info["nBands"] = dataset.RasterCount
396 self.image_info["Size"] = (dataset.RasterXSize, dataset.RasterYSize)
397 self.image_info["Driver"] = dataset.GetDriver().ShortName
398
399 # store some information about the individual bands
400 # [min_value, max_value]
401 a = self.image_info["BandData"] = []
402
403 for i in range(1, dataset.RasterCount+1):
404 band = dataset.GetRasterBand(i)
405 a.append(band.ComputeRasterMinMax())
406
407 self.UnsetModified()
408
409 def BoundingBox(self):
410 """Return the layer's bounding box in the intrinsic coordinate system.
411
412 If the there is no support for images, or the file cannot
413 be read, or there is no geographics information available, return None.
414 """
415 if not resource.has_gdal_support():
416 return None
417
418 if self.bbox == -1:
419 dataset = gdal.Open(self.filename, GA_ReadOnly)
420 if dataset is None:
421 self.bbox = None
422 else:
423 geotransform = dataset.GetGeoTransform()
424 if geotransform is None:
425 return None
426
427 x = 0
428 y = dataset.RasterYSize
429 left = geotransform[0] + \
430 geotransform[1] * x + \
431 geotransform[2] * y
432
433 bottom = geotransform[3] + \
434 geotransform[4] * x + \
435 geotransform[5] * y
436
437 x = dataset.RasterXSize
438 y = 0
439 right = geotransform[0] + \
440 geotransform[1] * x + \
441 geotransform[2] * y
442
443 top = geotransform[3] + \
444 geotransform[4] * x + \
445 geotransform[5] * y
446
447 self.bbox = (left, bottom, right, top)
448
449 return self.bbox
450
451 def LatLongBoundingBox(self):
452 bbox = self.BoundingBox()
453 if bbox is None:
454 return None
455
456 if self.projection is not None:
457 bbox = self.projection.InverseBBox(bbox)
458
459 return bbox
460
461 def Type(self):
462 return "Image"
463
464 def GetImageFilename(self):
465 return self.filename
466
467 def MaskType(self):
468 """Return True if the mask should be used when rendering the layer."""
469 return self.mask_type
470
471 def SetMaskType(self, type):
472 """Set the type of mask to use.
473
474 type can be one of MASK_NONE, MASK_BIT, MASK_ALPHA
475
476 If the state changes, a LAYER_CHANGED message is sent.
477 """
478 if type not in (self.MASK_NONE, self.MASK_BIT, self.MASK_ALPHA):
479 raise ValueError("type is invalid")
480
481 if type != self.mask_type:
482 self.mask_type = type
483 self.changed(LAYER_CHANGED, self)
484
485 def Opacity(self):
486 """Return the level of opacity used in alpha blending.
487 """
488 return self.opacity
489
490 def SetOpacity(self, op):
491 """Set the level of alpha opacity.
492
493 0 <= op <= 1.
494
495 The layer is fully opaque when op = 1.
496 """
497 if not (0 <= op <= 1):
498 raise ValueError("op out of range")
499
500 if op != self.opacity:
501 self.opacity = op
502 self.changed(LAYER_CHANGED, self)
503
504 def ImageInfo(self):
505 return self.image_info
506
507 def TreeInfo(self):
508 items = []
509
510 items.append(_("Filename: %s") % self.GetImageFilename())
511
512 if self.Visible():
513 items.append(_("Shown"))
514 else:
515 items.append(_("Hidden"))
516
517 bbox = self.LatLongBoundingBox()
518 if bbox is not None:
519 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
520 else:
521 items.append(_("Extent (lat-lon):"))
522
523 if self.projection and len(self.projection.params) > 0:
524 items.append((_("Projection"),
525 [str(param) for param in self.projection.params]))
526
527 return (_("Layer '%s'") % self.Title(), items)
528

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26