/[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 961 - (hide annotations)
Wed May 21 17:23:25 2003 UTC (21 years, 9 months ago) by jonathan
File MIME type: text/x-python
File size: 14411 byte(s)
(RasterLayer.__init__): Test if the filename is a valid image file.
        Throw IOError otherwise.

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     self.filename = self.store.filename
177     self.table = self.shapetable
178 bh 179
179 bh 723 numshapes, shapetype, mins, maxs = self.shapefile.info()
180     self.numshapes = numshapes
181     self.shapetype = shapelib_shapetypes[shapetype]
182 bh 171
183 bh 723 # 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 bh 171
190 bh 723 # 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 jan 791 if fieldname is not None and \
204 bh 839 not self.store.Table().HasColumn(fieldname):
205 bh 723 self.SetClassification(None)
206     self.changed(LAYER_CHANGED, self)
207    
208     def ShapeStore(self):
209     return self.store
210    
211 bh 258 def Destroy(self):
212 bh 260 BaseLayer.Destroy(self)
213 jonathan 529 self.SetClassification(None)
214 bh 258
215 bh 6 def BoundingBox(self):
216 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
217    
218     If the layer has no shapes, return None.
219     """
220 bh 6 return self.bbox
221    
222     def LatLongBoundingBox(self):
223 bh 179 """Return the layer's bounding box in lat/long coordinates.
224 bh 6
225 bh 179 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 jonathan 828 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 jonathan 464 def GetFieldType(self, fieldName):
270 bh 839 if self.table.HasColumn(fieldName):
271     return self.table.Column(fieldName).type
272     return None
273 jonathan 464
274 bh 6 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 jonathan 364
288 bh 6 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 bh 82 points.append((x, y))
296 jonathan 364
297 bh 6 return Shape(points)
298    
299 bh 143 def ShapesInRegion(self, box):
300     """Return the ids of the shapes that overlap the box.
301    
302 jonathan 794 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
303 bh 143 """
304     left, bottom, right, top = box
305 jonathan 794
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 bh 147 return self.shapetree.find_shapes((left, bottom), (right, top))
311 bh 143
312 jonathan 929 def HasClassification(self):
313     return True
314 jonathan 725
315 jonathan 412 def GetClassification(self):
316     return self.__classification
317    
318     def SetClassification(self, clazz):
319 jonathan 492 """Set the classification to 'clazz'
320 jonathan 481
321 jonathan 492 If 'clazz' is None a default classification is created
322     """
323 jonathan 481
324 jonathan 529 # prevent infinite recursion when calling SetLayer()
325     if self.__setClassLock: return
326    
327     self.__setClassLock = True
328    
329 jonathan 492 if clazz is None:
330 jonathan 529 if self.__classification is not None:
331     self.__classification.SetLayer(None)
332 jonathan 492 self.__classification = classification.Classification()
333     else:
334     self.__classification = clazz
335 jonathan 529 try:
336     self.__classification.SetLayer(self)
337     except ValueError:
338     self.__setClassLock = False
339     raise ValueError
340 jonathan 492
341 jonathan 545 self.changed(LAYER_CHANGED, self)
342 jonathan 412
343 jonathan 529 self.__setClassLock = False
344    
345 jonathan 492 def ClassChanged(self):
346     """Called from the classification object when it has changed."""
347 jonathan 558 self.changed(LAYER_CHANGED, self)
348 jonathan 492
349 bh 217 def TreeInfo(self):
350     items = []
351    
352     if self.Visible():
353 jan 374 items.append(_("Shown"))
354 bh 217 else:
355 jan 374 items.append(_("Hidden"))
356     items.append(_("Shapes: %d") % self.NumShapes())
357 bh 217
358     bbox = self.LatLongBoundingBox()
359     if bbox is not None:
360 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
361 bh 217 else:
362 jan 374 items.append(_("Extent (lat-lon):"))
363     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
364 bh 217
365 jonathan 736 if self.projection and len(self.projection.params) > 0:
366     items.append((_("Projection"),
367     [str(param) for param in self.projection.params]))
368    
369 jonathan 412 items.append(self.__classification)
370 bh 217
371 jan 374 return (_("Layer '%s'") % self.Title(), items)
372 jonathan 382
373 jonathan 736
374 jonathan 929 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 jonathan 961
388     Throws IOError if the filename is invalid or points to a file that
389     is not in a format GDAL can use.
390 jonathan 929 """
391    
392     BaseLayer.__init__(self, title, visible = visible)
393    
394     self.projection = projection
395     self.filename = filename
396    
397     self.bbox = -1
398    
399 jonathan 961 #
400     # temporarily open the file so that GDAL can test if it's valid.
401     #
402     dataset = gdal.Open(self.filename, GA_ReadOnly)
403    
404     if dataset is None:
405     raise IOError()
406    
407 jonathan 929 self.UnsetModified()
408    
409     def BoundingBox(self):
410     """Return the layer's bounding box in the intrinsic coordinate system.
411    
412     If the layer has no shapes, return None.
413     """
414     if self.bbox == -1:
415     dataset = gdal.Open(self.filename, GA_ReadOnly)
416     if dataset is None:
417     self.bbox = None
418     else:
419 jonathan 961 geotransform = dataset.GetGeoTransform()
420     if geotransform is None:
421     return None
422    
423     x = 0
424     y = dataset.RasterYSize
425     left = geotransform[0] + \
426     geotransform[1] * x + \
427     geotransform[2] * y
428    
429     bottom = geotransform[3] + \
430     geotransform[4] * x + \
431     geotransform[5] * y
432    
433     x = dataset.RasterXSize
434     y = 0
435     right = geotransform[0] + \
436     geotransform[1] * x + \
437     geotransform[2] * y
438    
439     top = geotransform[3] + \
440     geotransform[4] * x + \
441     geotransform[5] * y
442    
443 jonathan 929 self.bbox = (left, bottom, right, top)
444    
445     return self.bbox
446    
447     def LatLongBoundingBox(self):
448     bbox = self.BoundingBox()
449 jonathan 961 if bbox is None:
450 jonathan 929 return None
451    
452 jonathan 961 llx, lly, urx, ury = bbox
453     if self.projection is not None:
454     llx, lly = self.projection.Inverse(llx, lly)
455     urx, ury = self.projection.Inverse(urx, ury)
456    
457     return llx, lly, urx, ury
458    
459 jonathan 929 def GetImageFilename(self):
460     return self.filename
461    
462     def TreeInfo(self):
463     items = []
464    
465     if self.Visible():
466     items.append(_("Shown"))
467     else:
468     items.append(_("Hidden"))
469     items.append(_("Shapes: %d") % self.NumShapes())
470    
471     bbox = self.LatLongBoundingBox()
472     if bbox is not None:
473     items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
474     else:
475     items.append(_("Extent (lat-lon):"))
476    
477     if self.projection and len(self.projection.params) > 0:
478     items.append((_("Projection"),
479     [str(param) for param in self.projection.params]))
480    
481     return (_("Layer '%s'") % self.Title(), items)
482    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26