/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Annotation of /trunk/thuban/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1088 - (hide annotations)
Wed May 28 12:42:23 2003 UTC (21 years, 9 months ago) by bh
File MIME type: text/x-python
File size: 15056 byte(s)
* Thuban/Model/layer.py (Layer.SetShapeStore): Set the
classification to "None" if the type of the field has changed.

* test/test_layer.py (SetShapeStoreTests): New. Class with a few
test for the Layer.SetShapeStore method

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26