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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1593 - (show 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 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jonathan Coles <[email protected]>
5 #
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 import warnings
12
13 from Thuban import _
14
15 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
16 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
17
18 import classification
19
20 from color import Transparent, Black
21 from base import TitledObject, Modifiable
22 from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
23
24 import resource
25
26
27 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 def __init__(self, title, visible = True, projection = None):
36 """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 self.projection = projection
45
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
55 def HasClassification(self):
56 """Determine if this layer support classifications."""
57 return False
58
59 def HasShapes(self):
60 """Determine if this layer supports shapes."""
61 return False
62
63 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 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 Color. They can also be Transparent, indicating no fill or no stroke.
80
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 def __init__(self, title, data, projection = None,
89 fill = Transparent,
90 stroke = Black,
91 lineWidth = 1,
92 visible = True):
93 """Initialize the layer.
94
95 title -- the title
96 data -- datastore object for the shape data shown by the layer
97 projection -- the projection object. Its Inverse method is
98 assumed to map the layer's coordinates to lat/long
99 coordinates
100 fill -- the fill color or Transparent if the shapes are
101 not filled
102 stroke -- the stroke color or Transparent if the shapes
103 are not stroked
104 visible -- boolean. If true the layer is visible.
105
106 colors are expected to be instances of Color class
107 """
108 BaseLayer.__init__(self, title,
109 visible = visible,
110 projection = projection)
111
112 self.__classification = None
113 self.store = None
114
115 self.SetShapeStore(data)
116
117 self.classification_column = None
118 self.SetClassificationColumn(None)
119 self.SetClassification(None)
120
121 self.__classification.SetDefaultLineColor(stroke)
122 self.__classification.SetDefaultLineWidth(lineWidth)
123 self.__classification.SetDefaultFill(fill)
124
125 self.UnsetModified()
126
127 def __getattr__(self, attr):
128 """Access to some attributes for backwards compatibility
129
130 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 def SetShapeStore(self, store):
149 # 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 columnname = self.classification_column
156 columntype = self.GetFieldType(columnname)
157 table = store.Table()
158 if (columnname is not None
159 and (not table.HasColumn(columnname)
160 or table.Column(columnname).type != columntype)):
161 self.SetClassification(None)
162
163 self.store = store
164
165 self.changed(LAYER_SHAPESTORE_REPLACED, self)
166
167 def ShapeStore(self):
168 return self.store
169
170 def Destroy(self):
171 BaseLayer.Destroy(self)
172 if self.__classification is not None:
173 self.__classification.Unsubscribe(CLASS_CHANGED,
174 self._classification_changed)
175
176 def BoundingBox(self):
177 """Return the layer's bounding box in the intrinsic coordinate system.
178
179 If the layer has no shapes, return None.
180 """
181 return self.store.BoundingBox()
182
183 def LatLongBoundingBox(self):
184 """Return the layer's bounding box in lat/long coordinates.
185
186 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 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 def GetFieldType(self, fieldName):
231 if self.store:
232 table = self.store.Table()
233 if table.HasColumn(fieldName):
234 return table.Column(fieldName).type
235 return None
236
237 def HasShapes(self):
238 return True
239
240 def NumShapes(self):
241 """Return the number of shapes in the layer"""
242 return self.store.NumShapes()
243
244 def ShapeType(self):
245 """Return the type of the shapes in the layer.
246
247 The return value is one of the SHAPETYPE_* constants defined in
248 Thuban.Model.data.
249 """
250 return self.store.ShapeType()
251
252 def Shape(self, index):
253 """Return the shape with index index"""
254 return self.store.Shape(index)
255
256 def ShapesInRegion(self, bbox):
257 """Return an iterable over the shapes that overlap the bounding box.
258
259 The bbox parameter should be the bounding box as a tuple in the
260 form (minx, miny, maxx, maxy) in unprojected coordinates.
261 """
262 if self.projection is not None:
263 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
272 return self.store.ShapesInRegion(bbox)
273
274 def GetClassificationColumn(self):
275 return self.classification_column
276
277 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 """
281 if column:
282 columnType = self.GetFieldType(column)
283 if columnType is None:
284 raise ValueError()
285 changed = self.classification_column != column
286 self.classification_column = column
287 if changed:
288 self.changed(LAYER_CHANGED, self)
289
290 def HasClassification(self):
291 return True
292
293 def GetClassification(self):
294 return self.__classification
295
296 def SetClassification(self, clazz):
297 """Set the classification used by this layer to 'clazz'
298
299 If 'clazz' is None a default classification is created.
300
301 This issues a LAYER_CHANGED event.
302 """
303
304 if self.__classification is not None:
305 self.__classification.Unsubscribe(CLASS_CHANGED,
306 self._classification_changed)
307
308 if clazz is None:
309 clazz = classification.Classification()
310
311 self.__classification = clazz
312 self.__classification.Subscribe(CLASS_CHANGED,
313 self._classification_changed)
314
315 self._classification_changed()
316
317 def _classification_changed(self):
318 """Called from the classification object when it has changed."""
319 self.changed(LAYER_CHANGED, self)
320
321 def TreeInfo(self):
322 items = []
323
324 items.append(_("Filename: %s") % self.ShapeStore().FileName())
325
326 if self.Visible():
327 items.append(_("Shown"))
328 else:
329 items.append(_("Hidden"))
330 items.append(_("Shapes: %d") % self.NumShapes())
331
332 bbox = self.LatLongBoundingBox()
333 if bbox is not None:
334 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
335 else:
336 items.append(_("Extent (lat-lon):"))
337 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
338
339 if self.projection and len(self.projection.params) > 0:
340 items.append((_("Projection"),
341 [str(param) for param in self.projection.params]))
342
343 items.append(self.__classification)
344
345 return (_("Layer '%s'") % self.Title(), items)
346
347
348 if resource.has_gdal_support():
349 import gdal
350 from gdalconst import GA_ReadOnly
351
352 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
366 Throws IOError if the filename is invalid or points to a file that
367 is not in a format GDAL can use.
368 """
369
370 BaseLayer.__init__(self, title, visible = visible)
371
372 self.projection = projection
373 self.filename = filename
374
375 self.bbox = -1
376
377 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
383 if dataset is None:
384 raise IOError()
385
386 self.UnsetModified()
387
388 def BoundingBox(self):
389 """Return the layer's bounding box in the intrinsic coordinate system.
390
391 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 """
394 if not resource.has_gdal_support():
395 return None
396
397 if self.bbox == -1:
398 dataset = gdal.Open(self.filename, GA_ReadOnly)
399 if dataset is None:
400 self.bbox = None
401 else:
402 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 self.bbox = (left, bottom, right, top)
427
428 return self.bbox
429
430 def LatLongBoundingBox(self):
431 bbox = self.BoundingBox()
432 if bbox is None:
433 return None
434
435 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 def GetImageFilename(self):
443 return self.filename
444
445 def TreeInfo(self):
446 items = []
447
448 items.append(_("Filename: %s") % self.GetImageFilename())
449
450 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