/[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 1427 - (hide 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 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 jonathan 1427 LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
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 bh 723 self.__classification = None
156 jonathan 1427 self.store = None
157 jonathan 481
158 bh 723 self.SetShapeStore(data)
159 jonathan 492
160 jonathan 1427 self.SetClassificationField(None)
161 jonathan 492 self.SetClassification(None)
162    
163 jonathan 464 self.__classification.SetDefaultLineColor(stroke)
164     self.__classification.SetDefaultLineWidth(lineWidth)
165 jonathan 412 self.__classification.SetDefaultFill(fill)
166 jonathan 364
167 jonathan 389 self.UnsetModified()
168    
169 bh 1219 def __getattr__(self, attr):
170     """Access to some attributes for backwards compatibility
171 bh 6
172 bh 1219 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 bh 723 def SetShapeStore(self, store):
191 jonathan 1427 # 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 bh 723 self.store = store
206 bh 1219 shapefile = self.store.Shapefile()
207 bh 179
208 bh 1219 numshapes, shapetype, mins, maxs = shapefile.info()
209 bh 723 self.numshapes = numshapes
210     self.shapetype = shapelib_shapetypes[shapetype]
211 bh 171
212 bh 723 # 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 bh 171
219 bh 723 # 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 bh 1219 self.shapetree = shptree.SHPTree(shapefile.cobject(), 2,
229 bh 723 maxdepth)
230 bh 1142 self.changed(LAYER_SHAPESTORE_REPLACED, self)
231 bh 723
232     def ShapeStore(self):
233     return self.store
234    
235 bh 258 def Destroy(self):
236 bh 260 BaseLayer.Destroy(self)
237 jonathan 1427 if self.__classification is not None:
238     self.__classification.Unsubscribe(CLASS_CHANGED, self.ClassChanged)
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 jonathan 1427 if self.store:
296     table = self.store.Table()
297     if table.HasColumn(fieldName):
298     return table.Column(fieldName).type
299 bh 839 return None
300 jonathan 464
301 jonathan 1273 def HasShapes(self):
302     return True
303    
304 bh 6 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 bh 1219 shape = self.store.Shapefile().read_object(index)
317 jonathan 364
318 bh 6 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 bh 82 points.append((x, y))
326 jonathan 364
327 bh 6 return Shape(points)
328    
329 bh 143 def ShapesInRegion(self, box):
330     """Return the ids of the shapes that overlap the box.
331    
332 jonathan 794 Box is a tuple (left, bottom, right, top) in unprojected coordinates.
333 bh 143 """
334     left, bottom, right, top = box
335 jonathan 794
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 bh 147 return self.shapetree.find_shapes((left, bottom), (right, top))
341 bh 143
342 jonathan 1427 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 jonathan 929 def HasClassification(self):
356     return True
357 jonathan 725
358 jonathan 412 def GetClassification(self):
359     return self.__classification
360    
361     def SetClassification(self, clazz):
362 jonathan 1338 """Set the classification used by this layer to 'clazz'
363 jonathan 481
364 jonathan 1338 If 'clazz' is None a default classification is created.
365    
366 jonathan 1427 This issues a LAYER_CHANGED event.
367 jonathan 492 """
368 jonathan 481
369 jonathan 1427 if self.__classification is not None:
370     self.__classification.Unsubscribe(CLASS_CHANGED, self.ClassChanged)
371 jonathan 529
372 jonathan 1338 if clazz is None:
373     clazz = classification.Classification()
374 jonathan 529
375 jonathan 1427 self.__classification = clazz
376     self.__classification.Subscribe(CLASS_CHANGED, self.ClassChanged)
377 jonathan 1338
378 jonathan 1427 self.ClassChanged()
379 jonathan 492
380     def ClassChanged(self):
381     """Called from the classification object when it has changed."""
382 jonathan 558 self.changed(LAYER_CHANGED, self)
383 jonathan 492
384 bh 217 def TreeInfo(self):
385     items = []
386    
387 jonathan 1338 items.append(_("Filename: %s") % self.ShapeStore().FileName())
388 jan 1012
389 bh 217 if self.Visible():
390 jan 374 items.append(_("Shown"))
391 bh 217 else:
392 jan 374 items.append(_("Hidden"))
393     items.append(_("Shapes: %d") % self.NumShapes())
394 bh 217
395     bbox = self.LatLongBoundingBox()
396     if bbox is not None:
397 jan 374 items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
398 bh 217 else:
399 jan 374 items.append(_("Extent (lat-lon):"))
400     items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
401 bh 217
402 jonathan 736 if self.projection and len(self.projection.params) > 0:
403     items.append((_("Projection"),
404     [str(param) for param in self.projection.params]))
405    
406 jonathan 412 items.append(self.__classification)
407 bh 217
408 jan 374 return (_("Layer '%s'") % self.Title(), items)
409 jonathan 382
410 jonathan 736
411 jonathan 1158 if resource.has_gdal_support():
412     import gdal
413     from gdalconst import GA_ReadOnly
414    
415 jonathan 929 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 jonathan 961
429     Throws IOError if the filename is invalid or points to a file that
430     is not in a format GDAL can use.
431 jonathan 929 """
432    
433     BaseLayer.__init__(self, title, visible = visible)
434    
435     self.projection = projection
436     self.filename = filename
437    
438     self.bbox = -1
439    
440 jonathan 1158 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 jonathan 961
446 jonathan 1158 if dataset is None:
447     raise IOError()
448 jonathan 961
449 jonathan 929 self.UnsetModified()
450    
451     def BoundingBox(self):
452     """Return the layer's bounding box in the intrinsic coordinate system.
453    
454 jonathan 1338 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 jonathan 929 """
457 jonathan 1158 if not resource.has_gdal_support():
458     return None
459    
460 jonathan 929 if self.bbox == -1:
461     dataset = gdal.Open(self.filename, GA_ReadOnly)
462     if dataset is None:
463     self.bbox = None
464     else:
465 jonathan 961 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 jonathan 929 self.bbox = (left, bottom, right, top)
490    
491     return self.bbox
492    
493     def LatLongBoundingBox(self):
494     bbox = self.BoundingBox()
495 jonathan 961 if bbox is None:
496 jonathan 929 return None
497    
498 jonathan 961 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 jonathan 929 def GetImageFilename(self):
506     return self.filename
507    
508     def TreeInfo(self):
509     items = []
510    
511 jonathan 1338 items.append(_("Filename: %s") % self.GetImageFilename())
512    
513 jonathan 929 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