/[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 1355 - (hide 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 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 bh 1219 import warnings
13 bh 171
14 jan 374 from Thuban import _
15 bh 143 import shapelib, shptree
16 bh 6
17 bh 701 from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18 bh 1142 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED
19 bh 6
20 jonathan 437 import classification
21 bh 6
22 jonathan 1338 from color import Transparent, Black
23 bh 6 from base import TitledObject, Modifiable
24    
25 jonathan 1158 import resource
26 bh 723
27 jonathan 1158
28 bh 6 class Shape:
29    
30     """Represent one shape"""
31    
32     def __init__(self, points):
33     self.points = points
34     #self.compute_bbox()
35 jonathan 828 self.bbox = None
36 bh 6
37     def compute_bbox(self):
38 jonathan 828 if self.bbox is not None:
39     return self.bbox
40    
41 bh 6 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 jonathan 828 self.bbox = (self.llx, self.lly, self.urx, self.ury)
52    
53     return self.bbox
54    
55 bh 6 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 jonathan 929 def __init__(self, title, visible = True, projection = None):
79 bh 6 """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 jonathan 929 self.projection = projection
88 bh 6
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 bh 276
98 jonathan 929 def HasClassification(self):
99     """Determine if this layer support classifications."""
100     return False
101 bh 276
102 jonathan 1273 def HasShapes(self):
103     """Determine if this layer supports shapes."""
104     return False
105    
106 jonathan 929 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 bh 6 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 jonathan 1338 Color. They can also be Transparent, indicating no fill or no stroke.
123 bh 6
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 bh 723 def __init__(self, title, data, projection = None,
132 jonathan 1338 fill = Transparent,
133     stroke = Black,
134 jonathan 464 lineWidth = 1,
135 jonathan 771 visible = True):
136 bh 6 """Initialize the layer.
137    
138     title -- the title
139 bh 723 data -- datastore object for the shape data shown by the layer
140 bh 6 projection -- the projection object. Its Inverse method is
141     assumed to map the layer's coordinates to lat/long
142     coordinates
143 jonathan 1338 fill -- the fill color or Transparent if the shapes are
144 jonathan 610 not filled
145 jonathan 1338 stroke -- the stroke color or Transparent if the shapes
146 jonathan 610 are not stroked
147 bh 6 visible -- boolean. If true the layer is visible.
148    
149     colors are expected to be instances of Color class
150     """
151 jonathan 929 BaseLayer.__init__(self, title,
152     visible = visible,
153     projection = projection)
154 bh 276
155 jonathan 481 #
156     # this is really important so that when the classification class
157     # tries to set its parent layer the variable will exist
158     #
159 bh 723 self.__classification = None
160 jonathan 481
161 bh 723 self.SetShapeStore(data)
162 jonathan 492
163     self.SetClassification(None)
164    
165 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
166     self.__classification.SetDefaultLineWidth(lineWidth)
167 jonathan 412 self.__classification.SetDefaultFill(fill)
168 jonathan 364
169 jonathan 389 self.UnsetModified()
170    
171 bh 1219 def __getattr__(self, attr):
172     """Access to some attributes for backwards compatibility
173 bh 6
174 bh 1219 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 bh 723 def SetShapeStore(self, store):
193     self.store = store
194 bh 1219 shapefile = self.store.Shapefile()
195 bh 179
196 bh 1219 numshapes, shapetype, mins, maxs = shapefile.info()
197 bh 723 self.numshapes = numshapes
198     self.shapetype = shapelib_shapetypes[shapetype]
199 bh 171
200 bh 723 # 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 bh 171
207 bh 723 # 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 bh 1219 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
217 bh 723 maxdepth)
218 bh 1088 # 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 bh 723 if self.__classification is not None:
224     fieldname = self.__classification.GetField()
225 bh 1088 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 bh 723 self.SetClassification(None)
231 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
232 bh 723
233     def ShapeStore(self):
234     return self.store
235    
236 bh 258 def Destroy(self):
237 bh 260 BaseLayer.Destroy(self)
238 jonathan 1338 self.GetClassification()._set_layer(None)
239 bh 258
240 bh 6 def BoundingBox(self):
241 bh 179 """Return the layer's bounding box in the intrinsic coordinate system.
242    
243     If the layer has no shapes, return None.
244     """
245 bh 6 return self.bbox
246    
247     def LatLongBoundingBox(self):
248 bh 179 """Return the layer's bounding box in lat/long coordinates.
249 bh 6
250 bh 179 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 jonathan 828 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 jonathan 464 def GetFieldType(self, fieldName):
295 bh 1219 table = self.store.Table()
296     if table.HasColumn(fieldName):
297     return table.Column(fieldName).type
298 bh 839 return None
299 jonathan 464
300 jonathan 1273 def HasShapes(self):
301     return True
302    
303 bh 6 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 bh 1219 shape = self.store.Shapefile().read_object(index)
316 jonathan 364
317 bh 6 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 bh 82 points.append((x, y))
325 jonathan 364
326 bh 6 return Shape(points)
327    
328 bh 143 def ShapesInRegion(self, box):
329     """Return the ids of the shapes that overlap the box.
330    
331 jonathan 794 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
332 bh 143 """
333     left, bottom, right, top = box
334 jonathan 794
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 bh 147 return self.shapetree.find_shapes((left, bottom), (right, top))
340 bh 143
341 jonathan 929 def HasClassification(self):
342     return True
343 jonathan 725
344 jonathan 412 def GetClassification(self):
345     return self.__classification
346    
347     def SetClassification(self, clazz):
348 jonathan 1338 """Set the classification used by this layer to 'clazz'
349 jonathan 481
350 jonathan 1338 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 jonathan 492 """
357 jonathan 481
358 jonathan 1338 old_class = self.__classification
359 jonathan 529
360 jonathan 1338 if clazz is None:
361     clazz = classification.Classification()
362 jonathan 529
363 jonathan 1338 try:
364 jonathan 1355 self.__classification = clazz
365 jonathan 1338 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 jonathan 1355 self.__classification = old_class
372 jonathan 1338 raise ValueError
373 jonathan 492
374 jonathan 1338 # we don't need this since a message will be sent
375     # after calling _set_layer()
376     #self.changed(LAYER_CHANGED, self)
377 jonathan 412
378 jonathan 492 def ClassChanged(self):
379     """Called from the classification object when it has changed."""
380 jonathan 558 self.changed(LAYER_CHANGED, self)
381 jonathan 492
382 bh 217 def TreeInfo(self):
383     items = []
384    
385 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
386 jan 1012
387 bh 217 if self.Visible():
388 jan 374 items.append(_("Shown"))
389 bh 217 else:
390 jan 374 items.append(_("Hidden"))
391     items.append(_("Shapes: %d") % self.NumShapes())
392 bh 217
393     bbox = self.LatLongBoundingBox()
394     if bbox is not None:
395 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
396 bh 217 else:
397 jan 374 items.append(_("Extent (lat-lon):"))
398     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
399 bh 217
400 jonathan 736 if self.projection and len(self.projection.params) > 0:
401     items.append((_("Projection"),
402     [str(param) for param in self.projection.params]))
403    
404 jonathan 412 items.append(self.__classification)
405 bh 217
406 jan 374 return (_("Layer '%s'") % self.Title(), items)
407 jonathan 382
408 jonathan 736
409 jonathan 1158 if resource.has_gdal_support():
410     import gdal
411     from gdalconst import GA_ReadOnly
412    
413 jonathan 929 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 jonathan 961
427     Throws IOError if the filename is invalid or points to a file that
428     is not in a format GDAL can use.
429 jonathan 929 """
430    
431     BaseLayer.__init__(self, title, visible = visible)
432    
433     self.projection = projection
434     self.filename = filename
435    
436     self.bbox = -1
437    
438 jonathan 1158 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 jonathan 961
444 jonathan 1158 if dataset is None:
445     raise IOError()
446 jonathan 961
447 jonathan 929 self.UnsetModified()
448    
449     def BoundingBox(self):
450     """Return the layer's bounding box in the intrinsic coordinate system.
451    
452 jonathan 1338 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 jonathan 929 """
455 jonathan 1158 if not resource.has_gdal_support():
456     return None
457    
458 jonathan 929 if self.bbox == -1:
459     dataset = gdal.Open(self.filename, GA_ReadOnly)
460     if dataset is None:
461     self.bbox = None
462     else:
463 jonathan 961 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 jonathan 929 self.bbox = (left, bottom, right, top)
488    
489     return self.bbox
490    
491     def LatLongBoundingBox(self):
492     bbox = self.BoundingBox()
493 jonathan 961 if bbox is None:
494 jonathan 929 return None
495    
496 jonathan 961 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 jonathan 929 def GetImageFilename(self):
504     return self.filename
505    
506     def TreeInfo(self):
507     items = []
508    
509 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
510    
511 jonathan 929 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