/[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 828 - (show annotations)
Tue May 6 12:06:12 2003 UTC (21 years, 10 months ago) by jonathan
File MIME type: text/x-python
File size: 10812 byte(s)
(Shape): Since a Shape is immutable only
        calculate the bounding box once (the first time compute_bbox() is
        called).
(Layer.ShapesBoundingBox): New. Given a list of shape ids, return
        the bounding box for the shapes in lat/long coordinates.

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 self.bbox = None
35
36 def compute_bbox(self):
37 if self.bbox is not None:
38 return self.bbox
39
40 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 self.bbox = (self.llx, self.lly, self.urx, self.ury)
51
52 return self.bbox
53
54 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 def __init__(self, title, visible = True):
78 """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
96
97 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 def __init__(self, title, data, projection = None,
114 fill = Color.Transparent,
115 stroke = Color.Black,
116 lineWidth = 1,
117 visible = True):
118 """Initialize the layer.
119
120 title -- the title
121 data -- datastore object for the shape data shown by the layer
122 projection -- the projection object. Its Inverse method is
123 assumed to map the layer's coordinates to lat/long
124 coordinates
125 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 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
135 self.projection = projection
136
137 #
138 # this is really important so that when the classification class
139 # tries to set its parent layer the variable will exist
140 #
141 self.__classification = None
142 self.__setClassLock = False
143
144 self.SetShapeStore(data)
145
146 self.SetClassification(None)
147
148 self.__classification.SetDefaultLineColor(stroke)
149 self.__classification.SetDefaultLineWidth(lineWidth)
150 self.__classification.SetDefaultFill(fill)
151 self.__classification.SetLayer(self)
152
153 self.UnsetModified()
154
155
156 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
163 numshapes, shapetype, mins, maxs = self.shapefile.info()
164 self.numshapes = numshapes
165 self.shapetype = shapelib_shapetypes[shapetype]
166
167 # 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
174 # 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 if fieldname is not None and \
188 not self.store.Table().field_info_by_name(fieldname):
189 self.SetClassification(None)
190 self.changed(LAYER_CHANGED, self)
191
192 def ShapeStore(self):
193 return self.store
194
195 def Destroy(self):
196 BaseLayer.Destroy(self)
197 self.SetClassification(None)
198
199 def BoundingBox(self):
200 """Return the layer's bounding box in the intrinsic coordinate system.
201
202 If the layer has no shapes, return None.
203 """
204 return self.bbox
205
206 def LatLongBoundingBox(self):
207 """Return the layer's bounding box in lat/long coordinates.
208
209 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 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 def GetFieldType(self, fieldName):
254 info = self.table.field_info_by_name(fieldName)
255 if info is not None:
256 return info[0]
257 else:
258 return None
259
260 def NumShapes(self):
261 """Return the number of shapes in the layer"""
262 return self.numshapes
263
264 def ShapeType(self):
265 """Return the type of the shapes in the layer.
266 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
267 """
268 return self.shapetype
269
270 def Shape(self, index):
271 """Return the shape with index index"""
272 shape = self.shapefile.read_object(index)
273
274 if self.shapetype == SHAPETYPE_POINT:
275 points = shape.vertices()
276 else:
277 #for poly in shape.vertices():
278 poly = shape.vertices()[0]
279 points = []
280 for x, y in poly:
281 points.append((x, y))
282
283 return Shape(points)
284
285 def ShapesInRegion(self, box):
286 """Return the ids of the shapes that overlap the box.
287
288 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
289 """
290 left, bottom, right, top = box
291
292 if self.projection is not None:
293 left, bottom = self.projection.Forward(left, bottom)
294 right, top = self.projection.Forward(right, top)
295
296 return self.shapetree.find_shapes((left, bottom), (right, top))
297
298 def GetProjection(self):
299 return self.projection
300
301 def SetProjection(self, projection):
302 """Set the layer's projection"""
303 self.projection = projection
304 self.changed(LAYER_PROJECTION_CHANGED, self)
305
306 def GetClassification(self):
307 return self.__classification
308
309 def SetClassification(self, clazz):
310 """Set the classification to 'clazz'
311
312 If 'clazz' is None a default classification is created
313 """
314
315 # prevent infinite recursion when calling SetLayer()
316 if self.__setClassLock: return
317
318 self.__setClassLock = True
319
320 if clazz is None:
321 if self.__classification is not None:
322 self.__classification.SetLayer(None)
323 self.__classification = classification.Classification()
324 else:
325 self.__classification = clazz
326 try:
327 self.__classification.SetLayer(self)
328 except ValueError:
329 self.__setClassLock = False
330 raise ValueError
331
332 self.changed(LAYER_CHANGED, self)
333
334 self.__setClassLock = False
335
336 def ClassChanged(self):
337 """Called from the classification object when it has changed."""
338 self.changed(LAYER_CHANGED, self)
339
340 def TreeInfo(self):
341 items = []
342
343 if self.Visible():
344 items.append(_("Shown"))
345 else:
346 items.append(_("Hidden"))
347 items.append(_("Shapes: %d") % self.NumShapes())
348
349 bbox = self.LatLongBoundingBox()
350 if bbox is not None:
351 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
352 else:
353 items.append(_("Extent (lat-lon):"))
354 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
355
356 if self.projection and len(self.projection.params) > 0:
357 items.append((_("Projection"),
358 [str(param) for param in self.projection.params]))
359
360 items.append(self.__classification)
361
362 return (_("Layer '%s'") % self.Title(), items)
363
364

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26