/[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 1273 - (hide annotations)
Fri Jun 20 17:45:49 2003 UTC (21 years, 8 months ago) by jonathan
File MIME type: text/x-python
File size: 16135 byte(s)
(BaseLayer.HasShapes): New. Overridden
        by deriving classes to determine if that layer supports shapes.
(Layer): Override HasShapes and return true.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26