/[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 1983 - (show 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 # 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 SetShapeStore(self, store):
129 # 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 columnname = self.classification_column
136 columntype = self.GetFieldType(columnname)
137 table = store.Table()
138 if (columnname is not None
139 and (not table.HasColumn(columnname)
140 or table.Column(columnname).type != columntype)):
141 self.SetClassification(None)
142
143 self.store = store
144
145 self.changed(LAYER_SHAPESTORE_REPLACED, self)
146
147 def ShapeStore(self):
148 return self.store
149
150 def Destroy(self):
151 BaseLayer.Destroy(self)
152 if self.__classification is not None:
153 self.__classification.Unsubscribe(CLASS_CHANGED,
154 self._classification_changed)
155
156 def BoundingBox(self):
157 """Return the layer's bounding box in the intrinsic coordinate system.
158
159 If the layer has no shapes, return None.
160 """
161 return self.store.BoundingBox()
162
163 def LatLongBoundingBox(self):
164 """Return the layer's bounding box in lat/long coordinates.
165
166 Return None, if the layer doesn't contain any shapes.
167 """
168 bbox = self.BoundingBox()
169 if bbox is not None and self.projection is not None:
170 bbox = self.projection.InverseBBox(bbox)
171 return bbox
172
173 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 xs = []
183 ys = []
184
185 for id in shapes:
186 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
193 return (min(xs), min(ys), max(xs), max(ys))
194
195
196 def GetFieldType(self, fieldName):
197 if self.store:
198 table = self.store.Table()
199 if table.HasColumn(fieldName):
200 return table.Column(fieldName).type
201 return None
202
203 def HasShapes(self):
204 return True
205
206 def NumShapes(self):
207 """Return the number of shapes in the layer"""
208 return self.store.NumShapes()
209
210 def ShapeType(self):
211 """Return the type of the shapes in the layer.
212
213 The return value is one of the SHAPETYPE_* constants defined in
214 Thuban.Model.data.
215 """
216 return self.store.ShapeType()
217
218 def Shape(self, index):
219 """Return the shape with index index"""
220 return self.store.Shape(index)
221
222 def ShapesInRegion(self, bbox):
223 """Return an iterable over the shapes that overlap the bounding box.
224
225 The bbox parameter should be the bounding box as a tuple in the
226 form (minx, miny, maxx, maxy) in unprojected coordinates.
227 """
228 if self.projection is not None:
229 bbox = self.projection.ForwardBBox(bbox)
230 return self.store.ShapesInRegion(bbox)
231
232 def GetClassificationColumn(self):
233 return self.classification_column
234
235 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 """
239 if column:
240 columnType = self.GetFieldType(column)
241 if columnType is None:
242 raise ValueError()
243 changed = self.classification_column != column
244 self.classification_column = column
245 if changed:
246 self.changed(LAYER_CHANGED, self)
247
248 def HasClassification(self):
249 return True
250
251 def GetClassification(self):
252 return self.__classification
253
254 def SetClassification(self, clazz):
255 """Set the classification used by this layer to 'clazz'
256
257 If 'clazz' is None a default classification is created.
258
259 This issues a LAYER_CHANGED event.
260 """
261
262 if self.__classification is not None:
263 self.__classification.Unsubscribe(CLASS_CHANGED,
264 self._classification_changed)
265
266 if clazz is None:
267 clazz = classification.Classification()
268
269 self.__classification = clazz
270 self.__classification.Subscribe(CLASS_CHANGED,
271 self._classification_changed)
272
273 self._classification_changed()
274
275 def _classification_changed(self):
276 """Called from the classification object when it has changed."""
277 self.changed(LAYER_CHANGED, self)
278
279 def TreeInfo(self):
280 items = []
281
282 items.append(_("Filename: %s") % self.ShapeStore().FileName())
283
284 if self.Visible():
285 items.append(_("Shown"))
286 else:
287 items.append(_("Hidden"))
288 items.append(_("Shapes: %d") % self.NumShapes())
289
290 bbox = self.LatLongBoundingBox()
291 if bbox is not None:
292 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
293 else:
294 items.append(_("Extent (lat-lon):"))
295 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
296
297 if self.projection and len(self.projection.params) > 0:
298 items.append((_("Projection"),
299 [str(param) for param in self.projection.params]))
300
301 items.append(self.__classification)
302
303 return (_("Layer '%s'") % self.Title(), items)
304
305
306 if resource.has_gdal_support():
307 import gdal
308 from gdalconst import GA_ReadOnly
309
310 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
324 Throws IOError if the filename is invalid or points to a file that
325 is not in a format GDAL can use.
326 """
327
328 BaseLayer.__init__(self, title, visible = visible)
329
330 self.projection = projection
331 self.filename = os.path.abspath(filename)
332
333 self.bbox = -1
334
335 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
341 if dataset is None:
342 raise IOError()
343
344 self.UnsetModified()
345
346 def BoundingBox(self):
347 """Return the layer's bounding box in the intrinsic coordinate system.
348
349 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 """
352 if not resource.has_gdal_support():
353 return None
354
355 if self.bbox == -1:
356 dataset = gdal.Open(self.filename, GA_ReadOnly)
357 if dataset is None:
358 self.bbox = None
359 else:
360 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 self.bbox = (left, bottom, right, top)
385
386 return self.bbox
387
388 def LatLongBoundingBox(self):
389 bbox = self.BoundingBox()
390 if bbox is None:
391 return None
392
393 if self.projection is not None:
394 bbox = self.projection.InverseBBox(bbox)
395
396 return bbox
397
398 def GetImageFilename(self):
399 return self.filename
400
401 def TreeInfo(self):
402 items = []
403
404 items.append(_("Filename: %s") % self.GetImageFilename())
405
406 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