/[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 1338 - (hide annotations)
Tue Jul 1 16:10:00 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 16272 byte(s)
Fixes RTbug #1971, 1973.
(Layer.Destroy): We don't need to call SetClassification
        anymore to clear out the back reference in the classifcation
        to the layer. It's better to set it to None using _set_layer,
        and we won't be creating another clas object too.
(Layer.SetClassification): Classification._set_layer is not
        recursive so remove all the locking variables. Also clean
        up the code so that it remains unchanged if something fails.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26