/[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 1117 - (show annotations)
Fri May 30 18:31:19 2003 UTC (21 years, 9 months ago) by bh
File MIME type: text/x-python
File size: 15160 byte(s)
(Layer.Destroy): Set all instance
variables to None that have direct or indirect references to
shapefiles or dbf files to make sure that they do go away and the
files are closed.

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 # Set the classification to None if there is a classification
203 # and the new shapestore doesn't have a table with a suitable
204 # column, i.e one with the same name and type as before
205 # FIXME: Maybe we should keep it the same if the type is
206 # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
207 if self.__classification is not None:
208 fieldname = self.__classification.GetField()
209 fieldtype = self.__classification.GetFieldType()
210 table = self.store.Table()
211 if (fieldname is not None
212 and (not table.HasColumn(fieldname)
213 or table.Column(fieldname).type != fieldtype)):
214 self.SetClassification(None)
215 self.changed(LAYER_CHANGED, self)
216
217 def ShapeStore(self):
218 return self.store
219
220 def Destroy(self):
221 BaseLayer.Destroy(self)
222 self.SetClassification(None)
223 self.store = self.shapetree = None
224 self.table = self.shapefile = self.shapetable = None
225
226 def BoundingBox(self):
227 """Return the layer's bounding box in the intrinsic coordinate system.
228
229 If the layer has no shapes, return None.
230 """
231 return self.bbox
232
233 def LatLongBoundingBox(self):
234 """Return the layer's bounding box in lat/long coordinates.
235
236 Return None, if the layer doesn't contain any shapes.
237 """
238 bbox = self.BoundingBox()
239 if bbox is not None:
240 llx, lly, urx, ury = bbox
241 if self.projection is not None:
242 llx, lly = self.projection.Inverse(llx, lly)
243 urx, ury = self.projection.Inverse(urx, ury)
244 return llx, lly, urx, ury
245 else:
246 return None
247
248 def ShapesBoundingBox(self, shapes):
249 """Return a bounding box in lat/long coordinates for the given
250 list of shape ids.
251
252 If shapes is None or empty, return None.
253 """
254
255 if shapes is None or len(shapes) == 0: return None
256
257 llx = []
258 lly = []
259 urx = []
260 ury = []
261
262 if self.projection is not None:
263 inverse = lambda x, y: self.projection.Inverse(x, y)
264 else:
265 inverse = lambda x, y: (x, y)
266
267 for id in shapes:
268 left, bottom, right, top = self.Shape(id).compute_bbox()
269
270 left, bottom = inverse(left, bottom)
271 right, top = inverse(right, top)
272
273 llx.append(left)
274 lly.append(bottom)
275 urx.append(right)
276 ury.append(top)
277
278 return (min(llx), min(lly), max(urx), max(ury))
279
280 def GetFieldType(self, fieldName):
281 if self.table.HasColumn(fieldName):
282 return self.table.Column(fieldName).type
283 return None
284
285 def NumShapes(self):
286 """Return the number of shapes in the layer"""
287 return self.numshapes
288
289 def ShapeType(self):
290 """Return the type of the shapes in the layer.
291 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
292 """
293 return self.shapetype
294
295 def Shape(self, index):
296 """Return the shape with index index"""
297 shape = self.shapefile.read_object(index)
298
299 if self.shapetype == SHAPETYPE_POINT:
300 points = shape.vertices()
301 else:
302 #for poly in shape.vertices():
303 poly = shape.vertices()[0]
304 points = []
305 for x, y in poly:
306 points.append((x, y))
307
308 return Shape(points)
309
310 def ShapesInRegion(self, box):
311 """Return the ids of the shapes that overlap the box.
312
313 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
314 """
315 left, bottom, right, top = box
316
317 if self.projection is not None:
318 left, bottom = self.projection.Forward(left, bottom)
319 right, top = self.projection.Forward(right, top)
320
321 return self.shapetree.find_shapes((left, bottom), (right, top))
322
323 def HasClassification(self):
324 return True
325
326 def GetClassification(self):
327 return self.__classification
328
329 def SetClassification(self, clazz):
330 """Set the classification to 'clazz'
331
332 If 'clazz' is None a default classification is created
333 """
334
335 # prevent infinite recursion when calling SetLayer()
336 if self.__setClassLock: return
337
338 self.__setClassLock = True
339
340 if clazz is None:
341 if self.__classification is not None:
342 self.__classification.SetLayer(None)
343 self.__classification = classification.Classification()
344 else:
345 self.__classification = clazz
346 try:
347 self.__classification.SetLayer(self)
348 except ValueError:
349 self.__setClassLock = False
350 raise ValueError
351
352 self.changed(LAYER_CHANGED, self)
353
354 self.__setClassLock = False
355
356 def ClassChanged(self):
357 """Called from the classification object when it has changed."""
358 self.changed(LAYER_CHANGED, self)
359
360 def TreeInfo(self):
361 items = []
362
363 if hasattr(self, 'filename'):
364 items.append(_("Filename: %s") % self.filename)
365
366 if self.Visible():
367 items.append(_("Shown"))
368 else:
369 items.append(_("Hidden"))
370 items.append(_("Shapes: %d") % self.NumShapes())
371
372 bbox = self.LatLongBoundingBox()
373 if bbox is not None:
374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
375 else:
376 items.append(_("Extent (lat-lon):"))
377 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
378
379 if self.projection and len(self.projection.params) > 0:
380 items.append((_("Projection"),
381 [str(param) for param in self.projection.params]))
382
383 items.append(self.__classification)
384
385 return (_("Layer '%s'") % self.Title(), items)
386
387
388 class RasterLayer(BaseLayer):
389
390 def __init__(self, title, filename, projection = None, visible = True):
391 """Initialize the Raster Layer.
392
393 title -- title for the layer.
394
395 filename -- file name of the source image.
396
397 projection -- Projection object describing the projection which
398 the source image is in.
399
400 visible -- True is the layer should initially be visible.
401
402 Throws IOError if the filename is invalid or points to a file that
403 is not in a format GDAL can use.
404 """
405
406 BaseLayer.__init__(self, title, visible = visible)
407
408 self.projection = projection
409 self.filename = filename
410
411 self.bbox = -1
412
413 #
414 # temporarily open the file so that GDAL can test if it's valid.
415 #
416 dataset = gdal.Open(self.filename, GA_ReadOnly)
417
418 if dataset is None:
419 raise IOError()
420
421 self.UnsetModified()
422
423 def BoundingBox(self):
424 """Return the layer's bounding box in the intrinsic coordinate system.
425
426 If the layer has no shapes, return None.
427 """
428 if self.bbox == -1:
429 dataset = gdal.Open(self.filename, GA_ReadOnly)
430 if dataset is None:
431 self.bbox = None
432 else:
433 geotransform = dataset.GetGeoTransform()
434 if geotransform is None:
435 return None
436
437 x = 0
438 y = dataset.RasterYSize
439 left = geotransform[0] + \
440 geotransform[1] * x + \
441 geotransform[2] * y
442
443 bottom = geotransform[3] + \
444 geotransform[4] * x + \
445 geotransform[5] * y
446
447 x = dataset.RasterXSize
448 y = 0
449 right = geotransform[0] + \
450 geotransform[1] * x + \
451 geotransform[2] * y
452
453 top = geotransform[3] + \
454 geotransform[4] * x + \
455 geotransform[5] * y
456
457 self.bbox = (left, bottom, right, top)
458
459 return self.bbox
460
461 def LatLongBoundingBox(self):
462 bbox = self.BoundingBox()
463 if bbox is None:
464 return None
465
466 llx, lly, urx, ury = bbox
467 if self.projection is not None:
468 llx, lly = self.projection.Inverse(llx, lly)
469 urx, ury = self.projection.Inverse(urx, ury)
470
471 return llx, lly, urx, ury
472
473 def GetImageFilename(self):
474 return self.filename
475
476 def TreeInfo(self):
477 items = []
478
479 if self.Visible():
480 items.append(_("Shown"))
481 else:
482 items.append(_("Hidden"))
483 items.append(_("Shapes: %d") % self.NumShapes())
484
485 bbox = self.LatLongBoundingBox()
486 if bbox is not None:
487 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
488 else:
489 items.append(_("Extent (lat-lon):"))
490
491 if self.projection and len(self.projection.params) > 0:
492 items.append((_("Projection"),
493 [str(param) for param in self.projection.params]))
494
495 return (_("Layer '%s'") % self.Title(), items)
496

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26