/[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 1012 - (show annotations)
Fri May 23 09:13:58 2003 UTC (21 years, 9 months ago) by jan
File MIME type: text/x-python
File size: 14518 byte(s)
(Layer.TreeInfo): Added filename.

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 from math import log, ceil
12
13 from Thuban import _
14
15 import shapelib, shptree
16
17 import gdal
18 from gdalconst import GA_ReadOnly
19
20 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
21 LAYER_CHANGED
22
23 from color import Color
24
25 import classification
26
27 from base import TitledObject, Modifiable
28
29
30 class Shape:
31
32 """Represent one shape"""
33
34 def __init__(self, points):
35 self.points = points
36 #self.compute_bbox()
37 self.bbox = None
38
39 def compute_bbox(self):
40 if self.bbox is not None:
41 return self.bbox
42
43 xs = []
44 ys = []
45 for x, y in self.points:
46 xs.append(x)
47 ys.append(y)
48 self.llx = min(xs)
49 self.lly = min(ys)
50 self.urx = max(xs)
51 self.ury = max(ys)
52
53 self.bbox = (self.llx, self.lly, self.urx, self.ury)
54
55 return self.bbox
56
57 def Points(self):
58 return self.points
59
60
61
62 # Shape type constants
63 SHAPETYPE_POLYGON = "polygon"
64 SHAPETYPE_ARC = "arc"
65 SHAPETYPE_POINT = "point"
66
67 # mapping from shapelib shapetype constants to our constants
68 shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,
69 shapelib.SHPT_ARC: SHAPETYPE_ARC,
70 shapelib.SHPT_POINT: SHAPETYPE_POINT}
71
72 shapetype_names = {SHAPETYPE_POINT: "Point",
73 SHAPETYPE_ARC: "Arc",
74 SHAPETYPE_POLYGON: "Polygon"}
75
76 class BaseLayer(TitledObject, Modifiable):
77
78 """Base class for the layers."""
79
80 def __init__(self, title, visible = True, projection = None):
81 """Initialize the layer.
82
83 title -- the title
84 visible -- boolean. If true the layer is visible.
85 """
86 TitledObject.__init__(self, title)
87 Modifiable.__init__(self)
88 self.visible = visible
89 self.projection = projection
90
91 def Visible(self):
92 """Return true if layer is visible"""
93 return self.visible
94
95 def SetVisible(self, visible):
96 """Set the layer's visibility."""
97 self.visible = visible
98 self.issue(LAYER_VISIBILITY_CHANGED, self)
99
100 def HasClassification(self):
101 """Determine if this layer support classifications."""
102 return False
103
104 def GetProjection(self):
105 """Return the layer's projection."""
106 return self.projection
107
108 def SetProjection(self, projection):
109 """Set the layer's projection"""
110 self.projection = projection
111 self.changed(LAYER_PROJECTION_CHANGED, self)
112
113 class Layer(BaseLayer):
114
115 """Represent the information of one geodata file (currently a shapefile)
116
117 All children of the layer have the same type.
118
119 A layer has fill and stroke colors. Colors should be instances of
120 Color. They can also be None, indicating no fill or no stroke.
121
122 The layer objects send the following events, all of which have the
123 layer object as parameter:
124
125 TITLE_CHANGED -- The title has changed.
126 LAYER_PROJECTION_CHANGED -- the projection has changed.
127 """
128
129 def __init__(self, title, data, projection = None,
130 fill = Color.Transparent,
131 stroke = Color.Black,
132 lineWidth = 1,
133 visible = True):
134 """Initialize the layer.
135
136 title -- the title
137 data -- datastore object for the shape data shown by the layer
138 projection -- the projection object. Its Inverse method is
139 assumed to map the layer's coordinates to lat/long
140 coordinates
141 fill -- the fill color or Color.Transparent if the shapes are
142 not filled
143 stroke -- the stroke color or Color.Transparent if the shapes
144 are not stroked
145 visible -- boolean. If true the layer is visible.
146
147 colors are expected to be instances of Color class
148 """
149 BaseLayer.__init__(self, title,
150 visible = visible,
151 projection = projection)
152
153 #
154 # this is really important so that when the classification class
155 # tries to set its parent layer the variable will exist
156 #
157 self.__classification = None
158 self.__setClassLock = False
159
160 self.SetShapeStore(data)
161
162 self.SetClassification(None)
163
164 self.__classification.SetDefaultLineColor(stroke)
165 self.__classification.SetDefaultLineWidth(lineWidth)
166 self.__classification.SetDefaultFill(fill)
167 self.__classification.SetLayer(self)
168
169 self.UnsetModified()
170
171
172 def SetShapeStore(self, store):
173 self.store = store
174 self.shapefile = self.store.Shapefile()
175 self.shapetable = self.store.Table()
176 if hasattr(self.store, "FileName"):
177 self.filename = self.store.FileName()
178 self.table = self.shapetable
179
180 numshapes, shapetype, mins, maxs = self.shapefile.info()
181 self.numshapes = numshapes
182 self.shapetype = shapelib_shapetypes[shapetype]
183
184 # if there are shapes, set the bbox accordingly. Otherwise
185 # set it to None.
186 if self.numshapes:
187 self.bbox = mins[:2] + maxs[:2]
188 else:
189 self.bbox = None
190
191 # estimate a good depth for the quad tree. Each depth
192 # multiplies the number of nodes by four, therefore we
193 # basically take the base 4 logarithm of the number of
194 # shapes.
195 if self.numshapes < 4:
196 maxdepth = 1
197 else:
198 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
199
200 self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
201 maxdepth)
202 if self.__classification is not None:
203 fieldname = self.__classification.GetField()
204 if fieldname is not None and \
205 not self.store.Table().HasColumn(fieldname):
206 self.SetClassification(None)
207 self.changed(LAYER_CHANGED, self)
208
209 def ShapeStore(self):
210 return self.store
211
212 def Destroy(self):
213 BaseLayer.Destroy(self)
214 self.SetClassification(None)
215
216 def BoundingBox(self):
217 """Return the layer's bounding box in the intrinsic coordinate system.
218
219 If the layer has no shapes, return None.
220 """
221 return self.bbox
222
223 def LatLongBoundingBox(self):
224 """Return the layer's bounding box in lat/long coordinates.
225
226 Return None, if the layer doesn't contain any shapes.
227 """
228 bbox = self.BoundingBox()
229 if bbox is not None:
230 llx, lly, urx, ury = bbox
231 if self.projection is not None:
232 llx, lly = self.projection.Inverse(llx, lly)
233 urx, ury = self.projection.Inverse(urx, ury)
234 return llx, lly, urx, ury
235 else:
236 return None
237
238 def ShapesBoundingBox(self, shapes):
239 """Return a bounding box in lat/long coordinates for the given
240 list of shape ids.
241
242 If shapes is None or empty, return None.
243 """
244
245 if shapes is None or len(shapes) == 0: return None
246
247 llx = []
248 lly = []
249 urx = []
250 ury = []
251
252 if self.projection is not None:
253 inverse = lambda x, y: self.projection.Inverse(x, y)
254 else:
255 inverse = lambda x, y: (x, y)
256
257 for id in shapes:
258 left, bottom, right, top = self.Shape(id).compute_bbox()
259
260 left, bottom = inverse(left, bottom)
261 right, top = inverse(right, top)
262
263 llx.append(left)
264 lly.append(bottom)
265 urx.append(right)
266 ury.append(top)
267
268 return (min(llx), min(lly), max(urx), max(ury))
269
270 def GetFieldType(self, fieldName):
271 if self.table.HasColumn(fieldName):
272 return self.table.Column(fieldName).type
273 return None
274
275 def NumShapes(self):
276 """Return the number of shapes in the layer"""
277 return self.numshapes
278
279 def ShapeType(self):
280 """Return the type of the shapes in the layer.
281 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
282 """
283 return self.shapetype
284
285 def Shape(self, index):
286 """Return the shape with index index"""
287 shape = self.shapefile.read_object(index)
288
289 if self.shapetype == SHAPETYPE_POINT:
290 points = shape.vertices()
291 else:
292 #for poly in shape.vertices():
293 poly = shape.vertices()[0]
294 points = []
295 for x, y in poly:
296 points.append((x, y))
297
298 return Shape(points)
299
300 def ShapesInRegion(self, box):
301 """Return the ids of the shapes that overlap the box.
302
303 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
304 """
305 left, bottom, right, top = box
306
307 if self.projection is not None:
308 left, bottom = self.projection.Forward(left, bottom)
309 right, top = self.projection.Forward(right, top)
310
311 return self.shapetree.find_shapes((left, bottom), (right, top))
312
313 def HasClassification(self):
314 return True
315
316 def GetClassification(self):
317 return self.__classification
318
319 def SetClassification(self, clazz):
320 """Set the classification to 'clazz'
321
322 If 'clazz' is None a default classification is created
323 """
324
325 # prevent infinite recursion when calling SetLayer()
326 if self.__setClassLock: return
327
328 self.__setClassLock = True
329
330 if clazz is None:
331 if self.__classification is not None:
332 self.__classification.SetLayer(None)
333 self.__classification = classification.Classification()
334 else:
335 self.__classification = clazz
336 try:
337 self.__classification.SetLayer(self)
338 except ValueError:
339 self.__setClassLock = False
340 raise ValueError
341
342 self.changed(LAYER_CHANGED, self)
343
344 self.__setClassLock = False
345
346 def ClassChanged(self):
347 """Called from the classification object when it has changed."""
348 self.changed(LAYER_CHANGED, self)
349
350 def TreeInfo(self):
351 items = []
352
353 items.append(_("Filename: %s") % self.filename)
354
355 if self.Visible():
356 items.append(_("Shown"))
357 else:
358 items.append(_("Hidden"))
359 items.append(_("Shapes: %d") % self.NumShapes())
360
361 bbox = self.LatLongBoundingBox()
362 if bbox is not None:
363 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
364 else:
365 items.append(_("Extent (lat-lon):"))
366 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
367
368 if self.projection and len(self.projection.params) > 0:
369 items.append((_("Projection"),
370 [str(param) for param in self.projection.params]))
371
372 items.append(self.__classification)
373
374 return (_("Layer '%s'") % self.Title(), items)
375
376
377 class RasterLayer(BaseLayer):
378
379 def __init__(self, title, filename, projection = None, visible = True):
380 """Initialize the Raster Layer.
381
382 title -- title for the layer.
383
384 filename -- file name of the source image.
385
386 projection -- Projection object describing the projection which
387 the source image is in.
388
389 visible -- True is the layer should initially be visible.
390
391 Throws IOError if the filename is invalid or points to a file that
392 is not in a format GDAL can use.
393 """
394
395 BaseLayer.__init__(self, title, visible = visible)
396
397 self.projection = projection
398 self.filename = filename
399
400 self.bbox = -1
401
402 #
403 # temporarily open the file so that GDAL can test if it's valid.
404 #
405 dataset = gdal.Open(self.filename, GA_ReadOnly)
406
407 if dataset is None:
408 raise IOError()
409
410 self.UnsetModified()
411
412 def BoundingBox(self):
413 """Return the layer's bounding box in the intrinsic coordinate system.
414
415 If the layer has no shapes, return None.
416 """
417 if self.bbox == -1:
418 dataset = gdal.Open(self.filename, GA_ReadOnly)
419 if dataset is None:
420 self.bbox = None
421 else:
422 geotransform = dataset.GetGeoTransform()
423 if geotransform is None:
424 return None
425
426 x = 0
427 y = dataset.RasterYSize
428 left = geotransform[0] + \
429 geotransform[1] * x + \
430 geotransform[2] * y
431
432 bottom = geotransform[3] + \
433 geotransform[4] * x + \
434 geotransform[5] * y
435
436 x = dataset.RasterXSize
437 y = 0
438 right = geotransform[0] + \
439 geotransform[1] * x + \
440 geotransform[2] * y
441
442 top = geotransform[3] + \
443 geotransform[4] * x + \
444 geotransform[5] * y
445
446 self.bbox = (left, bottom, right, top)
447
448 return self.bbox
449
450 def LatLongBoundingBox(self):
451 bbox = self.BoundingBox()
452 if bbox is None:
453 return None
454
455 llx, lly, urx, ury = bbox
456 if self.projection is not None:
457 llx, lly = self.projection.Inverse(llx, lly)
458 urx, ury = self.projection.Inverse(urx, ury)
459
460 return llx, lly, urx, ury
461
462 def GetImageFilename(self):
463 return self.filename
464
465 def TreeInfo(self):
466 items = []
467
468 if self.Visible():
469 items.append(_("Shown"))
470 else:
471 items.append(_("Hidden"))
472 items.append(_("Shapes: %d") % self.NumShapes())
473
474 bbox = self.LatLongBoundingBox()
475 if bbox is not None:
476 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
477 else:
478 items.append(_("Extent (lat-lon):"))
479
480 if self.projection and len(self.projection.params) > 0:
481 items.append((_("Projection"),
482 [str(param) for param in self.projection.params]))
483
484 return (_("Layer '%s'") % self.Title(), items)
485

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26