/[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 529 - (show annotations)
Wed Mar 12 19:55:24 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 10039 byte(s)
Handle the cyclic references between
        a layer and its classification better, and be sure to disconnect
        the classification from the layer when the layer is destroyed
        so that we don't maintain a cyclic reference that may not be
        garbage collected.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26