/[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 1219 - (hide annotations)
Mon Jun 16 17:42:54 2003 UTC (21 years, 8 months ago) by bh
File MIME type: text/x-python
File size: 15987 byte(s)
Update to the layer interface: Direct access to the table,
shapetable, shapefile and filename attributes is now actively
deprecated by issuing deprecation warnings for all places where
this happens.

* Thuban/Model/layer.py (Layer.__getattr__): New. Implement access
to the instance variables table, shapetable, shapefile and
filename via __getattr__ so that we can issue a deprecation
warning.
(Layer.SetShapeStore): Don't set the deprecated instance variables
any more
(Layer.SetShapeStore): Don't use deprecated layer instance
variables
(Layer.Destroy): No need to explicitly remove the instance
variables any more
(Layer.GetFieldType, Layer.Shape): Don't use deprecated layer
instance variables

* Thuban/UI/classgen.py (ClassGenDialog.__init__)
(GenUniformPanel._OnRetrieve, GenUniquePanel._OnRetrieve)
(GenQuantilesPanel.GetList, GenQuantilesPanel.OnRetrieve): Don't
use deprecated layer instance variables

* Thuban/UI/classifier.py (Classifier.__init__): Don't use
deprecated layer instance variables

* Thuban/UI/identifyview.py (IdentifyListCtrl.selected_shape)
(IdentifyGridCtrl.selected_shape): Don't set the deprecated layer
instance variables

* Thuban/UI/tableview.py (LayerTableGrid.select_shapes): Don't use
deprecated layer instance variables

* Thuban/UI/mainwindow.py (MainWindow.LayerShowTable): Don't use
deprecated layer instance variables

* Thuban/Model/save.py (SessionSaver.write_layer): Don't use
deprecated layer instance variables

* Thuban/UI/renderer.py (MapRenderer.draw_shape_layer)
(MapRenderer.polygon_render_param): Don't use deprecated layer instance
variables

* test/runtests.py (main): Turn Thuban's deprecation warnings into
errors so that they're cought by the tests

* test/test_load.py (TestSingleLayer.test): Don't use deprecated
layer instance variables

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26