/[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 1983 - (hide annotations)
Thu Nov 27 15:57:23 2003 UTC (21 years, 3 months ago) by bh
File MIME type: text/x-python
File size: 13303 byte(s)
* Thuban/Model/layer.py (Layer.LatLongBoundingBox)
(Layer.ShapesBoundingBox, RasterLayer.LatLongBoundingBox): Use the
new InverseBBox method to determine the unprojected bounding box
(Layer.ShapesInRegion): Use the ForwardBBox method to project the
bbox.

* test/test_layer.py (TestLayer.test_point_layer_with_projection):
Removed.
(TestLayer.test_arc_layer_with_projection): New. This test is
better able to test whether bounding boxes are projected correctly
than test_point_layer_with_projection

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 723 def SetShapeStore(self, store):
129 jonathan 1427 # Set the classification to None if there is a classification
130     # and the new shapestore doesn't have a table with a suitable
131     # column, i.e one with the same name and type as before
132     # FIXME: Maybe we should keep it the same if the type is
133     # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
134     if self.__classification is not None:
135 bh 1452 columnname = self.classification_column
136     columntype = self.GetFieldType(columnname)
137 jonathan 1427 table = store.Table()
138 bh 1452 if (columnname is not None
139     and (not table.HasColumn(columnname)
140     or table.Column(columnname).type != columntype)):
141 jonathan 1427 self.SetClassification(None)
142    
143 bh 723 self.store = store
144 bh 179
145 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
146 bh 723
147     def ShapeStore(self):
148     return self.store
149    
150 bh 258 def Destroy(self):
151 bh 260 BaseLayer.Destroy(self)
152 jonathan 1427 if self.__classification is not None:
153 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
154     self._classification_changed)
155 bh 258
156 bh 6 def BoundingBox(self):
157 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
158    
159     If the layer has no shapes, return None.
160     """
161 bh 1535 return self.store.BoundingBox()
162 bh 6
163     def LatLongBoundingBox(self):
164 bh 179 """Return the layer's bounding box in lat/long coordinates.
165 bh 6
166 bh 179 Return None, if the layer doesn't contain any shapes.
167     """
168     bbox = self.BoundingBox()
169 bh 1983 if bbox is not None and self.projection is not None:
170     bbox = self.projection.InverseBBox(bbox)
171     return bbox
172 bh 179
173 jonathan 828 def ShapesBoundingBox(self, shapes):
174     """Return a bounding box in lat/long coordinates for the given
175     list of shape ids.
176    
177     If shapes is None or empty, return None.
178     """
179    
180     if shapes is None or len(shapes) == 0: return None
181    
182 bh 1983 xs = []
183     ys = []
184 jonathan 828
185     for id in shapes:
186 bh 1983 bbox = self.Shape(id).compute_bbox()
187     if self.projection is not None:
188     bbox = self.projection.InverseBBox(bbox)
189     left, bottom, right, top = bbox
190     xs.append(left); xs.append(right)
191     ys.append(bottom); ys.append(top)
192 jonathan 828
193 bh 1983 return (min(xs), min(ys), max(xs), max(ys))
194 jonathan 828
195    
196 jonathan 464 def GetFieldType(self, fieldName):
197 jonathan 1427 if self.store:
198     table = self.store.Table()
199     if table.HasColumn(fieldName):
200     return table.Column(fieldName).type
201 bh 839 return None
202 jonathan 464
203 jonathan 1273 def HasShapes(self):
204     return True
205    
206 bh 6 def NumShapes(self):
207     """Return the number of shapes in the layer"""
208 bh 1535 return self.store.NumShapes()
209 bh 6
210     def ShapeType(self):
211     """Return the type of the shapes in the layer.
212 bh 1535
213     The return value is one of the SHAPETYPE_* constants defined in
214     Thuban.Model.data.
215 bh 6 """
216 bh 1535 return self.store.ShapeType()
217 bh 6
218     def Shape(self, index):
219     """Return the shape with index index"""
220 bh 1535 return self.store.Shape(index)
221 jonathan 364
222 bh 1587 def ShapesInRegion(self, bbox):
223 bh 1593 """Return an iterable over the shapes that overlap the bounding box.
224 bh 143
225 bh 1593 The bbox parameter should be the bounding box as a tuple in the
226     form (minx, miny, maxx, maxy) in unprojected coordinates.
227 bh 143 """
228 jonathan 794 if self.projection is not None:
229 bh 1983 bbox = self.projection.ForwardBBox(bbox)
230 bh 1587 return self.store.ShapesInRegion(bbox)
231 bh 143
232 bh 1452 def GetClassificationColumn(self):
233     return self.classification_column
234 jonathan 1427
235 bh 1452 def SetClassificationColumn(self, column):
236     """Set the column to classifiy on, or None. If column is not None
237     and the column does not exist in the table, raise a ValueError.
238 jonathan 1427 """
239 bh 1452 if column:
240     columnType = self.GetFieldType(column)
241     if columnType is None:
242 jonathan 1427 raise ValueError()
243 bh 1452 changed = self.classification_column != column
244     self.classification_column = column
245     if changed:
246     self.changed(LAYER_CHANGED, self)
247 jonathan 1427
248 jonathan 929 def HasClassification(self):
249     return True
250 jonathan 725
251 jonathan 412 def GetClassification(self):
252     return self.__classification
253    
254     def SetClassification(self, clazz):
255 jonathan 1338 """Set the classification used by this layer to 'clazz'
256 jonathan 481
257 jonathan 1338 If 'clazz' is None a default classification is created.
258    
259 jonathan 1427 This issues a LAYER_CHANGED event.
260 jonathan 492 """
261 jonathan 481
262 jonathan 1427 if self.__classification is not None:
263 bh 1452 self.__classification.Unsubscribe(CLASS_CHANGED,
264     self._classification_changed)
265 jonathan 529
266 jonathan 1338 if clazz is None:
267     clazz = classification.Classification()
268 jonathan 529
269 jonathan 1427 self.__classification = clazz
270 bh 1452 self.__classification.Subscribe(CLASS_CHANGED,
271     self._classification_changed)
272 jonathan 1338
273 bh 1452 self._classification_changed()
274 jonathan 492
275 bh 1452 def _classification_changed(self):
276 jonathan 492 """Called from the classification object when it has changed."""
277 jonathan 558 self.changed(LAYER_CHANGED, self)
278 jonathan 492
279 bh 217 def TreeInfo(self):
280     items = []
281    
282 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
283 jan 1012
284 bh 217 if self.Visible():
285 jan 374 items.append(_("Shown"))
286 bh 217 else:
287 jan 374 items.append(_("Hidden"))
288     items.append(_("Shapes: %d") % self.NumShapes())
289 bh 217
290     bbox = self.LatLongBoundingBox()
291     if bbox is not None:
292 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
293 bh 217 else:
294 jan 374 items.append(_("Extent (lat-lon):"))
295     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
296 bh 217
297 jonathan 736 if self.projection and len(self.projection.params) > 0:
298     items.append((_("Projection"),
299     [str(param) for param in self.projection.params]))
300    
301 jonathan 412 items.append(self.__classification)
302 bh 217
303 jan 374 return (_("Layer '%s'") % self.Title(), items)
304 jonathan 382
305 jonathan 736
306 jonathan 1158 if resource.has_gdal_support():
307     import gdal
308     from gdalconst import GA_ReadOnly
309    
310 jonathan 929 class RasterLayer(BaseLayer):
311    
312     def __init__(self, title, filename, projection = None, visible = True):
313     """Initialize the Raster Layer.
314    
315     title -- title for the layer.
316    
317     filename -- file name of the source image.
318    
319     projection -- Projection object describing the projection which
320     the source image is in.
321    
322     visible -- True is the layer should initially be visible.
323 jonathan 961
324     Throws IOError if the filename is invalid or points to a file that
325     is not in a format GDAL can use.
326 jonathan 929 """
327    
328     BaseLayer.__init__(self, title, visible = visible)
329    
330     self.projection = projection
331 bh 1599 self.filename = os.path.abspath(filename)
332 jonathan 929
333     self.bbox = -1
334    
335 jonathan 1158 if resource.has_gdal_support():
336     #
337     # temporarily open the file so that GDAL can test if it's valid.
338     #
339     dataset = gdal.Open(self.filename, GA_ReadOnly)
340 jonathan 961
341 jonathan 1158 if dataset is None:
342     raise IOError()
343 jonathan 961
344 jonathan 929 self.UnsetModified()
345    
346     def BoundingBox(self):
347     """Return the layer's bounding box in the intrinsic coordinate system.
348    
349 jonathan 1338 If the there is no support for images, or the file cannot
350     be read, or there is no geographics information available, return None.
351 jonathan 929 """
352 jonathan 1158 if not resource.has_gdal_support():
353     return None
354    
355 jonathan 929 if self.bbox == -1:
356     dataset = gdal.Open(self.filename, GA_ReadOnly)
357     if dataset is None:
358     self.bbox = None
359     else:
360 jonathan 961 geotransform = dataset.GetGeoTransform()
361     if geotransform is None:
362     return None
363    
364     x = 0
365     y = dataset.RasterYSize
366     left = geotransform[0] + \
367     geotransform[1] * x + \
368     geotransform[2] * y
369    
370     bottom = geotransform[3] + \
371     geotransform[4] * x + \
372     geotransform[5] * y
373    
374     x = dataset.RasterXSize
375     y = 0
376     right = geotransform[0] + \
377     geotransform[1] * x + \
378     geotransform[2] * y
379    
380     top = geotransform[3] + \
381     geotransform[4] * x + \
382     geotransform[5] * y
383    
384 jonathan 929 self.bbox = (left, bottom, right, top)
385    
386     return self.bbox
387    
388     def LatLongBoundingBox(self):
389     bbox = self.BoundingBox()
390 jonathan 961 if bbox is None:
391 jonathan 929 return None
392    
393 jonathan 961 if self.projection is not None:
394 bh 1983 bbox = self.projection.InverseBBox(bbox)
395 jonathan 961
396 bh 1983 return bbox
397 jonathan 961
398 jonathan 929 def GetImageFilename(self):
399     return self.filename
400    
401     def TreeInfo(self):
402     items = []
403    
404 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
405    
406 jonathan 929 if self.Visible():
407     items.append(_("Shown"))
408     else:
409     items.append(_("Hidden"))
410    
411     bbox = self.LatLongBoundingBox()
412     if bbox is not None:
413     items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
414     else:
415     items.append(_("Extent (lat-lon):"))
416    
417     if self.projection and len(self.projection.params) > 0:
418     items.append((_("Projection"),
419     [str(param) for param in self.projection.params]))
420    
421     return (_("Layer '%s'") % self.Title(), items)
422    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26