/[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 2339 - (hide annotations)
Fri Aug 20 16:59:21 2004 UTC (20 years, 6 months ago) by silke
File MIME type: text/x-python
File size: 14428 byte(s)
Fix bug in projection handling of layers

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26