/[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 1273 - (show annotations)
Fri Jun 20 17:45:49 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 16135 byte(s)
(BaseLayer.HasShapes): New. Overridden
        by deriving classes to determine if that layer supports shapes.
(Layer): Override HasShapes and return true.

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 import warnings
13
14 from Thuban import _
15 import shapelib, shptree
16
17 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED
19
20 import classification
21
22 from color import Color
23 from base import TitledObject, Modifiable
24
25 import resource
26
27
28 class Shape:
29
30 """Represent one shape"""
31
32 def __init__(self, points):
33 self.points = points
34 #self.compute_bbox()
35 self.bbox = None
36
37 def compute_bbox(self):
38 if self.bbox is not None:
39 return self.bbox
40
41 xs = []
42 ys = []
43 for x, y in self.points:
44 xs.append(x)
45 ys.append(y)
46 self.llx = min(xs)
47 self.lly = min(ys)
48 self.urx = max(xs)
49 self.ury = max(ys)
50
51 self.bbox = (self.llx, self.lly, self.urx, self.ury)
52
53 return self.bbox
54
55 def Points(self):
56 return self.points
57
58
59
60 # Shape type constants
61 SHAPETYPE_POLYGON = "polygon"
62 SHAPETYPE_ARC = "arc"
63 SHAPETYPE_POINT = "point"
64
65 # mapping from shapelib shapetype constants to our constants
66 shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,
67 shapelib.SHPT_ARC: SHAPETYPE_ARC,
68 shapelib.SHPT_POINT: SHAPETYPE_POINT}
69
70 shapetype_names = {SHAPETYPE_POINT: "Point",
71 SHAPETYPE_ARC: "Arc",
72 SHAPETYPE_POLYGON: "Polygon"}
73
74 class BaseLayer(TitledObject, Modifiable):
75
76 """Base class for the layers."""
77
78 def __init__(self, title, visible = True, projection = None):
79 """Initialize the layer.
80
81 title -- the title
82 visible -- boolean. If true the layer is visible.
83 """
84 TitledObject.__init__(self, title)
85 Modifiable.__init__(self)
86 self.visible = visible
87 self.projection = projection
88
89 def Visible(self):
90 """Return true if layer is visible"""
91 return self.visible
92
93 def SetVisible(self, visible):
94 """Set the layer's visibility."""
95 self.visible = visible
96 self.issue(LAYER_VISIBILITY_CHANGED, self)
97
98 def HasClassification(self):
99 """Determine if this layer support classifications."""
100 return False
101
102 def HasShapes(self):
103 """Determine if this layer supports shapes."""
104 return False
105
106 def GetProjection(self):
107 """Return the layer's projection."""
108 return self.projection
109
110 def SetProjection(self, projection):
111 """Set the layer's projection"""
112 self.projection = projection
113 self.changed(LAYER_PROJECTION_CHANGED, self)
114
115 class Layer(BaseLayer):
116
117 """Represent the information of one geodata file (currently a shapefile)
118
119 All children of the layer have the same type.
120
121 A layer has fill and stroke colors. Colors should be instances of
122 Color. They can also be None, indicating no fill or no stroke.
123
124 The layer objects send the following events, all of which have the
125 layer object as parameter:
126
127 TITLE_CHANGED -- The title has changed.
128 LAYER_PROJECTION_CHANGED -- the projection has changed.
129 """
130
131 def __init__(self, title, data, projection = None,
132 fill = Color.Transparent,
133 stroke = Color.Black,
134 lineWidth = 1,
135 visible = True):
136 """Initialize the layer.
137
138 title -- the title
139 data -- datastore object for the shape data shown by the layer
140 projection -- the projection object. Its Inverse method is
141 assumed to map the layer's coordinates to lat/long
142 coordinates
143 fill -- the fill color or Color.Transparent if the shapes are
144 not filled
145 stroke -- the stroke color or Color.Transparent if the shapes
146 are not stroked
147 visible -- boolean. If true the layer is visible.
148
149 colors are expected to be instances of Color class
150 """
151 BaseLayer.__init__(self, title,
152 visible = visible,
153 projection = projection)
154
155 #
156 # this is really important so that when the classification class
157 # tries to set its parent layer the variable will exist
158 #
159 self.__classification = None
160 self.__setClassLock = False
161
162 self.SetShapeStore(data)
163
164 self.SetClassification(None)
165
166 self.__classification.SetDefaultLineColor(stroke)
167 self.__classification.SetDefaultLineWidth(lineWidth)
168 self.__classification.SetDefaultFill(fill)
169 self.__classification.SetLayer(self)
170
171 self.UnsetModified()
172
173 def __getattr__(self, attr):
174 """Access to some attributes for backwards compatibility
175
176 The attributes implemented here are now held by the shapestore
177 if at all. For backwards compatibility pretend that they are
178 still there but issue a DeprecationWarning when they are
179 accessed.
180 """
181 if attr in ("table", "shapetable"):
182 value = self.store.Table()
183 elif attr == "shapefile":
184 value = self.store.Shapefile()
185 elif attr == "filename":
186 value = self.store.FileName()
187 else:
188 raise AttributeError, attr
189 warnings.warn("The Layer attribute %r is deprecated."
190 " It's value can be accessed through the shapestore"
191 % attr, DeprecationWarning, stacklevel = 2)
192 return value
193
194 def SetShapeStore(self, store):
195 self.store = store
196 shapefile = self.store.Shapefile()
197
198 numshapes, shapetype, mins, maxs = shapefile.info()
199 self.numshapes = numshapes
200 self.shapetype = shapelib_shapetypes[shapetype]
201
202 # if there are shapes, set the bbox accordingly. Otherwise
203 # set it to None.
204 if self.numshapes:
205 self.bbox = mins[:2] + maxs[:2]
206 else:
207 self.bbox = None
208
209 # estimate a good depth for the quad tree. Each depth
210 # multiplies the number of nodes by four, therefore we
211 # basically take the base 4 logarithm of the number of
212 # shapes.
213 if self.numshapes < 4:
214 maxdepth = 1
215 else:
216 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
217
218 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
219 maxdepth)
220 # Set the classification to None if there is a classification
221 # and the new shapestore doesn't have a table with a suitable
222 # column, i.e one with the same name and type as before
223 # FIXME: Maybe we should keep it the same if the type is
224 # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
225 if self.__classification is not None:
226 fieldname = self.__classification.GetField()
227 fieldtype = self.__classification.GetFieldType()
228 table = self.store.Table()
229 if (fieldname is not None
230 and (not table.HasColumn(fieldname)
231 or table.Column(fieldname).type != fieldtype)):
232 self.SetClassification(None)
233 self.changed(LAYER_SHAPESTORE_REPLACED, self)
234
235 def ShapeStore(self):
236 return self.store
237
238 def Destroy(self):
239 BaseLayer.Destroy(self)
240 self.SetClassification(None)
241
242 def BoundingBox(self):
243 """Return the layer's bounding box in the intrinsic coordinate system.
244
245 If the layer has no shapes, return None.
246 """
247 return self.bbox
248
249 def LatLongBoundingBox(self):
250 """Return the layer's bounding box in lat/long coordinates.
251
252 Return None, if the layer doesn't contain any shapes.
253 """
254 bbox = self.BoundingBox()
255 if bbox is not None:
256 llx, lly, urx, ury = bbox
257 if self.projection is not None:
258 llx, lly = self.projection.Inverse(llx, lly)
259 urx, ury = self.projection.Inverse(urx, ury)
260 return llx, lly, urx, ury
261 else:
262 return None
263
264 def ShapesBoundingBox(self, shapes):
265 """Return a bounding box in lat/long coordinates for the given
266 list of shape ids.
267
268 If shapes is None or empty, return None.
269 """
270
271 if shapes is None or len(shapes) == 0: return None
272
273 llx = []
274 lly = []
275 urx = []
276 ury = []
277
278 if self.projection is not None:
279 inverse = lambda x, y: self.projection.Inverse(x, y)
280 else:
281 inverse = lambda x, y: (x, y)
282
283 for id in shapes:
284 left, bottom, right, top = self.Shape(id).compute_bbox()
285
286 left, bottom = inverse(left, bottom)
287 right, top = inverse(right, top)
288
289 llx.append(left)
290 lly.append(bottom)
291 urx.append(right)
292 ury.append(top)
293
294 return (min(llx), min(lly), max(urx), max(ury))
295
296 def GetFieldType(self, fieldName):
297 table = self.store.Table()
298 if table.HasColumn(fieldName):
299 return table.Column(fieldName).type
300 return None
301
302 def HasShapes(self):
303 return True
304
305 def NumShapes(self):
306 """Return the number of shapes in the layer"""
307 return self.numshapes
308
309 def ShapeType(self):
310 """Return the type of the shapes in the layer.
311 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
312 """
313 return self.shapetype
314
315 def Shape(self, index):
316 """Return the shape with index index"""
317 shape = self.store.Shapefile().read_object(index)
318
319 if self.shapetype == SHAPETYPE_POINT:
320 points = shape.vertices()
321 else:
322 #for poly in shape.vertices():
323 poly = shape.vertices()[0]
324 points = []
325 for x, y in poly:
326 points.append((x, y))
327
328 return Shape(points)
329
330 def ShapesInRegion(self, box):
331 """Return the ids of the shapes that overlap the box.
332
333 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
334 """
335 left, bottom, right, top = box
336
337 if self.projection is not None:
338 left, bottom = self.projection.Forward(left, bottom)
339 right, top = self.projection.Forward(right, top)
340
341 return self.shapetree.find_shapes((left, bottom), (right, top))
342
343 def HasClassification(self):
344 return True
345
346 def GetClassification(self):
347 return self.__classification
348
349 def SetClassification(self, clazz):
350 """Set the classification to 'clazz'
351
352 If 'clazz' is None a default classification is created
353 """
354
355 # prevent infinite recursion when calling SetLayer()
356 if self.__setClassLock: return
357
358 self.__setClassLock = True
359
360 if clazz is None:
361 if self.__classification is not None:
362 self.__classification.SetLayer(None)
363 self.__classification = classification.Classification()
364 else:
365 self.__classification = clazz
366 try:
367 self.__classification.SetLayer(self)
368 except ValueError:
369 self.__setClassLock = False
370 raise ValueError
371
372 self.changed(LAYER_CHANGED, self)
373
374 self.__setClassLock = False
375
376 def ClassChanged(self):
377 """Called from the classification object when it has changed."""
378 self.changed(LAYER_CHANGED, self)
379
380 def TreeInfo(self):
381 items = []
382
383 if hasattr(self, 'filename'):
384 items.append(_("Filename: %s") % self.filename)
385
386 if self.Visible():
387 items.append(_("Shown"))
388 else:
389 items.append(_("Hidden"))
390 items.append(_("Shapes: %d") % self.NumShapes())
391
392 bbox = self.LatLongBoundingBox()
393 if bbox is not None:
394 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
395 else:
396 items.append(_("Extent (lat-lon):"))
397 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
398
399 if self.projection and len(self.projection.params) > 0:
400 items.append((_("Projection"),
401 [str(param) for param in self.projection.params]))
402
403 items.append(self.__classification)
404
405 return (_("Layer '%s'") % self.Title(), items)
406
407
408 if resource.has_gdal_support():
409 import gdal
410 from gdalconst import GA_ReadOnly
411
412 class RasterLayer(BaseLayer):
413
414 def __init__(self, title, filename, projection = None, visible = True):
415 """Initialize the Raster Layer.
416
417 title -- title for the layer.
418
419 filename -- file name of the source image.
420
421 projection -- Projection object describing the projection which
422 the source image is in.
423
424 visible -- True is the layer should initially be visible.
425
426 Throws IOError if the filename is invalid or points to a file that
427 is not in a format GDAL can use.
428 """
429
430 BaseLayer.__init__(self, title, visible = visible)
431
432 self.projection = projection
433 self.filename = filename
434
435 self.bbox = -1
436
437 if resource.has_gdal_support():
438 #
439 # temporarily open the file so that GDAL can test if it's valid.
440 #
441 dataset = gdal.Open(self.filename, GA_ReadOnly)
442
443 if dataset is None:
444 raise IOError()
445
446 self.UnsetModified()
447
448 def BoundingBox(self):
449 """Return the layer's bounding box in the intrinsic coordinate system.
450
451 If the layer has no shapes, return None.
452 """
453 if not resource.has_gdal_support():
454 return None
455
456 if self.bbox == -1:
457 dataset = gdal.Open(self.filename, GA_ReadOnly)
458 if dataset is None:
459 self.bbox = None
460 else:
461 geotransform = dataset.GetGeoTransform()
462 if geotransform is None:
463 return None
464
465 x = 0
466 y = dataset.RasterYSize
467 left = geotransform[0] + \
468 geotransform[1] * x + \
469 geotransform[2] * y
470
471 bottom = geotransform[3] + \
472 geotransform[4] * x + \
473 geotransform[5] * y
474
475 x = dataset.RasterXSize
476 y = 0
477 right = geotransform[0] + \
478 geotransform[1] * x + \
479 geotransform[2] * y
480
481 top = geotransform[3] + \
482 geotransform[4] * x + \
483 geotransform[5] * y
484
485 self.bbox = (left, bottom, right, top)
486
487 return self.bbox
488
489 def LatLongBoundingBox(self):
490 bbox = self.BoundingBox()
491 if bbox is None:
492 return None
493
494 llx, lly, urx, ury = bbox
495 if self.projection is not None:
496 llx, lly = self.projection.Inverse(llx, lly)
497 urx, ury = self.projection.Inverse(urx, ury)
498
499 return llx, lly, urx, ury
500
501 def GetImageFilename(self):
502 return self.filename
503
504 def TreeInfo(self):
505 items = []
506
507 if self.Visible():
508 items.append(_("Shown"))
509 else:
510 items.append(_("Hidden"))
511 items.append(_("Shapes: %d") % self.NumShapes())
512
513 bbox = self.LatLongBoundingBox()
514 if bbox is not None:
515 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
516 else:
517 items.append(_("Extent (lat-lon):"))
518
519 if self.projection and len(self.projection.params) > 0:
520 items.append((_("Projection"),
521 [str(param) for param in self.projection.params]))
522
523 return (_("Layer '%s'") % self.Title(), items)
524

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26