/[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 1355 - (show annotations)
Wed Jul 2 09:37:23 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 16318 byte(s)
(Layer.SetClassification): Switch
        the classification instance variable to the new class
        before calling _set_layer otherwise subscribers to a
        LAYER_CHANGED event will not see any difference.

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 self.__classification = clazz
365 clazz._set_layer(self)
366
367 # only change things after a successful call
368 if old_class is not None:
369 old_class._set_layer(None)
370 except ValueError:
371 self.__classification = old_class
372 raise ValueError
373
374 # we don't need this since a message will be sent
375 # after calling _set_layer()
376 #self.changed(LAYER_CHANGED, self)
377
378 def ClassChanged(self):
379 """Called from the classification object when it has changed."""
380 self.changed(LAYER_CHANGED, self)
381
382 def TreeInfo(self):
383 items = []
384
385 items.append(_("Filename: %s") % self.ShapeStore().FileName())
386
387 if self.Visible():
388 items.append(_("Shown"))
389 else:
390 items.append(_("Hidden"))
391 items.append(_("Shapes: %d") % self.NumShapes())
392
393 bbox = self.LatLongBoundingBox()
394 if bbox is not None:
395 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
396 else:
397 items.append(_("Extent (lat-lon):"))
398 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
399
400 if self.projection and len(self.projection.params) > 0:
401 items.append((_("Projection"),
402 [str(param) for param in self.projection.params]))
403
404 items.append(self.__classification)
405
406 return (_("Layer '%s'") % self.Title(), items)
407
408
409 if resource.has_gdal_support():
410 import gdal
411 from gdalconst import GA_ReadOnly
412
413 class RasterLayer(BaseLayer):
414
415 def __init__(self, title, filename, projection = None, visible = True):
416 """Initialize the Raster Layer.
417
418 title -- title for the layer.
419
420 filename -- file name of the source image.
421
422 projection -- Projection object describing the projection which
423 the source image is in.
424
425 visible -- True is the layer should initially be visible.
426
427 Throws IOError if the filename is invalid or points to a file that
428 is not in a format GDAL can use.
429 """
430
431 BaseLayer.__init__(self, title, visible = visible)
432
433 self.projection = projection
434 self.filename = filename
435
436 self.bbox = -1
437
438 if resource.has_gdal_support():
439 #
440 # temporarily open the file so that GDAL can test if it's valid.
441 #
442 dataset = gdal.Open(self.filename, GA_ReadOnly)
443
444 if dataset is None:
445 raise IOError()
446
447 self.UnsetModified()
448
449 def BoundingBox(self):
450 """Return the layer's bounding box in the intrinsic coordinate system.
451
452 If the there is no support for images, or the file cannot
453 be read, or there is no geographics information available, return None.
454 """
455 if not resource.has_gdal_support():
456 return None
457
458 if self.bbox == -1:
459 dataset = gdal.Open(self.filename, GA_ReadOnly)
460 if dataset is None:
461 self.bbox = None
462 else:
463 geotransform = dataset.GetGeoTransform()
464 if geotransform is None:
465 return None
466
467 x = 0
468 y = dataset.RasterYSize
469 left = geotransform[0] + \
470 geotransform[1] * x + \
471 geotransform[2] * y
472
473 bottom = geotransform[3] + \
474 geotransform[4] * x + \
475 geotransform[5] * y
476
477 x = dataset.RasterXSize
478 y = 0
479 right = geotransform[0] + \
480 geotransform[1] * x + \
481 geotransform[2] * y
482
483 top = geotransform[3] + \
484 geotransform[4] * x + \
485 geotransform[5] * y
486
487 self.bbox = (left, bottom, right, top)
488
489 return self.bbox
490
491 def LatLongBoundingBox(self):
492 bbox = self.BoundingBox()
493 if bbox is None:
494 return None
495
496 llx, lly, urx, ury = bbox
497 if self.projection is not None:
498 llx, lly = self.projection.Inverse(llx, lly)
499 urx, ury = self.projection.Inverse(urx, ury)
500
501 return llx, lly, urx, ury
502
503 def GetImageFilename(self):
504 return self.filename
505
506 def TreeInfo(self):
507 items = []
508
509 items.append(_("Filename: %s") % self.GetImageFilename())
510
511 if self.Visible():
512 items.append(_("Shown"))
513 else:
514 items.append(_("Hidden"))
515
516 bbox = self.LatLongBoundingBox()
517 if bbox is not None:
518 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
519 else:
520 items.append(_("Extent (lat-lon):"))
521
522 if self.projection and len(self.projection.params) > 0:
523 items.append((_("Projection"),
524 [str(param) for param in self.projection.params]))
525
526 return (_("Layer '%s'") % self.Title(), items)
527

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26