/[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 2343 - (show annotations)
Mon Sep 20 08:13:32 2004 UTC (20 years, 5 months ago) by bernhard
File MIME type: text/x-python
File size: 14430 byte(s)
Fixed typos in docstrings.

1 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jonathan Coles <[email protected]>
5 # Silke Reimer <[email protected]>
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 import os
13 import warnings
14
15 from Thuban import _
16
17 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
19
20 import classification
21
22 from color import Transparent, Black
23 from base import TitledObject, Modifiable
24 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
25
26 import resource
27
28
29 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 def __init__(self, title, visible = True, projection = None):
38 """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 self.projection = projection
47
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
57 def HasClassification(self):
58 """Determine if this layer supports classifications."""
59 return False
60
61 def HasShapes(self):
62 """Determine if this layer supports shapes."""
63 return False
64
65 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 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 Color. They can also be Transparent, indicating no fill or no stroke.
82
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 def __init__(self, title, data, projection = None,
91 fill = Transparent,
92 stroke = Black,
93 lineWidth = 1,
94 visible = True):
95 """Initialize the layer.
96
97 title -- the title
98 data -- datastore object for the shape data shown by the layer
99 projection -- the projection object. Its Inverse method is
100 assumed to map the layer's coordinates to lat/long
101 coordinates
102 fill -- the fill color or Transparent if the shapes are
103 not filled
104 stroke -- the stroke color or Transparent if the shapes
105 are not stroked
106 visible -- boolean. If true the layer is visible.
107
108 colors are expected to be instances of Color class
109 """
110 BaseLayer.__init__(self, title,
111 visible = visible,
112 projection = projection)
113
114 self.__classification = None
115 self.store = None
116
117 self.SetShapeStore(data)
118
119 self.classification_column = None
120 self.SetClassificationColumn(None)
121 self.SetClassification(None)
122
123 self.__classification.SetDefaultLineColor(stroke)
124 self.__classification.SetDefaultLineWidth(lineWidth)
125 self.__classification.SetDefaultFill(fill)
126
127 self.UnsetModified()
128
129 def SetShapeStore(self, store):
130 # 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 columnname = self.classification_column
137 columntype = self.GetFieldType(columnname)
138 table = store.Table()
139 if (columnname is not None
140 and (not table.HasColumn(columnname)
141 or table.Column(columnname).type != columntype)):
142 self.SetClassification(None)
143
144 self.store = store
145
146 self.changed(LAYER_SHAPESTORE_REPLACED, self)
147
148 def ShapeStore(self):
149 return self.store
150
151 def Destroy(self):
152 BaseLayer.Destroy(self)
153 if self.__classification is not None:
154 self.__classification.Unsubscribe(CLASS_CHANGED,
155 self._classification_changed)
156
157 def BoundingBox(self):
158 """Return the layer's bounding box in the intrinsic coordinate system.
159
160 If the layer has no shapes, return None.
161 """
162 return self.store.BoundingBox()
163
164 def LatLongBoundingBox(self):
165 """Return the layer's bounding box in lat/long coordinates.
166
167 Return None, if the layer doesn't contain any shapes.
168 """
169 bbox = self.BoundingBox()
170 if bbox is not None and self.projection is not None:
171 bbox = self.projection.InverseBBox(bbox)
172 return bbox
173
174 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 xs = []
184 ys = []
185
186 for id in shapes:
187 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
194 return (min(xs), min(ys), max(xs), max(ys))
195
196
197 def GetFieldType(self, fieldName):
198 if self.store:
199 table = self.store.Table()
200 if table.HasColumn(fieldName):
201 return table.Column(fieldName).type
202 return None
203
204 def HasShapes(self):
205 return True
206
207 def NumShapes(self):
208 """Return the number of shapes in the layer"""
209 return self.store.NumShapes()
210
211 def ShapeType(self):
212 """Return the type of the shapes in the layer.
213
214 The return value is one of the SHAPETYPE_* constants defined in
215 Thuban.Model.data.
216 """
217 return self.store.ShapeType()
218
219 def Shape(self, index):
220 """Return the shape with index index"""
221 return self.store.Shape(index)
222
223 def ShapesInRegion(self, bbox):
224 """Return an iterable over the shapes that overlap the bounding box.
225
226 The bbox parameter should be the bounding box as a tuple in the
227 form (minx, miny, maxx, maxy) in unprojected coordinates.
228 """
229 if self.projection is not None:
230 # 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 return self.store.ShapesInRegion(bbox)
236
237 def GetClassificationColumn(self):
238 return self.classification_column
239
240 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 """
244 if column:
245 columnType = self.GetFieldType(column)
246 if columnType is None:
247 raise ValueError()
248 changed = self.classification_column != column
249 self.classification_column = column
250 if changed:
251 self.changed(LAYER_CHANGED, self)
252
253 def HasClassification(self):
254 return True
255
256 def GetClassification(self):
257 return self.__classification
258
259 def SetClassification(self, clazz):
260 """Set the classification used by this layer to 'clazz'
261
262 If 'clazz' is None a default classification is created.
263
264 This issues a LAYER_CHANGED event.
265 """
266
267 if self.__classification is not None:
268 self.__classification.Unsubscribe(CLASS_CHANGED,
269 self._classification_changed)
270
271 if clazz is None:
272 clazz = classification.Classification()
273
274 self.__classification = clazz
275 self.__classification.Subscribe(CLASS_CHANGED,
276 self._classification_changed)
277
278 self._classification_changed()
279
280 def _classification_changed(self):
281 """Called from the classification object when it has changed."""
282 self.changed(LAYER_CHANGED, self)
283
284 def TreeInfo(self):
285 items = []
286
287 items.append(_("Filename: %s") % self.ShapeStore().FileName())
288
289 if self.Visible():
290 items.append(_("Shown"))
291 else:
292 items.append(_("Hidden"))
293 items.append(_("Shapes: %d") % self.NumShapes())
294
295 bbox = self.LatLongBoundingBox()
296 if bbox is not None:
297 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
298 else:
299 items.append(_("Extent (lat-lon):"))
300 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
301
302 if self.projection and len(self.projection.params) > 0:
303 items.append((_("Projection"),
304 [str(param) for param in self.projection.params]))
305
306 items.append(self.__classification)
307
308 return (_("Layer '%s'") % self.Title(), items)
309
310 def ClipBoundingBox(self, bbox):
311 """ Clip bbox to layer's bounding box.
312
313 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 if resource.has_gdal_support():
335 import gdal
336 from gdalconst import GA_ReadOnly
337
338 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
352 Throws IOError if the filename is invalid or points to a file that
353 is not in a format GDAL can use.
354 """
355
356 BaseLayer.__init__(self, title, visible = visible)
357
358 self.projection = projection
359 self.filename = os.path.abspath(filename)
360
361 self.bbox = -1
362
363 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
369 if dataset is None:
370 raise IOError()
371
372 self.UnsetModified()
373
374 def BoundingBox(self):
375 """Return the layer's bounding box in the intrinsic coordinate system.
376
377 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 """
380 if not resource.has_gdal_support():
381 return None
382
383 if self.bbox == -1:
384 dataset = gdal.Open(self.filename, GA_ReadOnly)
385 if dataset is None:
386 self.bbox = None
387 else:
388 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 self.bbox = (left, bottom, right, top)
413
414 return self.bbox
415
416 def LatLongBoundingBox(self):
417 bbox = self.BoundingBox()
418 if bbox is None:
419 return None
420
421 if self.projection is not None:
422 bbox = self.projection.InverseBBox(bbox)
423
424 return bbox
425
426 def GetImageFilename(self):
427 return self.filename
428
429 def TreeInfo(self):
430 items = []
431
432 items.append(_("Filename: %s") % self.GetImageFilename())
433
434 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