/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Contents of /trunk/thuban/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1219 - (show 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 # 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 from math import log, ceil
12 import warnings
13
14 from Thuban import _
15 import shapelib, shptree
16
17 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED
19
20 import classification
21
22 from color import Color
23 from base import TitledObject, Modifiable
24
25 import resource
26
27
28 class Shape:
29
30 """Represent one shape"""
31
32 def __init__(self, points):
33 self.points = points
34 #self.compute_bbox()
35 self.bbox = None
36
37 def compute_bbox(self):
38 if self.bbox is not None:
39 return self.bbox
40
41 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 self.bbox = (self.llx, self.lly, self.urx, self.ury)
52
53 return self.bbox
54
55 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 def __init__(self, title, visible = True, projection = None):
79 """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 self.projection = projection
88
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
98 def HasClassification(self):
99 """Determine if this layer support classifications."""
100 return False
101
102 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 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 def __init__(self, title, data, projection = None,
128 fill = Color.Transparent,
129 stroke = Color.Black,
130 lineWidth = 1,
131 visible = True):
132 """Initialize the layer.
133
134 title -- the title
135 data -- datastore object for the shape data shown by the layer
136 projection -- the projection object. Its Inverse method is
137 assumed to map the layer's coordinates to lat/long
138 coordinates
139 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 visible -- boolean. If true the layer is visible.
144
145 colors are expected to be instances of Color class
146 """
147 BaseLayer.__init__(self, title,
148 visible = visible,
149 projection = projection)
150
151 #
152 # this is really important so that when the classification class
153 # tries to set its parent layer the variable will exist
154 #
155 self.__classification = None
156 self.__setClassLock = False
157
158 self.SetShapeStore(data)
159
160 self.SetClassification(None)
161
162 self.__classification.SetDefaultLineColor(stroke)
163 self.__classification.SetDefaultLineWidth(lineWidth)
164 self.__classification.SetDefaultFill(fill)
165 self.__classification.SetLayer(self)
166
167 self.UnsetModified()
168
169 def __getattr__(self, attr):
170 """Access to some attributes for backwards compatibility
171
172 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 def SetShapeStore(self, store):
191 self.store = store
192 shapefile = self.store.Shapefile()
193
194 numshapes, shapetype, mins, maxs = shapefile.info()
195 self.numshapes = numshapes
196 self.shapetype = shapelib_shapetypes[shapetype]
197
198 # 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
205 # 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 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
215 maxdepth)
216 # 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 if self.__classification is not None:
222 fieldname = self.__classification.GetField()
223 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 self.SetClassification(None)
229 self.changed(LAYER_SHAPESTORE_REPLACED, self)
230
231 def ShapeStore(self):
232 return self.store
233
234 def Destroy(self):
235 BaseLayer.Destroy(self)
236 self.SetClassification(None)
237
238 def BoundingBox(self):
239 """Return the layer's bounding box in the intrinsic coordinate system.
240
241 If the layer has no shapes, return None.
242 """
243 return self.bbox
244
245 def LatLongBoundingBox(self):
246 """Return the layer's bounding box in lat/long coordinates.
247
248 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 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 def GetFieldType(self, fieldName):
293 table = self.store.Table()
294 if table.HasColumn(fieldName):
295 return table.Column(fieldName).type
296 return None
297
298 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 shape = self.store.Shapefile().read_object(index)
311
312 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 points.append((x, y))
320
321 return Shape(points)
322
323 def ShapesInRegion(self, box):
324 """Return the ids of the shapes that overlap the box.
325
326 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
327 """
328 left, bottom, right, top = box
329
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 return self.shapetree.find_shapes((left, bottom), (right, top))
335
336 def HasClassification(self):
337 return True
338
339 def GetClassification(self):
340 return self.__classification
341
342 def SetClassification(self, clazz):
343 """Set the classification to 'clazz'
344
345 If 'clazz' is None a default classification is created
346 """
347
348 # prevent infinite recursion when calling SetLayer()
349 if self.__setClassLock: return
350
351 self.__setClassLock = True
352
353 if clazz is None:
354 if self.__classification is not None:
355 self.__classification.SetLayer(None)
356 self.__classification = classification.Classification()
357 else:
358 self.__classification = clazz
359 try:
360 self.__classification.SetLayer(self)
361 except ValueError:
362 self.__setClassLock = False
363 raise ValueError
364
365 self.changed(LAYER_CHANGED, self)
366
367 self.__setClassLock = False
368
369 def ClassChanged(self):
370 """Called from the classification object when it has changed."""
371 self.changed(LAYER_CHANGED, self)
372
373 def TreeInfo(self):
374 items = []
375
376 if hasattr(self, 'filename'):
377 items.append(_("Filename: %s") % self.filename)
378
379 if self.Visible():
380 items.append(_("Shown"))
381 else:
382 items.append(_("Hidden"))
383 items.append(_("Shapes: %d") % self.NumShapes())
384
385 bbox = self.LatLongBoundingBox()
386 if bbox is not None:
387 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
388 else:
389 items.append(_("Extent (lat-lon):"))
390 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
391
392 if self.projection and len(self.projection.params) > 0:
393 items.append((_("Projection"),
394 [str(param) for param in self.projection.params]))
395
396 items.append(self.__classification)
397
398 return (_("Layer '%s'") % self.Title(), items)
399
400
401 if resource.has_gdal_support():
402 import gdal
403 from gdalconst import GA_ReadOnly
404
405 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
419 Throws IOError if the filename is invalid or points to a file that
420 is not in a format GDAL can use.
421 """
422
423 BaseLayer.__init__(self, title, visible = visible)
424
425 self.projection = projection
426 self.filename = filename
427
428 self.bbox = -1
429
430 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
436 if dataset is None:
437 raise IOError()
438
439 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 if not resource.has_gdal_support():
447 return None
448
449 if self.bbox == -1:
450 dataset = gdal.Open(self.filename, GA_ReadOnly)
451 if dataset is None:
452 self.bbox = None
453 else:
454 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 self.bbox = (left, bottom, right, top)
479
480 return self.bbox
481
482 def LatLongBoundingBox(self):
483 bbox = self.BoundingBox()
484 if bbox is None:
485 return None
486
487 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 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