/[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 1599 - (show annotations)
Mon Aug 18 12:45:28 2003 UTC (21 years, 6 months ago) by bh
File MIME type: text/x-python
File size: 14933 byte(s)
Fix some bugs in Thuban and the test suite that were uncovered by
changes introduced in Python 2.3:

* Thuban/Model/table.py (DBFTable.__init__): Make sure the
filename is an absolute name

* Thuban/Model/layer.py (RasterLayer.__init__): Make sure the
filename is an absolute name

* test/test_save.py (SaveSessionTest.testRasterLayer): Use a
unique filename to save to and use the correct relative filename
in the expected output.
(SaveSessionTest.test_dbf_table): Use the correct relative
filename in the expected output.

* test/test_layer.py (TestLayer.test_raster_layer): Update the
test to check whether the filename is absolute.

1 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jonathan Coles <[email protected]>
5 #
6 # This program is free software under the GPL (>=v2)
7 # Read the file COPYING coming with Thuban for details.
8
9 __version__ = "$Revision$"
10
11 import os
12 import warnings
13
14 from Thuban import _
15
16 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
18
19 import classification
20
21 from color import Transparent, Black
22 from base import TitledObject, Modifiable
23 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
24
25 import resource
26
27
28 shapetype_names = {SHAPETYPE_POINT: "Point",
29 SHAPETYPE_ARC: "Arc",
30 SHAPETYPE_POLYGON: "Polygon"}
31
32 class BaseLayer(TitledObject, Modifiable):
33
34 """Base class for the layers."""
35
36 def __init__(self, title, visible = True, projection = None):
37 """Initialize the layer.
38
39 title -- the title
40 visible -- boolean. If true the layer is visible.
41 """
42 TitledObject.__init__(self, title)
43 Modifiable.__init__(self)
44 self.visible = visible
45 self.projection = projection
46
47 def Visible(self):
48 """Return true if layer is visible"""
49 return self.visible
50
51 def SetVisible(self, visible):
52 """Set the layer's visibility."""
53 self.visible = visible
54 self.issue(LAYER_VISIBILITY_CHANGED, self)
55
56 def HasClassification(self):
57 """Determine if this layer support classifications."""
58 return False
59
60 def HasShapes(self):
61 """Determine if this layer supports shapes."""
62 return False
63
64 def GetProjection(self):
65 """Return the layer's projection."""
66 return self.projection
67
68 def SetProjection(self, projection):
69 """Set the layer's projection"""
70 self.projection = projection
71 self.changed(LAYER_PROJECTION_CHANGED, self)
72
73 class Layer(BaseLayer):
74
75 """Represent the information of one geodata file (currently a shapefile)
76
77 All children of the layer have the same type.
78
79 A layer has fill and stroke colors. Colors should be instances of
80 Color. They can also be Transparent, indicating no fill or no stroke.
81
82 The layer objects send the following events, all of which have the
83 layer object as parameter:
84
85 TITLE_CHANGED -- The title has changed.
86 LAYER_PROJECTION_CHANGED -- the projection has changed.
87 """
88
89 def __init__(self, title, data, projection = None,
90 fill = Transparent,
91 stroke = Black,
92 lineWidth = 1,
93 visible = True):
94 """Initialize the layer.
95
96 title -- the title
97 data -- datastore object for the shape data shown by the layer
98 projection -- the projection object. Its Inverse method is
99 assumed to map the layer's coordinates to lat/long
100 coordinates
101 fill -- the fill color or Transparent if the shapes are
102 not filled
103 stroke -- the stroke color or Transparent if the shapes
104 are not stroked
105 visible -- boolean. If true the layer is visible.
106
107 colors are expected to be instances of Color class
108 """
109 BaseLayer.__init__(self, title,
110 visible = visible,
111 projection = projection)
112
113 self.__classification = None
114 self.store = None
115
116 self.SetShapeStore(data)
117
118 self.classification_column = None
119 self.SetClassificationColumn(None)
120 self.SetClassification(None)
121
122 self.__classification.SetDefaultLineColor(stroke)
123 self.__classification.SetDefaultLineWidth(lineWidth)
124 self.__classification.SetDefaultFill(fill)
125
126 self.UnsetModified()
127
128 def __getattr__(self, attr):
129 """Access to some attributes for backwards compatibility
130
131 The attributes implemented here are now held by the shapestore
132 if at all. For backwards compatibility pretend that they are
133 still there but issue a DeprecationWarning when they are
134 accessed.
135 """
136 if attr in ("table", "shapetable"):
137 value = self.store.Table()
138 elif attr == "shapefile":
139 value = self.store.Shapefile()
140 elif attr == "filename":
141 value = self.store.FileName()
142 else:
143 raise AttributeError, attr
144 warnings.warn("The Layer attribute %r is deprecated."
145 " It's value can be accessed through the shapestore"
146 % attr, DeprecationWarning, stacklevel = 2)
147 return value
148
149 def SetShapeStore(self, store):
150 # Set the classification to None if there is a classification
151 # and the new shapestore doesn't have a table with a suitable
152 # column, i.e one with the same name and type as before
153 # FIXME: Maybe we should keep it the same if the type is
154 # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
155 if self.__classification is not None:
156 columnname = self.classification_column
157 columntype = self.GetFieldType(columnname)
158 table = store.Table()
159 if (columnname is not None
160 and (not table.HasColumn(columnname)
161 or table.Column(columnname).type != columntype)):
162 self.SetClassification(None)
163
164 self.store = store
165
166 self.changed(LAYER_SHAPESTORE_REPLACED, self)
167
168 def ShapeStore(self):
169 return self.store
170
171 def Destroy(self):
172 BaseLayer.Destroy(self)
173 if self.__classification is not None:
174 self.__classification.Unsubscribe(CLASS_CHANGED,
175 self._classification_changed)
176
177 def BoundingBox(self):
178 """Return the layer's bounding box in the intrinsic coordinate system.
179
180 If the layer has no shapes, return None.
181 """
182 return self.store.BoundingBox()
183
184 def LatLongBoundingBox(self):
185 """Return the layer's bounding box in lat/long coordinates.
186
187 Return None, if the layer doesn't contain any shapes.
188 """
189 bbox = self.BoundingBox()
190 if bbox is not None:
191 llx, lly, urx, ury = bbox
192 if self.projection is not None:
193 llx, lly = self.projection.Inverse(llx, lly)
194 urx, ury = self.projection.Inverse(urx, ury)
195 return llx, lly, urx, ury
196 else:
197 return None
198
199 def ShapesBoundingBox(self, shapes):
200 """Return a bounding box in lat/long coordinates for the given
201 list of shape ids.
202
203 If shapes is None or empty, return None.
204 """
205
206 if shapes is None or len(shapes) == 0: return None
207
208 llx = []
209 lly = []
210 urx = []
211 ury = []
212
213 if self.projection is not None:
214 inverse = lambda x, y: self.projection.Inverse(x, y)
215 else:
216 inverse = lambda x, y: (x, y)
217
218 for id in shapes:
219 left, bottom, right, top = self.Shape(id).compute_bbox()
220
221 left, bottom = inverse(left, bottom)
222 right, top = inverse(right, top)
223
224 llx.append(left)
225 lly.append(bottom)
226 urx.append(right)
227 ury.append(top)
228
229 return (min(llx), min(lly), max(urx), max(ury))
230
231 def GetFieldType(self, fieldName):
232 if self.store:
233 table = self.store.Table()
234 if table.HasColumn(fieldName):
235 return table.Column(fieldName).type
236 return None
237
238 def HasShapes(self):
239 return True
240
241 def NumShapes(self):
242 """Return the number of shapes in the layer"""
243 return self.store.NumShapes()
244
245 def ShapeType(self):
246 """Return the type of the shapes in the layer.
247
248 The return value is one of the SHAPETYPE_* constants defined in
249 Thuban.Model.data.
250 """
251 return self.store.ShapeType()
252
253 def Shape(self, index):
254 """Return the shape with index index"""
255 return self.store.Shape(index)
256
257 def ShapesInRegion(self, bbox):
258 """Return an iterable over the shapes that overlap the bounding box.
259
260 The bbox parameter should be the bounding box as a tuple in the
261 form (minx, miny, maxx, maxy) in unprojected coordinates.
262 """
263 if self.projection is not None:
264 left, bottom, right, top = bbox
265 xs = []; ys = []
266 for x, y in [(left, bottom), (left, top), (right, top),
267 (right, bottom)]:
268 x, y = self.projection.Forward(x, y)
269 xs.append(x)
270 ys.append(y)
271 bbox = (min(xs), min(ys), max(xs), max(ys))
272
273 return self.store.ShapesInRegion(bbox)
274
275 def GetClassificationColumn(self):
276 return self.classification_column
277
278 def SetClassificationColumn(self, column):
279 """Set the column to classifiy on, or None. If column is not None
280 and the column does not exist in the table, raise a ValueError.
281 """
282 if column:
283 columnType = self.GetFieldType(column)
284 if columnType is None:
285 raise ValueError()
286 changed = self.classification_column != column
287 self.classification_column = column
288 if changed:
289 self.changed(LAYER_CHANGED, self)
290
291 def HasClassification(self):
292 return True
293
294 def GetClassification(self):
295 return self.__classification
296
297 def SetClassification(self, clazz):
298 """Set the classification used by this layer to 'clazz'
299
300 If 'clazz' is None a default classification is created.
301
302 This issues a LAYER_CHANGED event.
303 """
304
305 if self.__classification is not None:
306 self.__classification.Unsubscribe(CLASS_CHANGED,
307 self._classification_changed)
308
309 if clazz is None:
310 clazz = classification.Classification()
311
312 self.__classification = clazz
313 self.__classification.Subscribe(CLASS_CHANGED,
314 self._classification_changed)
315
316 self._classification_changed()
317
318 def _classification_changed(self):
319 """Called from the classification object when it has changed."""
320 self.changed(LAYER_CHANGED, self)
321
322 def TreeInfo(self):
323 items = []
324
325 items.append(_("Filename: %s") % self.ShapeStore().FileName())
326
327 if self.Visible():
328 items.append(_("Shown"))
329 else:
330 items.append(_("Hidden"))
331 items.append(_("Shapes: %d") % self.NumShapes())
332
333 bbox = self.LatLongBoundingBox()
334 if bbox is not None:
335 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
336 else:
337 items.append(_("Extent (lat-lon):"))
338 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
339
340 if self.projection and len(self.projection.params) > 0:
341 items.append((_("Projection"),
342 [str(param) for param in self.projection.params]))
343
344 items.append(self.__classification)
345
346 return (_("Layer '%s'") % self.Title(), items)
347
348
349 if resource.has_gdal_support():
350 import gdal
351 from gdalconst import GA_ReadOnly
352
353 class RasterLayer(BaseLayer):
354
355 def __init__(self, title, filename, projection = None, visible = True):
356 """Initialize the Raster Layer.
357
358 title -- title for the layer.
359
360 filename -- file name of the source image.
361
362 projection -- Projection object describing the projection which
363 the source image is in.
364
365 visible -- True is the layer should initially be visible.
366
367 Throws IOError if the filename is invalid or points to a file that
368 is not in a format GDAL can use.
369 """
370
371 BaseLayer.__init__(self, title, visible = visible)
372
373 self.projection = projection
374 self.filename = os.path.abspath(filename)
375
376 self.bbox = -1
377
378 if resource.has_gdal_support():
379 #
380 # temporarily open the file so that GDAL can test if it's valid.
381 #
382 dataset = gdal.Open(self.filename, GA_ReadOnly)
383
384 if dataset is None:
385 raise IOError()
386
387 self.UnsetModified()
388
389 def BoundingBox(self):
390 """Return the layer's bounding box in the intrinsic coordinate system.
391
392 If the there is no support for images, or the file cannot
393 be read, or there is no geographics information available, return None.
394 """
395 if not resource.has_gdal_support():
396 return None
397
398 if self.bbox == -1:
399 dataset = gdal.Open(self.filename, GA_ReadOnly)
400 if dataset is None:
401 self.bbox = None
402 else:
403 geotransform = dataset.GetGeoTransform()
404 if geotransform is None:
405 return None
406
407 x = 0
408 y = dataset.RasterYSize
409 left = geotransform[0] + \
410 geotransform[1] * x + \
411 geotransform[2] * y
412
413 bottom = geotransform[3] + \
414 geotransform[4] * x + \
415 geotransform[5] * y
416
417 x = dataset.RasterXSize
418 y = 0
419 right = geotransform[0] + \
420 geotransform[1] * x + \
421 geotransform[2] * y
422
423 top = geotransform[3] + \
424 geotransform[4] * x + \
425 geotransform[5] * y
426
427 self.bbox = (left, bottom, right, top)
428
429 return self.bbox
430
431 def LatLongBoundingBox(self):
432 bbox = self.BoundingBox()
433 if bbox is None:
434 return None
435
436 llx, lly, urx, ury = bbox
437 if self.projection is not None:
438 llx, lly = self.projection.Inverse(llx, lly)
439 urx, ury = self.projection.Inverse(urx, ury)
440
441 return llx, lly, urx, ury
442
443 def GetImageFilename(self):
444 return self.filename
445
446 def TreeInfo(self):
447 items = []
448
449 items.append(_("Filename: %s") % self.GetImageFilename())
450
451 if self.Visible():
452 items.append(_("Shown"))
453 else:
454 items.append(_("Hidden"))
455
456 bbox = self.LatLongBoundingBox()
457 if bbox is not None:
458 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
459 else:
460 items.append(_("Extent (lat-lon):"))
461
462 if self.projection and len(self.projection.params) > 0:
463 items.append((_("Projection"),
464 [str(param) for param in self.projection.params]))
465
466 return (_("Layer '%s'") % self.Title(), items)
467

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26