/[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 1452 - (hide annotations)
Fri Jul 18 12:57:59 2003 UTC (21 years, 7 months ago) by bh
File MIME type: text/x-python
File size: 16755 byte(s)
* Thuban/Model/layer.py (Layer.__init__): Rename
classificationField to classificatin_column and init it here so
that it can be used in SetClassificationColumn
(Layer.GetClassificationColumn, Layer.GetClassificationField):
Rename to GetClassificationColumn.
(Layer.SetClassificationColumn, Layer.SetClassificationField):
Rename to SetClassificationColumn and issue a LAYER_CHANGED
message if the column changes.
(Layer._classification_changed, Layer.ClassChanged): Rename to
_classification_changed. Update the callers.
(Layer.SetShapeStore): Further field->column renames.

* Thuban/Model/load.py (SessionLoader.start_classification)
(SessionLoader.start_clpoint): Updates because of
field->column method name changes in the Layer class

* Thuban/Model/save.py (SessionSaver.write_classification): Updates
because of field->column method name changes in the Layer class

* Thuban/UI/classifier.py (Classifier.__init__)
(Classifier._OnTry, Classifier._OnRevert): Updates because of
field->column method name changes in the Layer class

* Thuban/UI/renderer.py (MapRenderer.draw_shape_layer): Updates
because of field->column method name changes in the Layer class

* Thuban/UI/viewport.py (ViewPort.find_shape_at): Updates because
of field->column method name changes in the Layer class

* test/test_save.py (SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.testClassifiedLayer): Update because of
field->column method name changes in the Layer class

* test/test_layer.py (SetShapeStoreTests.setUp)
(SetShapeStoreTests.test_sanity): Update because of field->column
method name changes in the Layer class
(TestLayerModification.setUp): Subscribe to LAYER_CHANGED as well
(TestLayerModification.test_sanity)
(TestLayerModification.test_initial_settings): remove unsued code
and rename to test_sanity.
(TestLayerModification.test_set_classification): New test for
SetClassification and SetClassificationField.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26