/[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 929 - (show annotations)
Tue May 20 15:22:42 2003 UTC (21 years, 9 months ago) by jonathan
File MIME type: text/x-python
File size: 13801 byte(s)
(BaseLayer.HasClassification): New.
        Defaults to False, but can be overridden by subclasses if they
        support classification.
(RasterLayer): New. Defines a new layer that represents an image.

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 self.filename = self.store.filename
177 self.table = self.shapetable
178
179 numshapes, shapetype, mins, maxs = self.shapefile.info()
180 self.numshapes = numshapes
181 self.shapetype = shapelib_shapetypes[shapetype]
182
183 # if there are shapes, set the bbox accordingly. Otherwise
184 # set it to None.
185 if self.numshapes:
186 self.bbox = mins[:2] + maxs[:2]
187 else:
188 self.bbox = None
189
190 # estimate a good depth for the quad tree. Each depth
191 # multiplies the number of nodes by four, therefore we
192 # basically take the base 4 logarithm of the number of
193 # shapes.
194 if self.numshapes < 4:
195 maxdepth = 1
196 else:
197 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
198
199 self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
200 maxdepth)
201 if self.__classification is not None:
202 fieldname = self.__classification.GetField()
203 if fieldname is not None and \
204 not self.store.Table().HasColumn(fieldname):
205 self.SetClassification(None)
206 self.changed(LAYER_CHANGED, self)
207
208 def ShapeStore(self):
209 return self.store
210
211 def Destroy(self):
212 BaseLayer.Destroy(self)
213 self.SetClassification(None)
214
215 def BoundingBox(self):
216 """Return the layer's bounding box in the intrinsic coordinate system.
217
218 If the layer has no shapes, return None.
219 """
220 return self.bbox
221
222 def LatLongBoundingBox(self):
223 """Return the layer's bounding box in lat/long coordinates.
224
225 Return None, if the layer doesn't contain any shapes.
226 """
227 bbox = self.BoundingBox()
228 if bbox is not None:
229 llx, lly, urx, ury = bbox
230 if self.projection is not None:
231 llx, lly = self.projection.Inverse(llx, lly)
232 urx, ury = self.projection.Inverse(urx, ury)
233 return llx, lly, urx, ury
234 else:
235 return None
236
237 def ShapesBoundingBox(self, shapes):
238 """Return a bounding box in lat/long coordinates for the given
239 list of shape ids.
240
241 If shapes is None or empty, return None.
242 """
243
244 if shapes is None or len(shapes) == 0: return None
245
246 llx = []
247 lly = []
248 urx = []
249 ury = []
250
251 if self.projection is not None:
252 inverse = lambda x, y: self.projection.Inverse(x, y)
253 else:
254 inverse = lambda x, y: (x, y)
255
256 for id in shapes:
257 left, bottom, right, top = self.Shape(id).compute_bbox()
258
259 left, bottom = inverse(left, bottom)
260 right, top = inverse(right, top)
261
262 llx.append(left)
263 lly.append(bottom)
264 urx.append(right)
265 ury.append(top)
266
267 return (min(llx), min(lly), max(urx), max(ury))
268
269 def GetFieldType(self, fieldName):
270 if self.table.HasColumn(fieldName):
271 return self.table.Column(fieldName).type
272 return None
273
274 def NumShapes(self):
275 """Return the number of shapes in the layer"""
276 return self.numshapes
277
278 def ShapeType(self):
279 """Return the type of the shapes in the layer.
280 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
281 """
282 return self.shapetype
283
284 def Shape(self, index):
285 """Return the shape with index index"""
286 shape = self.shapefile.read_object(index)
287
288 if self.shapetype == SHAPETYPE_POINT:
289 points = shape.vertices()
290 else:
291 #for poly in shape.vertices():
292 poly = shape.vertices()[0]
293 points = []
294 for x, y in poly:
295 points.append((x, y))
296
297 return Shape(points)
298
299 def ShapesInRegion(self, box):
300 """Return the ids of the shapes that overlap the box.
301
302 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
303 """
304 left, bottom, right, top = box
305
306 if self.projection is not None:
307 left, bottom = self.projection.Forward(left, bottom)
308 right, top = self.projection.Forward(right, top)
309
310 return self.shapetree.find_shapes((left, bottom), (right, top))
311
312 def HasClassification(self):
313 return True
314
315 def GetClassification(self):
316 return self.__classification
317
318 def SetClassification(self, clazz):
319 """Set the classification to 'clazz'
320
321 If 'clazz' is None a default classification is created
322 """
323
324 # prevent infinite recursion when calling SetLayer()
325 if self.__setClassLock: return
326
327 self.__setClassLock = True
328
329 if clazz is None:
330 if self.__classification is not None:
331 self.__classification.SetLayer(None)
332 self.__classification = classification.Classification()
333 else:
334 self.__classification = clazz
335 try:
336 self.__classification.SetLayer(self)
337 except ValueError:
338 self.__setClassLock = False
339 raise ValueError
340
341 self.changed(LAYER_CHANGED, self)
342
343 self.__setClassLock = False
344
345 def ClassChanged(self):
346 """Called from the classification object when it has changed."""
347 self.changed(LAYER_CHANGED, self)
348
349 def TreeInfo(self):
350 items = []
351
352 if self.Visible():
353 items.append(_("Shown"))
354 else:
355 items.append(_("Hidden"))
356 items.append(_("Shapes: %d") % self.NumShapes())
357
358 bbox = self.LatLongBoundingBox()
359 if bbox is not None:
360 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
361 else:
362 items.append(_("Extent (lat-lon):"))
363 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
364
365 if self.projection and len(self.projection.params) > 0:
366 items.append((_("Projection"),
367 [str(param) for param in self.projection.params]))
368
369 items.append(self.__classification)
370
371 return (_("Layer '%s'") % self.Title(), items)
372
373
374 class RasterLayer(BaseLayer):
375
376 def __init__(self, title, filename, projection = None, visible = True):
377 """Initialize the Raster Layer.
378
379 title -- title for the layer.
380
381 filename -- file name of the source image.
382
383 projection -- Projection object describing the projection which
384 the source image is in.
385
386 visible -- True is the layer should initially be visible.
387 """
388
389 BaseLayer.__init__(self, title, visible = visible)
390
391 self.projection = projection
392 self.filename = filename
393
394 self.bbox = -1
395
396 self.UnsetModified()
397
398
399 def BoundingBox(self):
400 """Return the layer's bounding box in the intrinsic coordinate system.
401
402 If the layer has no shapes, return None.
403 """
404 if self.bbox == -1:
405 dataset = gdal.Open(self.filename, GA_ReadOnly)
406 if dataset is None:
407 self.bbox = None
408 else:
409 left, bottom = self.__CalcCorner(dataset,
410 0, dataset.RasterYSize)
411 right, top = self.__CalcCorner(dataset,
412 dataset.RasterXSize, 0)
413 self.bbox = (left, bottom, right, top)
414
415 return self.bbox
416
417 def __CalcCorner(self, dataset, x, y):
418 geotransform = dataset.GetGeoTransform()
419 return geotransform[0] + geotransform[1] * x + geotransform[2] * y, \
420 geotransform[3] + geotransform[4] * x + geotransform[5] * y
421
422 def LatLongBoundingBox(self):
423 bbox = self.BoundingBox()
424 if bbox is not None:
425 llx, lly, urx, ury = bbox
426 if self.projection is not None:
427 llx, lly = self.projection.Inverse(llx, lly)
428 urx, ury = self.projection.Inverse(urx, ury)
429 return llx, lly, urx, ury
430 else:
431 return None
432
433 def GetImageFilename(self):
434 return self.filename
435
436 def TreeInfo(self):
437 items = []
438
439 if self.Visible():
440 items.append(_("Shown"))
441 else:
442 items.append(_("Hidden"))
443 items.append(_("Shapes: %d") % self.NumShapes())
444
445 bbox = self.LatLongBoundingBox()
446 if bbox is not None:
447 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
448 else:
449 items.append(_("Extent (lat-lon):"))
450
451 if self.projection and len(self.projection.params) > 0:
452 items.append((_("Projection"),
453 [str(param) for param in self.projection.params]))
454
455 return (_("Layer '%s'") % self.Title(), items)
456

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26