/[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 1978 - (show annotations)
Tue Nov 25 14:30:34 2003 UTC (21 years, 3 months ago) by bh
File MIME type: text/x-python
File size: 14086 byte(s)
(Layer.__getattr__): Removed. It was only
there for backwards compatibility and all code relying on that
should have been updated by now.

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:
170 llx, lly, urx, ury = bbox
171 if self.projection is not None:
172 llx, lly = self.projection.Inverse(llx, lly)
173 urx, ury = self.projection.Inverse(urx, ury)
174 return llx, lly, urx, ury
175 else:
176 return None
177
178 def ShapesBoundingBox(self, shapes):
179 """Return a bounding box in lat/long coordinates for the given
180 list of shape ids.
181
182 If shapes is None or empty, return None.
183 """
184
185 if shapes is None or len(shapes) == 0: return None
186
187 llx = []
188 lly = []
189 urx = []
190 ury = []
191
192 if self.projection is not None:
193 inverse = lambda x, y: self.projection.Inverse(x, y)
194 else:
195 inverse = lambda x, y: (x, y)
196
197 for id in shapes:
198 left, bottom, right, top = self.Shape(id).compute_bbox()
199
200 left, bottom = inverse(left, bottom)
201 right, top = inverse(right, top)
202
203 llx.append(left)
204 lly.append(bottom)
205 urx.append(right)
206 ury.append(top)
207
208 return (min(llx), min(lly), max(urx), max(ury))
209
210 def GetFieldType(self, fieldName):
211 if self.store:
212 table = self.store.Table()
213 if table.HasColumn(fieldName):
214 return table.Column(fieldName).type
215 return None
216
217 def HasShapes(self):
218 return True
219
220 def NumShapes(self):
221 """Return the number of shapes in the layer"""
222 return self.store.NumShapes()
223
224 def ShapeType(self):
225 """Return the type of the shapes in the layer.
226
227 The return value is one of the SHAPETYPE_* constants defined in
228 Thuban.Model.data.
229 """
230 return self.store.ShapeType()
231
232 def Shape(self, index):
233 """Return the shape with index index"""
234 return self.store.Shape(index)
235
236 def ShapesInRegion(self, bbox):
237 """Return an iterable over the shapes that overlap the bounding box.
238
239 The bbox parameter should be the bounding box as a tuple in the
240 form (minx, miny, maxx, maxy) in unprojected coordinates.
241 """
242 if self.projection is not None:
243 left, bottom, right, top = bbox
244 xs = []; ys = []
245 for x, y in [(left, bottom), (left, top), (right, top),
246 (right, bottom)]:
247 x, y = self.projection.Forward(x, y)
248 xs.append(x)
249 ys.append(y)
250 bbox = (min(xs), min(ys), max(xs), max(ys))
251
252 return self.store.ShapesInRegion(bbox)
253
254 def GetClassificationColumn(self):
255 return self.classification_column
256
257 def SetClassificationColumn(self, column):
258 """Set the column to classifiy on, or None. If column is not None
259 and the column does not exist in the table, raise a ValueError.
260 """
261 if column:
262 columnType = self.GetFieldType(column)
263 if columnType is None:
264 raise ValueError()
265 changed = self.classification_column != column
266 self.classification_column = column
267 if changed:
268 self.changed(LAYER_CHANGED, self)
269
270 def HasClassification(self):
271 return True
272
273 def GetClassification(self):
274 return self.__classification
275
276 def SetClassification(self, clazz):
277 """Set the classification used by this layer to 'clazz'
278
279 If 'clazz' is None a default classification is created.
280
281 This issues a LAYER_CHANGED event.
282 """
283
284 if self.__classification is not None:
285 self.__classification.Unsubscribe(CLASS_CHANGED,
286 self._classification_changed)
287
288 if clazz is None:
289 clazz = classification.Classification()
290
291 self.__classification = clazz
292 self.__classification.Subscribe(CLASS_CHANGED,
293 self._classification_changed)
294
295 self._classification_changed()
296
297 def _classification_changed(self):
298 """Called from the classification object when it has changed."""
299 self.changed(LAYER_CHANGED, self)
300
301 def TreeInfo(self):
302 items = []
303
304 items.append(_("Filename: %s") % self.ShapeStore().FileName())
305
306 if self.Visible():
307 items.append(_("Shown"))
308 else:
309 items.append(_("Hidden"))
310 items.append(_("Shapes: %d") % self.NumShapes())
311
312 bbox = self.LatLongBoundingBox()
313 if bbox is not None:
314 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
315 else:
316 items.append(_("Extent (lat-lon):"))
317 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
318
319 if self.projection and len(self.projection.params) > 0:
320 items.append((_("Projection"),
321 [str(param) for param in self.projection.params]))
322
323 items.append(self.__classification)
324
325 return (_("Layer '%s'") % self.Title(), items)
326
327
328 if resource.has_gdal_support():
329 import gdal
330 from gdalconst import GA_ReadOnly
331
332 class RasterLayer(BaseLayer):
333
334 def __init__(self, title, filename, projection = None, visible = True):
335 """Initialize the Raster Layer.
336
337 title -- title for the layer.
338
339 filename -- file name of the source image.
340
341 projection -- Projection object describing the projection which
342 the source image is in.
343
344 visible -- True is the layer should initially be visible.
345
346 Throws IOError if the filename is invalid or points to a file that
347 is not in a format GDAL can use.
348 """
349
350 BaseLayer.__init__(self, title, visible = visible)
351
352 self.projection = projection
353 self.filename = os.path.abspath(filename)
354
355 self.bbox = -1
356
357 if resource.has_gdal_support():
358 #
359 # temporarily open the file so that GDAL can test if it's valid.
360 #
361 dataset = gdal.Open(self.filename, GA_ReadOnly)
362
363 if dataset is None:
364 raise IOError()
365
366 self.UnsetModified()
367
368 def BoundingBox(self):
369 """Return the layer's bounding box in the intrinsic coordinate system.
370
371 If the there is no support for images, or the file cannot
372 be read, or there is no geographics information available, return None.
373 """
374 if not resource.has_gdal_support():
375 return None
376
377 if self.bbox == -1:
378 dataset = gdal.Open(self.filename, GA_ReadOnly)
379 if dataset is None:
380 self.bbox = None
381 else:
382 geotransform = dataset.GetGeoTransform()
383 if geotransform is None:
384 return None
385
386 x = 0
387 y = dataset.RasterYSize
388 left = geotransform[0] + \
389 geotransform[1] * x + \
390 geotransform[2] * y
391
392 bottom = geotransform[3] + \
393 geotransform[4] * x + \
394 geotransform[5] * y
395
396 x = dataset.RasterXSize
397 y = 0
398 right = geotransform[0] + \
399 geotransform[1] * x + \
400 geotransform[2] * y
401
402 top = geotransform[3] + \
403 geotransform[4] * x + \
404 geotransform[5] * y
405
406 self.bbox = (left, bottom, right, top)
407
408 return self.bbox
409
410 def LatLongBoundingBox(self):
411 bbox = self.BoundingBox()
412 if bbox is None:
413 return None
414
415 llx, lly, urx, ury = bbox
416 if self.projection is not None:
417 llx, lly = self.projection.Inverse(llx, lly)
418 urx, ury = self.projection.Inverse(urx, ury)
419
420 return llx, lly, urx, ury
421
422 def GetImageFilename(self):
423 return self.filename
424
425 def TreeInfo(self):
426 items = []
427
428 items.append(_("Filename: %s") % self.GetImageFilename())
429
430 if self.Visible():
431 items.append(_("Shown"))
432 else:
433 items.append(_("Hidden"))
434
435 bbox = self.LatLongBoundingBox()
436 if bbox is not None:
437 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
438 else:
439 items.append(_("Extent (lat-lon):"))
440
441 if self.projection and len(self.projection.params) > 0:
442 items.append((_("Projection"),
443 [str(param) for param in self.projection.params]))
444
445 return (_("Layer '%s'") % self.Title(), items)
446

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26