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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1599 - (hide 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 bh 701 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4 jonathan 412 # Jonathan Coles <[email protected]>
5 bh 6 #
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 bh 1599 import os
12 bh 1219 import warnings
13 bh 171
14 jan 374 from Thuban import _
15 bh 6
16 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17 jonathan 1427 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
18 bh 6
19 jonathan 437 import classification
20 bh 6
21 jonathan 1338 from color import Transparent, Black
22 bh 6 from base import TitledObject, Modifiable
23 bh 1558 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
24 bh 6
25 jonathan 1158 import resource
26 bh 723
27 jonathan 1158
28 bh 6 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 jonathan 929 def __init__(self, title, visible = True, projection = None):
37 bh 6 """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 jonathan 929 self.projection = projection
46 bh 6
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 bh 276
56 jonathan 929 def HasClassification(self):
57     """Determine if this layer support classifications."""
58     return False
59 bh 276
60 jonathan 1273 def HasShapes(self):
61     """Determine if this layer supports shapes."""
62     return False
63    
64 jonathan 929 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 bh 6 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 jonathan 1338 Color. They can also be Transparent, indicating no fill or no stroke.
81 bh 6
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 bh 723 def __init__(self, title, data, projection = None,
90 jonathan 1338 fill = Transparent,
91     stroke = Black,
92 jonathan 464 lineWidth = 1,
93 jonathan 771 visible = True):
94 bh 6 """Initialize the layer.
95    
96     title -- the title
97 bh 723 data -- datastore object for the shape data shown by the layer
98 bh 6 projection -- the projection object. Its Inverse method is
99     assumed to map the layer's coordinates to lat/long
100     coordinates
101 jonathan 1338 fill -- the fill color or Transparent if the shapes are
102 jonathan 610 not filled
103 jonathan 1338 stroke -- the stroke color or Transparent if the shapes
104 jonathan 610 are not stroked
105 bh 6 visible -- boolean. If true the layer is visible.
106    
107     colors are expected to be instances of Color class
108     """
109 jonathan 929 BaseLayer.__init__(self, title,
110     visible = visible,
111     projection = projection)
112 bh 276
113 bh 723 self.__classification = None
114 jonathan 1427 self.store = None
115 jonathan 481
116 bh 723 self.SetShapeStore(data)
117 jonathan 492
118 bh 1452 self.classification_column = None
119     self.SetClassificationColumn(None)
120 jonathan 492 self.SetClassification(None)
121    
122 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
123     self.__classification.SetDefaultLineWidth(lineWidth)
124 jonathan 412 self.__classification.SetDefaultFill(fill)
125 jonathan 364
126 jonathan 389 self.UnsetModified()
127    
128 bh 1219 def __getattr__(self, attr):
129     """Access to some attributes for backwards compatibility
130 bh 6
131 bh 1219 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 bh 723 def SetShapeStore(self, store):
150 jonathan 1427 # 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 bh 1452 columnname = self.classification_column
157     columntype = self.GetFieldType(columnname)
158 jonathan 1427 table = store.Table()
159 bh 1452 if (columnname is not None
160     and (not table.HasColumn(columnname)
161     or table.Column(columnname).type != columntype)):
162 jonathan 1427 self.SetClassification(None)
163    
164 bh 723 self.store = store
165 bh 179
166 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
167 bh 723
168     def ShapeStore(self):
169     return self.store
170    
171 bh 258 def Destroy(self):
172 bh 260 BaseLayer.Destroy(self)
173 jonathan 1427 if self.__classification is not None:
174 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
175     self._classification_changed)
176 bh 258
177 bh 6 def BoundingBox(self):
178 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
179    
180     If the layer has no shapes, return None.
181     """
182 bh 1535 return self.store.BoundingBox()
183 bh 6
184     def LatLongBoundingBox(self):
185 bh 179 """Return the layer's bounding box in lat/long coordinates.
186 bh 6
187 bh 179 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 jonathan 828 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 jonathan 464 def GetFieldType(self, fieldName):
232 jonathan 1427 if self.store:
233     table = self.store.Table()
234     if table.HasColumn(fieldName):
235     return table.Column(fieldName).type
236 bh 839 return None
237 jonathan 464
238 jonathan 1273 def HasShapes(self):
239     return True
240    
241 bh 6 def NumShapes(self):
242     """Return the number of shapes in the layer"""
243 bh 1535 return self.store.NumShapes()
244 bh 6
245     def ShapeType(self):
246     """Return the type of the shapes in the layer.
247 bh 1535
248     The return value is one of the SHAPETYPE_* constants defined in
249     Thuban.Model.data.
250 bh 6 """
251 bh 1535 return self.store.ShapeType()
252 bh 6
253     def Shape(self, index):
254     """Return the shape with index index"""
255 bh 1535 return self.store.Shape(index)
256 jonathan 364
257 bh 1587 def ShapesInRegion(self, bbox):
258 bh 1593 """Return an iterable over the shapes that overlap the bounding box.
259 bh 143
260 bh 1593 The bbox parameter should be the bounding box as a tuple in the
261     form (minx, miny, maxx, maxy) in unprojected coordinates.
262 bh 143 """
263 jonathan 794 if self.projection is not None:
264 bh 1587 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 jonathan 794
273 bh 1587 return self.store.ShapesInRegion(bbox)
274 bh 143
275 bh 1452 def GetClassificationColumn(self):
276     return self.classification_column
277 jonathan 1427
278 bh 1452 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 jonathan 1427 """
282 bh 1452 if column:
283     columnType = self.GetFieldType(column)
284     if columnType is None:
285 jonathan 1427 raise ValueError()
286 bh 1452 changed = self.classification_column != column
287     self.classification_column = column
288     if changed:
289     self.changed(LAYER_CHANGED, self)
290 jonathan 1427
291 jonathan 929 def HasClassification(self):
292     return True
293 jonathan 725
294 jonathan 412 def GetClassification(self):
295     return self.__classification
296    
297     def SetClassification(self, clazz):
298 jonathan 1338 """Set the classification used by this layer to 'clazz'
299 jonathan 481
300 jonathan 1338 If 'clazz' is None a default classification is created.
301    
302 jonathan 1427 This issues a LAYER_CHANGED event.
303 jonathan 492 """
304 jonathan 481
305 jonathan 1427 if self.__classification is not None:
306 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
307     self._classification_changed)
308 jonathan 529
309 jonathan 1338 if clazz is None:
310     clazz = classification.Classification()
311 jonathan 529
312 jonathan 1427 self.__classification = clazz
313 bh 1452 self.__classification.Subscribe(CLASS_CHANGED,
314     self._classification_changed)
315 jonathan 1338
316 bh 1452 self._classification_changed()
317 jonathan 492
318 bh 1452 def _classification_changed(self):
319 jonathan 492 """Called from the classification object when it has changed."""
320 jonathan 558 self.changed(LAYER_CHANGED, self)
321 jonathan 492
322 bh 217 def TreeInfo(self):
323     items = []
324    
325 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
326 jan 1012
327 bh 217 if self.Visible():
328 jan 374 items.append(_("Shown"))
329 bh 217 else:
330 jan 374 items.append(_("Hidden"))
331     items.append(_("Shapes: %d") % self.NumShapes())
332 bh 217
333     bbox = self.LatLongBoundingBox()
334     if bbox is not None:
335 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
336 bh 217 else:
337 jan 374 items.append(_("Extent (lat-lon):"))
338     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
339 bh 217
340 jonathan 736 if self.projection and len(self.projection.params) > 0:
341     items.append((_("Projection"),
342     [str(param) for param in self.projection.params]))
343    
344 jonathan 412 items.append(self.__classification)
345 bh 217
346 jan 374 return (_("Layer '%s'") % self.Title(), items)
347 jonathan 382
348 jonathan 736
349 jonathan 1158 if resource.has_gdal_support():
350     import gdal
351     from gdalconst import GA_ReadOnly
352    
353 jonathan 929 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 jonathan 961
367     Throws IOError if the filename is invalid or points to a file that
368     is not in a format GDAL can use.
369 jonathan 929 """
370    
371     BaseLayer.__init__(self, title, visible = visible)
372    
373     self.projection = projection
374 bh 1599 self.filename = os.path.abspath(filename)
375 jonathan 929
376     self.bbox = -1
377    
378 jonathan 1158 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 jonathan 961
384 jonathan 1158 if dataset is None:
385     raise IOError()
386 jonathan 961
387 jonathan 929 self.UnsetModified()
388    
389     def BoundingBox(self):
390     """Return the layer's bounding box in the intrinsic coordinate system.
391    
392 jonathan 1338 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 jonathan 929 """
395 jonathan 1158 if not resource.has_gdal_support():
396     return None
397    
398 jonathan 929 if self.bbox == -1:
399     dataset = gdal.Open(self.filename, GA_ReadOnly)
400     if dataset is None:
401     self.bbox = None
402     else:
403 jonathan 961 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 jonathan 929 self.bbox = (left, bottom, right, top)
428    
429     return self.bbox
430    
431     def LatLongBoundingBox(self):
432     bbox = self.BoundingBox()
433 jonathan 961 if bbox is None:
434 jonathan 929 return None
435    
436 jonathan 961 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 jonathan 929 def GetImageFilename(self):
444     return self.filename
445    
446     def TreeInfo(self):
447     items = []
448    
449 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
450    
451 jonathan 929 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