/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1593 - (hide annotations)
Fri Aug 15 14:10:27 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/layer.py
File MIME type: text/x-python
File size: 14906 byte(s)
Change the way shapes are returned by a shape store. The
ShapesInRegion method returns an iterator over actual shape
objects instead of a list of shape ids.

* Thuban/Model/data.py (ShapefileShape.ShapeID): New. Return shape
id.
(ShapefileStore.ShapesInRegion): Return an iterator over the
shapes which yields shape objects instead of returning a list of
shape ids
(ShapefileStore.AllShapes): New. Return an iterator over all
shapes in the shape store
(DerivedShapeStore.AllShapes): New. Like in ShapefileStore

* Thuban/Model/layer.py (Layer.ShapesInRegion): Update
doc-string.

* Thuban/UI/baserenderer.py
(BaseRenderer.layer_ids, BaseRenderer.layer_shapes): Rename to
layer_shapes and make it return an iterator containg shapes
instead of a list of ids.
(BaseRenderer.draw_shape_layer): Update doc-string; Adapt to
layer_shapes() change

* Thuban/UI/renderer.py (ScreenRenderer.layer_ids)
(ScreenRenderer.layer_shapes): Rename as in BaseRenderer

* Thuban/UI/viewport.py (ViewPort._find_shape_in_layer): Adapt to
changes in the ShapesInRegion return value.
(ViewPort._get_hit_tester): Remove commented out code

* test/mockgeo.py (SimpleShapeStore.ShapesInRegion): Adapt to the
new return value.
(SimpleShapeStore.AllShapes): New. Implement this method too.

* test/test_layer.py (TestLayer.test_arc_layer)
(TestLayer.test_polygon_layer, TestLayer.test_point_layer)
(TestLayer.test_point_layer_with_projection)
(TestLayer.test_derived_store): Adapt to changes in the
ShapesInRegion return value.

* test/test_shapefilestore.py
(TestShapefileStoreArc.test_shapes_in_region)
(TestShapefileStorePolygon.test_shapes_in_region)
(TestShapefileStorePoint.test_shapes_in_region): Adapt to changes
in the ShapesInRegion return value.
(TestShapefileStorePoint.test_all_shapes)
(TestShapefileStoreArc.test_shape_shapeid): New tests for the new
methods

* test/test_derivedshapestore.py
(TestDerivedShapeStore.test_shapes_in_region): Adapt to changes in
the ShapesInRegion return value.
(TestDerivedShapeStore.test_all_shapes)
(TestDerivedShapeStore.test_shape_shapeid): New tests for the new
methods

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26