/[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 723 - (show annotations)
Thu Apr 24 15:31:53 2003 UTC (21 years, 10 months ago) by bh
File MIME type: text/x-python
File size: 9343 byte(s)
First step towards table management. Introduce a simple data
abstraction so that we replace the data a layer uses more easily
in the next step.

* Thuban/Model/data.py: New file with a simple data abstraction
that bundles shapefile and dbffile into one object.

* Thuban/Model/session.py (Session.OpenShapefile): New method to
open shapefiles and return a shape store object

* Thuban/Model/layer.py (Layer.__init__): Pass the data as a store
object instead of a shapefile filename. This introduces a new
instance variable store holding the datastore. For intermediate
backwards compatibility keep the old instance variables.
(open_shapefile): Removed. No longer needed with the shape store.
(Layer.SetShapeStore, Layer.ShapeStore): New methods to set and
get the shape store used by a layer.
(Layer.Destroy): No need to explicitly destroy the shapefile or
table anymore.

* Thuban/UI/mainwindow.py (MainWindow.AddLayer)
(MainWindow.AddLayer): Use the session's OpenShapefile method to
open shapefiles

* Thuban/Model/load.py (ProcessSession.start_layer): Use the
session's OpenShapefile method to open shapefiles

* test/test_classification.py
(TestClassification.test_classification): Use the session's
OpenShapefile method to open shapefiles and build the filename in
a more platform independed way

* test/test_layer.py (TestLayer.setUp, TestLayer.tearDown):
Implement to have a session to use in the tests
(TestLayer.test_arc_layer, TestLayer.test_polygon_layer)
(TestLayer.test_point_layer, TestLayer.test_empty_layer): Use the
session's OpenShapefile method to open shapefiles
(TestLayerLegend.setUp): Instantiate a session so that we can use
it to open shapefiles.
(TestLayerLegend.tearDown): Make sure that all references to
layers and session are removed otherwise we may get a resource
leak

* test/test_map.py (TestMapAddLayer.test_add_layer)
(TestMapWithContents.setUp): Instantiate a session so that we can
use it to open shapefiles.
(TestMapWithContents.tearDown): Make sure that all references to
layers, maps and sessions are removed otherwise we may get a
resource leak
("__main__"): use support.run_tests() so that more info about
uncollected garbage is printed

* test/test_save.py (SaveSessionTest.testSingleLayer): Use the
session's OpenShapefile method to open shapefiles
("__main__"): use support.run_tests() so that more info about
uncollected garbage is printed

* test/test_selection.py (TestSelection.tearDown): Make sure that
all references to the session and the selection are removed
otherwise we may get a resource leak
(TestSelection.get_layer): Instantiate a session so that we can
use it to open shapefiles.
("__main__"): use support.run_tests() so that more info about
uncollected garbage is printed

* test/test_session.py (TestSessionBase.tearDown)
(TestSessionWithContent.tearDown): Make sure that all references
to the session and layers are removed otherwise we may get a
resource leak
(TestSessionWithContent.setUp): Use the session's OpenShapefile
method to open shapefiles

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26