/[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 1452 - (show annotations)
Fri Jul 18 12:57:59 2003 UTC (21 years, 7 months ago) by bh
File MIME type: text/x-python
File size: 16755 byte(s)
* Thuban/Model/layer.py (Layer.__init__): Rename
classificationField to classificatin_column and init it here so
that it can be used in SetClassificationColumn
(Layer.GetClassificationColumn, Layer.GetClassificationField):
Rename to GetClassificationColumn.
(Layer.SetClassificationColumn, Layer.SetClassificationField):
Rename to SetClassificationColumn and issue a LAYER_CHANGED
message if the column changes.
(Layer._classification_changed, Layer.ClassChanged): Rename to
_classification_changed. Update the callers.
(Layer.SetShapeStore): Further field->column renames.

* Thuban/Model/load.py (SessionLoader.start_classification)
(SessionLoader.start_clpoint): Updates because of
field->column method name changes in the Layer class

* Thuban/Model/save.py (SessionSaver.write_classification): Updates
because of field->column method name changes in the Layer class

* Thuban/UI/classifier.py (Classifier.__init__)
(Classifier._OnTry, Classifier._OnRevert): Updates because of
field->column method name changes in the Layer class

* Thuban/UI/renderer.py (MapRenderer.draw_shape_layer): Updates
because of field->column method name changes in the Layer class

* Thuban/UI/viewport.py (ViewPort.find_shape_at): Updates because
of field->column method name changes in the Layer class

* test/test_save.py (SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.testClassifiedLayer): Update because of
field->column method name changes in the Layer class

* test/test_layer.py (SetShapeStoreTests.setUp)
(SetShapeStoreTests.test_sanity): Update because of field->column
method name changes in the Layer class
(TestLayerModification.setUp): Subscribe to LAYER_CHANGED as well
(TestLayerModification.test_sanity)
(TestLayerModification.test_initial_settings): remove unsued code
and rename to test_sanity.
(TestLayerModification.test_set_classification): New test for
SetClassification and SetClassificationField.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26