/[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 2552 - (show annotations)
Fri Jan 28 15:54:00 2005 UTC (20 years, 1 month ago) by jonathan
File MIME type: text/x-python
File size: 15820 byte(s)
Make layer's use_mask flag default to true. Support a bit array describing
the mask to use. Improve error handling in ProjectRasterFile (also addresses
RT #2947).

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 def __init__(self, title, filename, projection = None, visible = True):
348 """Initialize the Raster Layer.
349
350 title -- title for the layer.
351
352 filename -- file name of the source image.
353
354 projection -- Projection object describing the projection which
355 the source image is in.
356
357 visible -- True is the layer should initially be visible.
358
359 Throws IOError if the filename is invalid or points to a file that
360 is not in a format GDAL can use.
361 """
362
363 BaseLayer.__init__(self, title, visible = visible)
364
365 self.projection = projection
366 self.filename = os.path.abspath(filename)
367
368 self.bbox = -1
369
370 self.use_mask = True
371
372 self.image_info = None
373
374 if resource.has_gdal_support():
375 #
376 # temporarily open the file so that GDAL can test if it's valid.
377 #
378 dataset = gdal.Open(self.filename, GA_ReadOnly)
379
380 if dataset is None:
381 raise IOError()
382
383 #
384 # while we have the file, extract some basic information
385 # that we can display later
386 #
387 self.image_info = {}
388
389 self.image_info["nBands"] = dataset.RasterCount
390 self.image_info["Size"] = (dataset.RasterXSize, dataset.RasterYSize)
391 self.image_info["Driver"] = dataset.GetDriver().ShortName
392
393 # store some information about the individual bands
394 # [min_value, max_value]
395 a = self.image_info["BandData"] = []
396
397 for i in range(1, dataset.RasterCount+1):
398 band = dataset.GetRasterBand(i)
399 a.append(band.ComputeRasterMinMax())
400
401 self.UnsetModified()
402
403 def BoundingBox(self):
404 """Return the layer's bounding box in the intrinsic coordinate system.
405
406 If the there is no support for images, or the file cannot
407 be read, or there is no geographics information available, return None.
408 """
409 if not resource.has_gdal_support():
410 return None
411
412 if self.bbox == -1:
413 dataset = gdal.Open(self.filename, GA_ReadOnly)
414 if dataset is None:
415 self.bbox = None
416 else:
417 geotransform = dataset.GetGeoTransform()
418 if geotransform is None:
419 return None
420
421 x = 0
422 y = dataset.RasterYSize
423 left = geotransform[0] + \
424 geotransform[1] * x + \
425 geotransform[2] * y
426
427 bottom = geotransform[3] + \
428 geotransform[4] * x + \
429 geotransform[5] * y
430
431 x = dataset.RasterXSize
432 y = 0
433 right = geotransform[0] + \
434 geotransform[1] * x + \
435 geotransform[2] * y
436
437 top = geotransform[3] + \
438 geotransform[4] * x + \
439 geotransform[5] * y
440
441 self.bbox = (left, bottom, right, top)
442
443 return self.bbox
444
445 def LatLongBoundingBox(self):
446 bbox = self.BoundingBox()
447 if bbox is None:
448 return None
449
450 if self.projection is not None:
451 bbox = self.projection.InverseBBox(bbox)
452
453 return bbox
454
455 def Type(self):
456 return "Image"
457
458 def GetImageFilename(self):
459 return self.filename
460
461 def UseMask(self):
462 """Return True if the mask should be used when rendering the layer."""
463 return self.use_mask
464
465 def SetUseMask(self, use):
466 """Set whether to use a mask when render the image.
467
468 If the state changes, a LAYER_CHANGED message is sent.
469 """
470 if use != self.use_mask:
471 self.use_mask = use
472 self.changed(LAYER_CHANGED, self)
473
474 def ImageInfo(self):
475 return self.image_info
476
477 def TreeInfo(self):
478 items = []
479
480 items.append(_("Filename: %s") % self.GetImageFilename())
481
482 if self.Visible():
483 items.append(_("Shown"))
484 else:
485 items.append(_("Hidden"))
486
487 bbox = self.LatLongBoundingBox()
488 if bbox is not None:
489 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
490 else:
491 items.append(_("Extent (lat-lon):"))
492
493 if self.projection and len(self.projection.params) > 0:
494 items.append((_("Projection"),
495 [str(param) for param in self.projection.params]))
496
497 return (_("Layer '%s'") % self.Title(), items)
498

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26