/[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 1012 - (hide 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 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    
15 bh 143 import shapelib, shptree
16 bh 6
17 jonathan 929 import gdal
18     from gdalconst import GA_ReadOnly
19    
20 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
21     LAYER_CHANGED
22 bh 6
23     from color import Color
24    
25 jonathan 437 import classification
26 bh 6
27     from base import TitledObject, Modifiable
28    
29 bh 723
30 bh 6 class Shape:
31    
32     """Represent one shape"""
33    
34     def __init__(self, points):
35     self.points = points
36     #self.compute_bbox()
37 jonathan 828 self.bbox = None
38 bh 6
39     def compute_bbox(self):
40 jonathan 828 if self.bbox is not None:
41     return self.bbox
42    
43 bh 6 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 jonathan 828 self.bbox = (self.llx, self.lly, self.urx, self.ury)
54    
55     return self.bbox
56    
57 bh 6 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 jonathan 929 def __init__(self, title, visible = True, projection = None):
81 bh 6 """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 jonathan 929 self.projection = projection
90 bh 6
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 bh 276
100 jonathan 929 def HasClassification(self):
101     """Determine if this layer support classifications."""
102     return False
103 bh 276
104 jonathan 929 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 bh 6 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 bh 723 def __init__(self, title, data, projection = None,
130 jonathan 610 fill = Color.Transparent,
131 jonathan 412 stroke = Color.Black,
132 jonathan 464 lineWidth = 1,
133 jonathan 771 visible = True):
134 bh 6 """Initialize the layer.
135    
136     title -- the title
137 bh 723 data -- datastore object for the shape data shown by the layer
138 bh 6 projection -- the projection object. Its Inverse method is
139     assumed to map the layer's coordinates to lat/long
140     coordinates
141 jonathan 610 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 bh 6 visible -- boolean. If true the layer is visible.
146    
147     colors are expected to be instances of Color class
148     """
149 jonathan 929 BaseLayer.__init__(self, title,
150     visible = visible,
151     projection = projection)
152 bh 276
153 jonathan 481 #
154     # this is really important so that when the classification class
155     # tries to set its parent layer the variable will exist
156     #
157 bh 723 self.__classification = None
158 jonathan 529 self.__setClassLock = False
159 jonathan 481
160 bh 723 self.SetShapeStore(data)
161 jonathan 492
162     self.SetClassification(None)
163    
164 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
165     self.__classification.SetDefaultLineWidth(lineWidth)
166 jonathan 412 self.__classification.SetDefaultFill(fill)
167 jonathan 492 self.__classification.SetLayer(self)
168 jonathan 364
169 jonathan 389 self.UnsetModified()
170    
171 bh 6
172 bh 723 def SetShapeStore(self, store):
173     self.store = store
174     self.shapefile = self.store.Shapefile()
175     self.shapetable = self.store.Table()
176 bh 996 if hasattr(self.store, "FileName"):
177     self.filename = self.store.FileName()
178 bh 723 self.table = self.shapetable
179 bh 179
180 bh 723 numshapes, shapetype, mins, maxs = self.shapefile.info()
181     self.numshapes = numshapes
182     self.shapetype = shapelib_shapetypes[shapetype]
183 bh 171
184 bh 723 # 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 bh 171
191 bh 723 # 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 jan 791 if fieldname is not None and \
205 bh 839 not self.store.Table().HasColumn(fieldname):
206 bh 723 self.SetClassification(None)
207     self.changed(LAYER_CHANGED, self)
208    
209     def ShapeStore(self):
210     return self.store
211    
212 bh 258 def Destroy(self):
213 bh 260 BaseLayer.Destroy(self)
214 jonathan 529 self.SetClassification(None)
215 bh 258
216 bh 6 def BoundingBox(self):
217 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
218    
219     If the layer has no shapes, return None.
220     """
221 bh 6 return self.bbox
222    
223     def LatLongBoundingBox(self):
224 bh 179 """Return the layer's bounding box in lat/long coordinates.
225 bh 6
226 bh 179 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 jonathan 828 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 jonathan 464 def GetFieldType(self, fieldName):
271 bh 839 if self.table.HasColumn(fieldName):
272     return self.table.Column(fieldName).type
273     return None
274 jonathan 464
275 bh 6 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 jonathan 364
289 bh 6 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 bh 82 points.append((x, y))
297 jonathan 364
298 bh 6 return Shape(points)
299    
300 bh 143 def ShapesInRegion(self, box):
301     """Return the ids of the shapes that overlap the box.
302    
303 jonathan 794 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
304 bh 143 """
305     left, bottom, right, top = box
306 jonathan 794
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 bh 147 return self.shapetree.find_shapes((left, bottom), (right, top))
312 bh 143
313 jonathan 929 def HasClassification(self):
314     return True
315 jonathan 725
316 jonathan 412 def GetClassification(self):
317     return self.__classification
318    
319     def SetClassification(self, clazz):
320 jonathan 492 """Set the classification to 'clazz'
321 jonathan 481
322 jonathan 492 If 'clazz' is None a default classification is created
323     """
324 jonathan 481
325 jonathan 529 # prevent infinite recursion when calling SetLayer()
326     if self.__setClassLock: return
327    
328     self.__setClassLock = True
329    
330 jonathan 492 if clazz is None:
331 jonathan 529 if self.__classification is not None:
332     self.__classification.SetLayer(None)
333 jonathan 492 self.__classification = classification.Classification()
334     else:
335     self.__classification = clazz
336 jonathan 529 try:
337     self.__classification.SetLayer(self)
338     except ValueError:
339     self.__setClassLock = False
340     raise ValueError
341 jonathan 492
342 jonathan 545 self.changed(LAYER_CHANGED, self)
343 jonathan 412
344 jonathan 529 self.__setClassLock = False
345    
346 jonathan 492 def ClassChanged(self):
347     """Called from the classification object when it has changed."""
348 jonathan 558 self.changed(LAYER_CHANGED, self)
349 jonathan 492
350 bh 217 def TreeInfo(self):
351     items = []
352    
353 jan 1012 items.append(_("Filename: %s") % self.filename)
354    
355 bh 217 if self.Visible():
356 jan 374 items.append(_("Shown"))
357 bh 217 else:
358 jan 374 items.append(_("Hidden"))
359     items.append(_("Shapes: %d") % self.NumShapes())
360 bh 217
361     bbox = self.LatLongBoundingBox()
362     if bbox is not None:
363 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
364 bh 217 else:
365 jan 374 items.append(_("Extent (lat-lon):"))
366     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
367 bh 217
368 jonathan 736 if self.projection and len(self.projection.params) > 0:
369     items.append((_("Projection"),
370     [str(param) for param in self.projection.params]))
371    
372 jonathan 412 items.append(self.__classification)
373 bh 217
374 jan 374 return (_("Layer '%s'") % self.Title(), items)
375 jonathan 382
376 jonathan 736
377 jonathan 929 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 jonathan 961
391     Throws IOError if the filename is invalid or points to a file that
392     is not in a format GDAL can use.
393 jonathan 929 """
394    
395     BaseLayer.__init__(self, title, visible = visible)
396    
397     self.projection = projection
398     self.filename = filename
399    
400     self.bbox = -1
401    
402 jonathan 961 #
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 jonathan 929 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 jonathan 961 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 jonathan 929 self.bbox = (left, bottom, right, top)
447    
448     return self.bbox
449    
450     def LatLongBoundingBox(self):
451     bbox = self.BoundingBox()
452 jonathan 961 if bbox is None:
453 jonathan 929 return None
454    
455 jonathan 961 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 jonathan 929 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