/[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 1427 - (show annotations)
Wed Jul 16 13:22:48 2003 UTC (21 years, 7 months ago) by jonathan
File MIME type: text/x-python
File size: 16380 byte(s)
(Layer.Destroy): Unsubscribe from the classification.
(Layer.SetShapeStore): Reset the classification first while
        we still have the old shape store to work with.
(Layer.GetClassificationField, Layer.SetClassificationField):
        New. Method for getting/setting the field to classify on.
(Layer.SetClassification): Simplified now that the layer
        simply has to hold a reference to the classification and not
        tell the classification who owns it.

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, CLASS_CHANGED
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 self.__classification = None
156 self.store = None
157
158 self.SetShapeStore(data)
159
160 self.SetClassificationField(None)
161 self.SetClassification(None)
162
163 self.__classification.SetDefaultLineColor(stroke)
164 self.__classification.SetDefaultLineWidth(lineWidth)
165 self.__classification.SetDefaultFill(fill)
166
167 self.UnsetModified()
168
169 def __getattr__(self, attr):
170 """Access to some attributes for backwards compatibility
171
172 The attributes implemented here are now held by the shapestore
173 if at all. For backwards compatibility pretend that they are
174 still there but issue a DeprecationWarning when they are
175 accessed.
176 """
177 if attr in ("table", "shapetable"):
178 value = self.store.Table()
179 elif attr == "shapefile":
180 value = self.store.Shapefile()
181 elif attr == "filename":
182 value = self.store.FileName()
183 else:
184 raise AttributeError, attr
185 warnings.warn("The Layer attribute %r is deprecated."
186 " It's value can be accessed through the shapestore"
187 % attr, DeprecationWarning, stacklevel = 2)
188 return value
189
190 def SetShapeStore(self, store):
191 # Set the classification to None if there is a classification
192 # and the new shapestore doesn't have a table with a suitable
193 # column, i.e one with the same name and type as before
194 # FIXME: Maybe we should keep it the same if the type is
195 # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
196 if self.__classification is not None:
197 fieldname = self.classificationField
198 fieldtype = self.GetFieldType(fieldname)
199 table = store.Table()
200 if (fieldname is not None
201 and (not table.HasColumn(fieldname)
202 or table.Column(fieldname).type != fieldtype)):
203 self.SetClassification(None)
204
205 self.store = store
206 shapefile = self.store.Shapefile()
207
208 numshapes, shapetype, mins, maxs = shapefile.info()
209 self.numshapes = numshapes
210 self.shapetype = shapelib_shapetypes[shapetype]
211
212 # if there are shapes, set the bbox accordingly. Otherwise
213 # set it to None.
214 if self.numshapes:
215 self.bbox = mins[:2] + maxs[:2]
216 else:
217 self.bbox = None
218
219 # estimate a good depth for the quad tree. Each depth
220 # multiplies the number of nodes by four, therefore we
221 # basically take the base 4 logarithm of the number of
222 # shapes.
223 if self.numshapes < 4:
224 maxdepth = 1
225 else:
226 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
227
228 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
229 maxdepth)
230 self.changed(LAYER_SHAPESTORE_REPLACED, self)
231
232 def ShapeStore(self):
233 return self.store
234
235 def Destroy(self):
236 BaseLayer.Destroy(self)
237 if self.__classification is not None:
238 self.__classification.Unsubscribe(CLASS_CHANGED, self.ClassChanged)
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 if self.store:
296 table = self.store.Table()
297 if table.HasColumn(fieldName):
298 return table.Column(fieldName).type
299 return None
300
301 def HasShapes(self):
302 return True
303
304 def NumShapes(self):
305 """Return the number of shapes in the layer"""
306 return self.numshapes
307
308 def ShapeType(self):
309 """Return the type of the shapes in the layer.
310 This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
311 """
312 return self.shapetype
313
314 def Shape(self, index):
315 """Return the shape with index index"""
316 shape = self.store.Shapefile().read_object(index)
317
318 if self.shapetype == SHAPETYPE_POINT:
319 points = shape.vertices()
320 else:
321 #for poly in shape.vertices():
322 poly = shape.vertices()[0]
323 points = []
324 for x, y in poly:
325 points.append((x, y))
326
327 return Shape(points)
328
329 def ShapesInRegion(self, box):
330 """Return the ids of the shapes that overlap the box.
331
332 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
333 """
334 left, bottom, right, top = box
335
336 if self.projection is not None:
337 left, bottom = self.projection.Forward(left, bottom)
338 right, top = self.projection.Forward(right, top)
339
340 return self.shapetree.find_shapes((left, bottom), (right, top))
341
342 def GetClassificationField(self):
343 return self.classificationField
344
345 def SetClassificationField(self, field):
346 """Set the field to classifiy on, or None. If field is not None
347 and the field does not exist in the table, raise a ValueError.
348 """
349 if field:
350 fieldType = self.GetFieldType(field)
351 if fieldType is None:
352 raise ValueError()
353 self.classificationField = field
354
355 def HasClassification(self):
356 return True
357
358 def GetClassification(self):
359 return self.__classification
360
361 def SetClassification(self, clazz):
362 """Set the classification used by this layer to 'clazz'
363
364 If 'clazz' is None a default classification is created.
365
366 This issues a LAYER_CHANGED event.
367 """
368
369 if self.__classification is not None:
370 self.__classification.Unsubscribe(CLASS_CHANGED, self.ClassChanged)
371
372 if clazz is None:
373 clazz = classification.Classification()
374
375 self.__classification = clazz
376 self.__classification.Subscribe(CLASS_CHANGED, self.ClassChanged)
377
378 self.ClassChanged()
379
380 def ClassChanged(self):
381 """Called from the classification object when it has changed."""
382 self.changed(LAYER_CHANGED, self)
383
384 def TreeInfo(self):
385 items = []
386
387 items.append(_("Filename: %s") % self.ShapeStore().FileName())
388
389 if self.Visible():
390 items.append(_("Shown"))
391 else:
392 items.append(_("Hidden"))
393 items.append(_("Shapes: %d") % self.NumShapes())
394
395 bbox = self.LatLongBoundingBox()
396 if bbox is not None:
397 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
398 else:
399 items.append(_("Extent (lat-lon):"))
400 items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
401
402 if self.projection and len(self.projection.params) > 0:
403 items.append((_("Projection"),
404 [str(param) for param in self.projection.params]))
405
406 items.append(self.__classification)
407
408 return (_("Layer '%s'") % self.Title(), items)
409
410
411 if resource.has_gdal_support():
412 import gdal
413 from gdalconst import GA_ReadOnly
414
415 class RasterLayer(BaseLayer):
416
417 def __init__(self, title, filename, projection = None, visible = True):
418 """Initialize the Raster Layer.
419
420 title -- title for the layer.
421
422 filename -- file name of the source image.
423
424 projection -- Projection object describing the projection which
425 the source image is in.
426
427 visible -- True is the layer should initially be visible.
428
429 Throws IOError if the filename is invalid or points to a file that
430 is not in a format GDAL can use.
431 """
432
433 BaseLayer.__init__(self, title, visible = visible)
434
435 self.projection = projection
436 self.filename = filename
437
438 self.bbox = -1
439
440 if resource.has_gdal_support():
441 #
442 # temporarily open the file so that GDAL can test if it's valid.
443 #
444 dataset = gdal.Open(self.filename, GA_ReadOnly)
445
446 if dataset is None:
447 raise IOError()
448
449 self.UnsetModified()
450
451 def BoundingBox(self):
452 """Return the layer's bounding box in the intrinsic coordinate system.
453
454 If the there is no support for images, or the file cannot
455 be read, or there is no geographics information available, return None.
456 """
457 if not resource.has_gdal_support():
458 return None
459
460 if self.bbox == -1:
461 dataset = gdal.Open(self.filename, GA_ReadOnly)
462 if dataset is None:
463 self.bbox = None
464 else:
465 geotransform = dataset.GetGeoTransform()
466 if geotransform is None:
467 return None
468
469 x = 0
470 y = dataset.RasterYSize
471 left = geotransform[0] + \
472 geotransform[1] * x + \
473 geotransform[2] * y
474
475 bottom = geotransform[3] + \
476 geotransform[4] * x + \
477 geotransform[5] * y
478
479 x = dataset.RasterXSize
480 y = 0
481 right = geotransform[0] + \
482 geotransform[1] * x + \
483 geotransform[2] * y
484
485 top = geotransform[3] + \
486 geotransform[4] * x + \
487 geotransform[5] * y
488
489 self.bbox = (left, bottom, right, top)
490
491 return self.bbox
492
493 def LatLongBoundingBox(self):
494 bbox = self.BoundingBox()
495 if bbox is None:
496 return None
497
498 llx, lly, urx, ury = bbox
499 if self.projection is not None:
500 llx, lly = self.projection.Inverse(llx, lly)
501 urx, ury = self.projection.Inverse(urx, ury)
502
503 return llx, lly, urx, ury
504
505 def GetImageFilename(self):
506 return self.filename
507
508 def TreeInfo(self):
509 items = []
510
511 items.append(_("Filename: %s") % self.GetImageFilename())
512
513 if self.Visible():
514 items.append(_("Shown"))
515 else:
516 items.append(_("Hidden"))
517
518 bbox = self.LatLongBoundingBox()
519 if bbox is not None:
520 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
521 else:
522 items.append(_("Extent (lat-lon):"))
523
524 if self.projection and len(self.projection.params) > 0:
525 items.append((_("Projection"),
526 [str(param) for param in self.projection.params]))
527
528 return (_("Layer '%s'") % self.Title(), items)
529

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26