/[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 839 - (hide annotations)
Tue May 6 15:54:18 2003 UTC (21 years, 10 months ago) by bh
File MIME type: text/x-python
File size: 10770 byte(s)
Convert all table users to use the new table interface. This only
covers Thuban itself, not GREAT-ER or other applications built on
Thuban yet, so the compatibility interface stays in place for the
time being but it now issues DeprecationWarnings.

Finally, the new Table interface has a new method, HasColumn.

* Thuban/Model/table.py (OldTableInterfaceMixin): All methods
issue deprecation warnings when they're. The warnings refer to the
caller of the method.
(OldTableInterfaceMixin.__deprecation_warning): New. Helper method
for the deprecation warnings

* test/test_table.py: Ignore the deprecation warnings for the old
table in the tests in this module. The purpose of the tests is to
test the old interface, after all.

* test/test_transientdb.py
(TestTransientTable.run_iceland_political_tests): Use the
constants for the types. Add a test for HasColumn
(TestTransientTable.test_transient_joined_table): Adapt to new
table interface. Add a test for HasColumn
(TestTransientTable.test_transient_table_read_twice): Adapt to new
table interface

* Thuban/Model/transientdb.py (TransientTableBase.HasColumn)
(AutoTransientTable.HasColumn): Implement the new table interface
method
(AutoTransientTable.ReadRowAsDict, AutoTransientTable.ValueRange)
(AutoTransientTable.UniqueValues): Adapt to new table interface

* Thuban/Model/layer.py (Layer.SetShapeStore, Layer.GetFieldType):
Adapt to new table interface

* test/test_layer.py (TestLayer.open_shapefile): Helper method to
simplify opening shapefiles a bit easier.
(TestLayer.test_arc_layer, TestLayer.test_polygon_layer)
(TestLayer.test_point_layer): Use the new helper method
(TestLayer.test_get_field_type): New. Test for the GetFieldType
method

* test/test_dbf_table.py (TestDBFTable.test_has_column): Test for
the new table method

* test/test_memory_table.py (TestMemoryTable.test_has_column):
Test for the new table method HasColumn

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    
13 jan 374 from Thuban import _
14    
15 bh 143 import shapelib, shptree
16 bh 6
17 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18     LAYER_CHANGED
19 bh 6
20     from color import Color
21    
22 jonathan 437 import classification
23 bh 6
24     from base import TitledObject, Modifiable
25    
26 bh 723
27 bh 6 class Shape:
28    
29     """Represent one shape"""
30    
31     def __init__(self, points):
32     self.points = points
33     #self.compute_bbox()
34 jonathan 828 self.bbox = None
35 bh 6
36     def compute_bbox(self):
37 jonathan 828 if self.bbox is not None:
38     return self.bbox
39    
40 bh 6 xs = []
41     ys = []
42     for x, y in self.points:
43     xs.append(x)
44     ys.append(y)
45     self.llx = min(xs)
46     self.lly = min(ys)
47     self.urx = max(xs)
48     self.ury = max(ys)
49    
50 jonathan 828 self.bbox = (self.llx, self.lly, self.urx, self.ury)
51    
52     return self.bbox
53    
54 bh 6 def Points(self):
55     return self.points
56    
57    
58    
59     # Shape type constants
60     SHAPETYPE_POLYGON = "polygon"
61     SHAPETYPE_ARC = "arc"
62     SHAPETYPE_POINT = "point"
63    
64     # mapping from shapelib shapetype constants to our constants
65     shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,
66     shapelib.SHPT_ARC: SHAPETYPE_ARC,
67     shapelib.SHPT_POINT: SHAPETYPE_POINT}
68    
69     shapetype_names = {SHAPETYPE_POINT: "Point",
70     SHAPETYPE_ARC: "Arc",
71     SHAPETYPE_POLYGON: "Polygon"}
72    
73     class BaseLayer(TitledObject, Modifiable):
74    
75     """Base class for the layers."""
76    
77 jonathan 771 def __init__(self, title, visible = True):
78 bh 6 """Initialize the layer.
79    
80     title -- the title
81     visible -- boolean. If true the layer is visible.
82     """
83     TitledObject.__init__(self, title)
84     Modifiable.__init__(self)
85     self.visible = visible
86    
87     def Visible(self):
88     """Return true if layer is visible"""
89     return self.visible
90    
91     def SetVisible(self, visible):
92     """Set the layer's visibility."""
93     self.visible = visible
94     self.issue(LAYER_VISIBILITY_CHANGED, self)
95 bh 276
96    
97 bh 6 class Layer(BaseLayer):
98    
99     """Represent the information of one geodata file (currently a shapefile)
100    
101     All children of the layer have the same type.
102    
103     A layer has fill and stroke colors. Colors should be instances of
104     Color. They can also be None, indicating no fill or no stroke.
105    
106     The layer objects send the following events, all of which have the
107     layer object as parameter:
108    
109     TITLE_CHANGED -- The title has changed.
110     LAYER_PROJECTION_CHANGED -- the projection has changed.
111     """
112    
113 bh 723 def __init__(self, title, data, projection = None,
114 jonathan 610 fill = Color.Transparent,
115 jonathan 412 stroke = Color.Black,
116 jonathan 464 lineWidth = 1,
117 jonathan 771 visible = True):
118 bh 6 """Initialize the layer.
119    
120     title -- the title
121 bh 723 data -- datastore object for the shape data shown by the layer
122 bh 6 projection -- the projection object. Its Inverse method is
123     assumed to map the layer's coordinates to lat/long
124     coordinates
125 jonathan 610 fill -- the fill color or Color.Transparent if the shapes are
126     not filled
127     stroke -- the stroke color or Color.Transparent if the shapes
128     are not stroked
129 bh 6 visible -- boolean. If true the layer is visible.
130    
131     colors are expected to be instances of Color class
132     """
133     BaseLayer.__init__(self, title, visible = visible)
134 bh 276
135 bh 6 self.projection = projection
136    
137 jonathan 481 #
138     # this is really important so that when the classification class
139     # tries to set its parent layer the variable will exist
140     #
141 bh 723 self.__classification = None
142 jonathan 529 self.__setClassLock = False
143 jonathan 481
144 bh 723 self.SetShapeStore(data)
145 jonathan 492
146     self.SetClassification(None)
147    
148 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
149     self.__classification.SetDefaultLineWidth(lineWidth)
150 jonathan 412 self.__classification.SetDefaultFill(fill)
151 jonathan 492 self.__classification.SetLayer(self)
152 jonathan 364
153 jonathan 389 self.UnsetModified()
154    
155 bh 6
156 bh 723 def SetShapeStore(self, store):
157     self.store = store
158     self.shapefile = self.store.Shapefile()
159     self.shapetable = self.store.Table()
160     self.filename = self.store.filename
161     self.table = self.shapetable
162 bh 179
163 bh 723 numshapes, shapetype, mins, maxs = self.shapefile.info()
164     self.numshapes = numshapes
165     self.shapetype = shapelib_shapetypes[shapetype]
166 bh 171
167 bh 723 # if there are shapes, set the bbox accordingly. Otherwise
168     # set it to None.
169     if self.numshapes:
170     self.bbox = mins[:2] + maxs[:2]
171     else:
172     self.bbox = None
173 bh 171
174 bh 723 # estimate a good depth for the quad tree. Each depth
175     # multiplies the number of nodes by four, therefore we
176     # basically take the base 4 logarithm of the number of
177     # shapes.
178     if self.numshapes < 4:
179     maxdepth = 1
180     else:
181     maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
182    
183     self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
184     maxdepth)
185     if self.__classification is not None:
186     fieldname = self.__classification.GetField()
187 jan 791 if fieldname is not None and \
188 bh 839 not self.store.Table().HasColumn(fieldname):
189 bh 723 self.SetClassification(None)
190     self.changed(LAYER_CHANGED, self)
191    
192     def ShapeStore(self):
193     return self.store
194    
195 bh 258 def Destroy(self):
196 bh 260 BaseLayer.Destroy(self)
197 jonathan 529 self.SetClassification(None)
198 bh 258
199 bh 6 def BoundingBox(self):
200 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
201    
202     If the layer has no shapes, return None.
203     """
204 bh 6 return self.bbox
205    
206     def LatLongBoundingBox(self):
207 bh 179 """Return the layer's bounding box in lat/long coordinates.
208 bh 6
209 bh 179 Return None, if the layer doesn't contain any shapes.
210     """
211     bbox = self.BoundingBox()
212     if bbox is not None:
213     llx, lly, urx, ury = bbox
214     if self.projection is not None:
215     llx, lly = self.projection.Inverse(llx, lly)
216     urx, ury = self.projection.Inverse(urx, ury)
217     return llx, lly, urx, ury
218     else:
219     return None
220    
221 jonathan 828 def ShapesBoundingBox(self, shapes):
222     """Return a bounding box in lat/long coordinates for the given
223     list of shape ids.
224    
225     If shapes is None or empty, return None.
226     """
227    
228     if shapes is None or len(shapes) == 0: return None
229    
230     llx = []
231     lly = []
232     urx = []
233     ury = []
234    
235     if self.projection is not None:
236     inverse = lambda x, y: self.projection.Inverse(x, y)
237     else:
238     inverse = lambda x, y: (x, y)
239    
240     for id in shapes:
241     left, bottom, right, top = self.Shape(id).compute_bbox()
242    
243     left, bottom = inverse(left, bottom)
244     right, top = inverse(right, top)
245    
246     llx.append(left)
247     lly.append(bottom)
248     urx.append(right)
249     ury.append(top)
250    
251     return (min(llx), min(lly), max(urx), max(ury))
252    
253 jonathan 464 def GetFieldType(self, fieldName):
254 bh 839 if self.table.HasColumn(fieldName):
255     return self.table.Column(fieldName).type
256     return None
257 jonathan 464
258 bh 6 def NumShapes(self):
259     """Return the number of shapes in the layer"""
260     return self.numshapes
261    
262     def ShapeType(self):
263     """Return the type of the shapes in the layer.
264     This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
265     """
266     return self.shapetype
267    
268     def Shape(self, index):
269     """Return the shape with index index"""
270     shape = self.shapefile.read_object(index)
271 jonathan 364
272 bh 6 if self.shapetype == SHAPETYPE_POINT:
273     points = shape.vertices()
274     else:
275     #for poly in shape.vertices():
276     poly = shape.vertices()[0]
277     points = []
278     for x, y in poly:
279 bh 82 points.append((x, y))
280 jonathan 364
281 bh 6 return Shape(points)
282    
283 bh 143 def ShapesInRegion(self, box):
284     """Return the ids of the shapes that overlap the box.
285    
286 jonathan 794 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
287 bh 143 """
288     left, bottom, right, top = box
289 jonathan 794
290     if self.projection is not None:
291     left, bottom = self.projection.Forward(left, bottom)
292     right, top = self.projection.Forward(right, top)
293    
294 bh 147 return self.shapetree.find_shapes((left, bottom), (right, top))
295 bh 143
296 jonathan 725 def GetProjection(self):
297     return self.projection
298    
299 bh 6 def SetProjection(self, projection):
300     """Set the layer's projection"""
301     self.projection = projection
302     self.changed(LAYER_PROJECTION_CHANGED, self)
303    
304 jonathan 412 def GetClassification(self):
305     return self.__classification
306    
307     def SetClassification(self, clazz):
308 jonathan 492 """Set the classification to 'clazz'
309 jonathan 481
310 jonathan 492 If 'clazz' is None a default classification is created
311     """
312 jonathan 481
313 jonathan 529 # prevent infinite recursion when calling SetLayer()
314     if self.__setClassLock: return
315    
316     self.__setClassLock = True
317    
318 jonathan 492 if clazz is None:
319 jonathan 529 if self.__classification is not None:
320     self.__classification.SetLayer(None)
321 jonathan 492 self.__classification = classification.Classification()
322     else:
323     self.__classification = clazz
324 jonathan 529 try:
325     self.__classification.SetLayer(self)
326     except ValueError:
327     self.__setClassLock = False
328     raise ValueError
329 jonathan 492
330 jonathan 545 self.changed(LAYER_CHANGED, self)
331 jonathan 412
332 jonathan 529 self.__setClassLock = False
333    
334 jonathan 492 def ClassChanged(self):
335     """Called from the classification object when it has changed."""
336 jonathan 558 self.changed(LAYER_CHANGED, self)
337 jonathan 492
338 bh 217 def TreeInfo(self):
339     items = []
340    
341     if self.Visible():
342 jan 374 items.append(_("Shown"))
343 bh 217 else:
344 jan 374 items.append(_("Hidden"))
345     items.append(_("Shapes: %d") % self.NumShapes())
346 bh 217
347     bbox = self.LatLongBoundingBox()
348     if bbox is not None:
349 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
350 bh 217 else:
351 jan 374 items.append(_("Extent (lat-lon):"))
352     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
353 bh 217
354 jonathan 736 if self.projection and len(self.projection.params) > 0:
355     items.append((_("Projection"),
356     [str(param) for param in self.projection.params]))
357    
358 jonathan 412 items.append(self.__classification)
359 bh 217
360 jan 374 return (_("Layer '%s'") % self.Title(), items)
361 jonathan 382
362 jonathan 736

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26