/[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 1338 - (show annotations)
Tue Jul 1 16:10:00 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 16272 byte(s)
Fixes RTbug #1971, 1973.
(Layer.Destroy): We don't need to call SetClassification
        anymore to clear out the back reference in the classifcation
        to the layer. It's better to set it to None using _set_layer,
        and we won't be creating another clas object too.
(Layer.SetClassification): Classification._set_layer is not
        recursive so remove all the locking variables. Also clean
        up the code so that it remains unchanged if something fails.

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 Transparent, Black
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 Transparent, 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 = Transparent,
133 stroke = 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 Transparent if the shapes are
144 not filled
145 stroke -- the stroke color or 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
161 self.SetShapeStore(data)
162
163 self.SetClassification(None)
164
165 self.__classification.SetDefaultLineColor(stroke)
166 self.__classification.SetDefaultLineWidth(lineWidth)
167 self.__classification.SetDefaultFill(fill)
168
169 self.UnsetModified()
170
171 def __getattr__(self, attr):
172 """Access to some attributes for backwards compatibility
173
174 The attributes implemented here are now held by the shapestore
175 if at all. For backwards compatibility pretend that they are
176 still there but issue a DeprecationWarning when they are
177 accessed.
178 """
179 if attr in ("table", "shapetable"):
180 value = self.store.Table()
181 elif attr == "shapefile":
182 value = self.store.Shapefile()
183 elif attr == "filename":
184 value = self.store.FileName()
185 else:
186 raise AttributeError, attr
187 warnings.warn("The Layer attribute %r is deprecated."
188 " It's value can be accessed through the shapestore"
189 % attr, DeprecationWarning, stacklevel = 2)
190 return value
191
192 def SetShapeStore(self, store):
193 self.store = store
194 shapefile = self.store.Shapefile()
195
196 numshapes, shapetype, mins, maxs = shapefile.info()
197 self.numshapes = numshapes
198 self.shapetype = shapelib_shapetypes[shapetype]
199
200 # if there are shapes, set the bbox accordingly. Otherwise
201 # set it to None.
202 if self.numshapes:
203 self.bbox = mins[:2] + maxs[:2]
204 else:
205 self.bbox = None
206
207 # estimate a good depth for the quad tree. Each depth
208 # multiplies the number of nodes by four, therefore we
209 # basically take the base 4 logarithm of the number of
210 # shapes.
211 if self.numshapes < 4:
212 maxdepth = 1
213 else:
214 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
215
216 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
217 maxdepth)
218 # Set the classification to None if there is a classification
219 # and the new shapestore doesn't have a table with a suitable
220 # column, i.e one with the same name and type as before
221 # FIXME: Maybe we should keep it the same if the type is
222 # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
223 if self.__classification is not None:
224 fieldname = self.__classification.GetField()
225 fieldtype = self.__classification.GetFieldType()
226 table = self.store.Table()
227 if (fieldname is not None
228 and (not table.HasColumn(fieldname)
229 or table.Column(fieldname).type != fieldtype)):
230 self.SetClassification(None)
231 self.changed(LAYER_SHAPESTORE_REPLACED, self)
232
233 def ShapeStore(self):
234 return self.store
235
236 def Destroy(self):
237 BaseLayer.Destroy(self)
238 self.GetClassification()._set_layer(None)
239
240 def BoundingBox(self):
241 """Return the layer's bounding box in the intrinsic coordinate system.
242
243 If the layer has no shapes, return None.
244 """
245 return self.bbox
246
247 def LatLongBoundingBox(self):
248 """Return the layer's bounding box in lat/long coordinates.
249
250 Return None, if the layer doesn't contain any shapes.
251 """
252 bbox = self.BoundingBox()
253 if bbox is not None:
254 llx, lly, urx, ury = bbox
255 if self.projection is not None:
256 llx, lly = self.projection.Inverse(llx, lly)
257 urx, ury = self.projection.Inverse(urx, ury)
258 return llx, lly, urx, ury
259 else:
260 return None
261
262 def ShapesBoundingBox(self, shapes):
263 """Return a bounding box in lat/long coordinates for the given
264 list of shape ids.
265
266 If shapes is None or empty, return None.
267 """
268
269 if shapes is None or len(shapes) == 0: return None
270
271 llx = []
272 lly = []
273 urx = []
274 ury = []
275
276 if self.projection is not None:
277 inverse = lambda x, y: self.projection.Inverse(x, y)
278 else:
279 inverse = lambda x, y: (x, y)
280
281 for id in shapes:
282 left, bottom, right, top = self.Shape(id).compute_bbox()
283
284 left, bottom = inverse(left, bottom)
285 right, top = inverse(right, top)
286
287 llx.append(left)
288 lly.append(bottom)
289 urx.append(right)
290 ury.append(top)
291
292 return (min(llx), min(lly), max(urx), max(ury))
293
294 def GetFieldType(self, fieldName):
295 table = self.store.Table()
296 if table.HasColumn(fieldName):
297 return table.Column(fieldName).type
298 return None
299
300 def HasShapes(self):
301 return True
302
303 def NumShapes(self):
304 """Return the number of shapes in the layer"""
305 return self.numshapes
306
307 def ShapeType(self):
308 """Return the type of the shapes in the layer.
309 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
310 """
311 return self.shapetype
312
313 def Shape(self, index):
314 """Return the shape with index index"""
315 shape = self.store.Shapefile().read_object(index)
316
317 if self.shapetype == SHAPETYPE_POINT:
318 points = shape.vertices()
319 else:
320 #for poly in shape.vertices():
321 poly = shape.vertices()[0]
322 points = []
323 for x, y in poly:
324 points.append((x, y))
325
326 return Shape(points)
327
328 def ShapesInRegion(self, box):
329 """Return the ids of the shapes that overlap the box.
330
331 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
332 """
333 left, bottom, right, top = box
334
335 if self.projection is not None:
336 left, bottom = self.projection.Forward(left, bottom)
337 right, top = self.projection.Forward(right, top)
338
339 return self.shapetree.find_shapes((left, bottom), (right, top))
340
341 def HasClassification(self):
342 return True
343
344 def GetClassification(self):
345 return self.__classification
346
347 def SetClassification(self, clazz):
348 """Set the classification used by this layer to 'clazz'
349
350 If 'clazz' is None a default classification is created.
351
352 ValueError is raised if the classification's field name
353 and type are different (if they aren't None) than what
354 is in the shapestore. The Layer will not be changed in
355 this case.
356 """
357
358 old_class = self.__classification
359
360 if clazz is None:
361 clazz = classification.Classification()
362
363 try:
364 clazz._set_layer(self)
365
366 # only change things after a successful call
367 if old_class is not None:
368 old_class._set_layer(None)
369 self.__classification = clazz
370 except ValueError:
371 raise ValueError
372
373 # we don't need this since a message will be sent
374 # after calling _set_layer()
375 #self.changed(LAYER_CHANGED, self)
376
377 def ClassChanged(self):
378 """Called from the classification object when it has changed."""
379 self.changed(LAYER_CHANGED, self)
380
381 def TreeInfo(self):
382 items = []
383
384 items.append(_("Filename: %s") % self.ShapeStore().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 there is no support for images, or the file cannot
452 be read, or there is no geographics information available, return None.
453 """
454 if not resource.has_gdal_support():
455 return None
456
457 if self.bbox == -1:
458 dataset = gdal.Open(self.filename, GA_ReadOnly)
459 if dataset is None:
460 self.bbox = None
461 else:
462 geotransform = dataset.GetGeoTransform()
463 if geotransform is None:
464 return None
465
466 x = 0
467 y = dataset.RasterYSize
468 left = geotransform[0] + \
469 geotransform[1] * x + \
470 geotransform[2] * y
471
472 bottom = geotransform[3] + \
473 geotransform[4] * x + \
474 geotransform[5] * y
475
476 x = dataset.RasterXSize
477 y = 0
478 right = geotransform[0] + \
479 geotransform[1] * x + \
480 geotransform[2] * y
481
482 top = geotransform[3] + \
483 geotransform[4] * x + \
484 geotransform[5] * y
485
486 self.bbox = (left, bottom, right, top)
487
488 return self.bbox
489
490 def LatLongBoundingBox(self):
491 bbox = self.BoundingBox()
492 if bbox is None:
493 return None
494
495 llx, lly, urx, ury = bbox
496 if self.projection is not None:
497 llx, lly = self.projection.Inverse(llx, lly)
498 urx, ury = self.projection.Inverse(urx, ury)
499
500 return llx, lly, urx, ury
501
502 def GetImageFilename(self):
503 return self.filename
504
505 def TreeInfo(self):
506 items = []
507
508 items.append(_("Filename: %s") % self.GetImageFilename())
509
510 if self.Visible():
511 items.append(_("Shown"))
512 else:
513 items.append(_("Hidden"))
514
515 bbox = self.LatLongBoundingBox()
516 if bbox is not None:
517 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
518 else:
519 items.append(_("Extent (lat-lon):"))
520
521 if self.projection and len(self.projection.params) > 0:
522 items.append((_("Projection"),
523 [str(param) for param in self.projection.params]))
524
525 return (_("Layer '%s'") % self.Title(), items)
526

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26