/[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 492 - (show annotations)
Mon Mar 10 10:44:57 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 9698 byte(s)
 Added ClassChanged() so that the
        classification can tell the layer when its data has changed.
        (Layer.SetClassification): Accepts None as an arguement to
        remove the current classification and correctly handles
        adding a new classification.

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
151
152 self.SetClassification(None)
153
154 self.__classification.SetDefaultLineColor(stroke)
155 self.__classification.SetDefaultLineWidth(lineWidth)
156 self.__classification.SetDefaultFill(fill)
157 self.__classification.SetLayer(self)
158
159 self.UnsetModified()
160
161 def open_shapefile(self):
162 if self.shapefile is None:
163 self.shapefile = shapelib.ShapeFile(self.filename)
164 numshapes, shapetype, mins, maxs = self.shapefile.info()
165 self.numshapes = numshapes
166 self.shapetype = shapelib_shapetypes[shapetype]
167
168 # if there are shapes, set the bbox accordinly. Otherwise
169 # set it to None.
170 if self.numshapes:
171 self.bbox = mins[:2] + maxs[:2]
172 else:
173 self.bbox = None
174
175 # estimate a good depth for the quad tree. Each depth
176 # multiplies the number of nodes by four, therefore we
177 # basically take the base 4 logarithm of the number of
178 # shapes.
179 if self.numshapes < 4:
180 maxdepth = 1
181 else:
182 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
183
184 self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
185 maxdepth)
186
187 def Destroy(self):
188 BaseLayer.Destroy(self)
189 if self.shapefile is not None:
190 self.shapefile.close()
191 self.shapefile = None
192 self.shapetree = None
193 self.table.Destroy()
194
195 def BoundingBox(self):
196 """Return the layer's bounding box in the intrinsic coordinate system.
197
198 If the layer has no shapes, return None.
199 """
200 # The bbox will be set by open_shapefile just as we need it
201 # here.
202 self.open_shapefile()
203 return self.bbox
204
205 def LatLongBoundingBox(self):
206 """Return the layer's bounding box in lat/long coordinates.
207
208 Return None, if the layer doesn't contain any shapes.
209 """
210 bbox = self.BoundingBox()
211 if bbox is not None:
212 llx, lly, urx, ury = bbox
213 if self.projection is not None:
214 llx, lly = self.projection.Inverse(llx, lly)
215 urx, ury = self.projection.Inverse(urx, ury)
216 return llx, lly, urx, ury
217 else:
218 return None
219
220 def GetFieldType(self, fieldName):
221 self.open_shapefile()
222 info = self.table.field_info_by_name(fieldName)
223 if info is not None:
224 return info[0]
225 else:
226 return None
227
228 def NumShapes(self):
229 """Return the number of shapes in the layer"""
230 self.open_shapefile()
231 return self.numshapes
232
233 def ShapeType(self):
234 """Return the type of the shapes in the layer.
235 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
236 """
237 self.open_shapefile()
238 return self.shapetype
239
240 def Shape(self, index):
241 """Return the shape with index index"""
242 self.open_shapefile()
243 shape = self.shapefile.read_object(index)
244
245 if self.shapetype == SHAPETYPE_POINT:
246 points = shape.vertices()
247 else:
248 #for poly in shape.vertices():
249 poly = shape.vertices()[0]
250 points = []
251 for x, y in poly:
252 points.append((x, y))
253
254 return Shape(points)
255
256 def ShapesInRegion(self, box):
257 """Return the ids of the shapes that overlap the box.
258
259 Box is a tuple (left, bottom, right, top) in the coordinate
260 system used by the layer's shapefile.
261 """
262 left, bottom, right, top = box
263 return self.shapetree.find_shapes((left, bottom), (right, top))
264
265 def SetProjection(self, projection):
266 """Set the layer's projection"""
267 self.projection = projection
268 self.changed(LAYER_PROJECTION_CHANGED, self)
269
270 def GetClassification(self):
271 return self.__classification
272
273 def SetClassification(self, clazz):
274 """Set the classification to 'clazz'
275
276 If 'clazz' is None a default classification is created
277 """
278
279 if clazz is None:
280 self.__classification = classification.Classification()
281 else:
282
283 # prevent infinite recursion when calling SetLayer()
284 if clazz == self.__classification:
285 return
286
287 self.__classification = clazz
288 self.__classification.SetLayer(self)
289
290 self.changed(LAYER_LEGEND_CHANGED, self)
291
292 def ClassChanged(self):
293 """Called from the classification object when it has changed."""
294 self.changed(LAYER_LEGEND_CHANGED, self)
295
296 def TreeInfo(self):
297 items = []
298
299 if self.Visible():
300 items.append(_("Shown"))
301 else:
302 items.append(_("Hidden"))
303 items.append(_("Shapes: %d") % self.NumShapes())
304
305 bbox = self.LatLongBoundingBox()
306 if bbox is not None:
307 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
308 else:
309 items.append(_("Extent (lat-lon):"))
310 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
311
312 items.append(self.__classification)
313
314 return (_("Layer '%s'") % self.Title(), items)
315

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26