/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Annotation of /trunk/thuban/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1158 - (hide annotations)
Thu Jun 12 12:40:11 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 15387 byte(s)
Import gdal only if it available.
(RasterLayer): Handle the case where the gdal library is unavailable.
        Addresses RTbug #1877.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26