/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/Model/layer.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1117 by bh, Fri May 30 18:31:19 2003 UTC revision 2339 by silke, Fri Aug 20 16:59:21 2004 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4  # Jonathan Coles <[email protected]>  # Jonathan Coles <[email protected]>
5    # Silke Reimer <[email protected]>
6  #  #
7  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
8  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
9    
10  __version__ = "$Revision$"  __version__ = "$Revision$"
11    
12  from math import log, ceil  import os
13    import warnings
14    
15  from Thuban import _  from Thuban import _
16    
 import shapelib, shptree  
   
 import gdal  
 from gdalconst import GA_ReadOnly  
   
17  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18       LAYER_CHANGED       LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
   
 from color import Color  
19    
20  import classification  import classification
21    
22    from color import Transparent, Black
23  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
24    from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
25    
26    import resource
 class Shape:  
   
     """Represent one shape"""  
   
     def __init__(self, points):  
         self.points = points  
         #self.compute_bbox()  
         self.bbox = None  
   
     def compute_bbox(self):  
         if self.bbox is not None:  
             return self.bbox  
   
         xs = []  
         ys = []  
         for x, y in self.points:  
             xs.append(x)  
             ys.append(y)  
         self.llx = min(xs)  
         self.lly = min(ys)  
         self.urx = max(xs)  
         self.ury = max(ys)  
   
         self.bbox = (self.llx, self.lly, self.urx, self.ury)  
   
         return self.bbox  
   
     def Points(self):  
         return self.points  
   
27    
28    
 # Shape type constants  
 SHAPETYPE_POLYGON = "polygon"  
 SHAPETYPE_ARC = "arc"  
 SHAPETYPE_POINT = "point"  
   
 # mapping from shapelib shapetype constants to our constants  
 shapelib_shapetypes = {shapelib.SHPT_POLYGON: SHAPETYPE_POLYGON,  
                        shapelib.SHPT_ARC: SHAPETYPE_ARC,  
                        shapelib.SHPT_POINT: SHAPETYPE_POINT}  
   
29  shapetype_names = {SHAPETYPE_POINT: "Point",  shapetype_names = {SHAPETYPE_POINT: "Point",
30                     SHAPETYPE_ARC: "Arc",                     SHAPETYPE_ARC: "Arc",
31                     SHAPETYPE_POLYGON: "Polygon"}                     SHAPETYPE_POLYGON: "Polygon"}
# Line 101  class BaseLayer(TitledObject, Modifiable Line 58  class BaseLayer(TitledObject, Modifiable
58          """Determine if this layer support classifications."""          """Determine if this layer support classifications."""
59          return False          return False
60    
61        def HasShapes(self):
62            """Determine if this layer supports shapes."""
63            return False
64    
65      def GetProjection(self):      def GetProjection(self):
66          """Return the layer's projection."""          """Return the layer's projection."""
67          return self.projection          return self.projection
# Line 117  class Layer(BaseLayer): Line 78  class Layer(BaseLayer):
78      All children of the layer have the same type.      All children of the layer have the same type.
79    
80      A layer has fill and stroke colors. Colors should be instances of      A layer has fill and stroke colors. Colors should be instances of
81      Color. They can also be None, indicating no fill or no stroke.      Color. They can also be Transparent, indicating no fill or no stroke.
82    
83      The layer objects send the following events, all of which have the      The layer objects send the following events, all of which have the
84      layer object as parameter:      layer object as parameter:
# Line 127  class Layer(BaseLayer): Line 88  class Layer(BaseLayer):
88      """      """
89    
90      def __init__(self, title, data, projection = None,      def __init__(self, title, data, projection = None,
91                   fill = Color.Transparent,                   fill = Transparent,
92                   stroke = Color.Black,                   stroke = Black,
93                   lineWidth = 1,                   lineWidth = 1,
94                   visible = True):                   visible = True):
95          """Initialize the layer.          """Initialize the layer.
# Line 138  class Layer(BaseLayer): Line 99  class Layer(BaseLayer):
99          projection -- the projection object. Its Inverse method is          projection -- the projection object. Its Inverse method is
100                 assumed to map the layer's coordinates to lat/long                 assumed to map the layer's coordinates to lat/long
101                 coordinates                 coordinates
102          fill -- the fill color or Color.Transparent if the shapes are          fill -- the fill color or Transparent if the shapes are
103                  not filled                  not filled
104          stroke -- the stroke color or Color.Transparent if the shapes          stroke -- the stroke color or Transparent if the shapes
105                  are not stroked                  are not stroked
106          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
107    
# Line 150  class Layer(BaseLayer): Line 111  class Layer(BaseLayer):
111                                   visible = visible,                                   visible = visible,
112                                   projection = projection)                                   projection = projection)
113    
         #  
         # this is really important so that when the classification class  
         # tries to set its parent layer the variable will exist  
         #  
114          self.__classification = None          self.__classification = None
115          self.__setClassLock = False          self.store = None
116    
117          self.SetShapeStore(data)          self.SetShapeStore(data)
118    
119            self.classification_column = None
120            self.SetClassificationColumn(None)
121          self.SetClassification(None)          self.SetClassification(None)
122    
123          self.__classification.SetDefaultLineColor(stroke)          self.__classification.SetDefaultLineColor(stroke)
124          self.__classification.SetDefaultLineWidth(lineWidth)          self.__classification.SetDefaultLineWidth(lineWidth)
125          self.__classification.SetDefaultFill(fill)          self.__classification.SetDefaultFill(fill)
         self.__classification.SetLayer(self)  
126    
127          self.UnsetModified()          self.UnsetModified()
128    
   
129      def SetShapeStore(self, store):      def SetShapeStore(self, store):
         self.store = store  
         self.shapefile = self.store.Shapefile()  
         self.shapetable = self.store.Table()  
         if hasattr(self.store, "FileName"):  
             self.filename = self.store.FileName()  
         self.table = self.shapetable  
   
         numshapes, shapetype, mins, maxs = self.shapefile.info()  
         self.numshapes = numshapes  
         self.shapetype = shapelib_shapetypes[shapetype]  
   
         # if there are shapes, set the bbox accordingly. Otherwise  
         # set it to None.  
         if self.numshapes:  
             self.bbox = mins[:2] + maxs[:2]  
         else:  
             self.bbox = None  
   
         # estimate a good depth for the quad tree. Each depth  
         # multiplies the number of nodes by four, therefore we  
         # basically take the base 4 logarithm of the number of  
         # shapes.  
         if self.numshapes < 4:  
             maxdepth = 1  
         else:  
             maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))  
   
         self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,  
                                          maxdepth)  
130          # Set the classification to None if there is a classification          # Set the classification to None if there is a classification
131          # and the new shapestore doesn't have a table with a suitable          # and the new shapestore doesn't have a table with a suitable
132          # column, i.e one with the same name and type as before          # column, i.e one with the same name and type as before
133          # FIXME: Maybe we should keep it the same if the type is          # FIXME: Maybe we should keep it the same if the type is
134          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
135          if self.__classification is not None:          if self.__classification is not None:
136              fieldname = self.__classification.GetField()              columnname = self.classification_column
137              fieldtype = self.__classification.GetFieldType()              columntype = self.GetFieldType(columnname)
138              table = self.store.Table()              table = store.Table()
139              if (fieldname is not None              if (columnname is not None
140                  and (not table.HasColumn(fieldname)                  and (not table.HasColumn(columnname)
141                       or table.Column(fieldname).type != fieldtype)):                       or table.Column(columnname).type != columntype)):
142                  self.SetClassification(None)                  self.SetClassification(None)
143          self.changed(LAYER_CHANGED, self)  
144            self.store = store
145    
146            self.changed(LAYER_SHAPESTORE_REPLACED, self)
147    
148      def ShapeStore(self):      def ShapeStore(self):
149          return self.store          return self.store
150    
151      def Destroy(self):      def Destroy(self):
152          BaseLayer.Destroy(self)          BaseLayer.Destroy(self)
153          self.SetClassification(None)          if self.__classification is not None:
154          self.store = self.shapetree = None              self.__classification.Unsubscribe(CLASS_CHANGED,
155          self.table = self.shapefile = self.shapetable = None                                                self._classification_changed)
156    
157      def BoundingBox(self):      def BoundingBox(self):
158          """Return the layer's bounding box in the intrinsic coordinate system.          """Return the layer's bounding box in the intrinsic coordinate system.
159    
160          If the layer has no shapes, return None.          If the layer has no shapes, return None.
161          """          """
162          return self.bbox          return self.store.BoundingBox()
163    
164      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
165          """Return the layer's bounding box in lat/long coordinates.          """Return the layer's bounding box in lat/long coordinates.
# Line 236  class Layer(BaseLayer): Line 167  class Layer(BaseLayer):
167          Return None, if the layer doesn't contain any shapes.          Return None, if the layer doesn't contain any shapes.
168          """          """
169          bbox = self.BoundingBox()          bbox = self.BoundingBox()
170          if bbox is not None:          if bbox is not None and self.projection is not None:
171              llx, lly, urx, ury = bbox              bbox = self.projection.InverseBBox(bbox)
172              if self.projection is not None:          return bbox
                 llx, lly = self.projection.Inverse(llx, lly)  
                 urx, ury = self.projection.Inverse(urx, ury)  
             return llx, lly, urx, ury  
         else:  
             return None  
173    
174      def ShapesBoundingBox(self, shapes):      def ShapesBoundingBox(self, shapes):
175          """Return a bounding box in lat/long coordinates for the given          """Return a bounding box in lat/long coordinates for the given
# Line 254  class Layer(BaseLayer): Line 180  class Layer(BaseLayer):
180    
181          if shapes is None or len(shapes) == 0: return None          if shapes is None or len(shapes) == 0: return None
182    
183          llx = []          xs = []
184          lly = []          ys = []
         urx = []  
         ury = []  
   
         if self.projection is not None:  
             inverse = lambda x, y: self.projection.Inverse(x, y)  
         else:  
             inverse = lambda x, y: (x, y)  
185    
186          for id in shapes:          for id in shapes:
187              left, bottom, right, top = self.Shape(id).compute_bbox()              bbox = self.Shape(id).compute_bbox()
188                if self.projection is not None:
189              left, bottom = inverse(left, bottom)                  bbox = self.projection.InverseBBox(bbox)
190              right, top   = inverse(right, top)              left, bottom, right, top = bbox
191                xs.append(left); xs.append(right)
192                ys.append(bottom); ys.append(top)
193    
194              llx.append(left)          return (min(xs), min(ys), max(xs), max(ys))
             lly.append(bottom)  
             urx.append(right)  
             ury.append(top)  
195    
         return (min(llx), min(lly), max(urx), max(ury))  
196    
197      def GetFieldType(self, fieldName):      def GetFieldType(self, fieldName):
198          if self.table.HasColumn(fieldName):          if self.store:
199              return self.table.Column(fieldName).type              table = self.store.Table()
200                if table.HasColumn(fieldName):
201                    return table.Column(fieldName).type
202          return None          return None
203    
204        def HasShapes(self):
205            return True
206    
207      def NumShapes(self):      def NumShapes(self):
208          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
209          return self.numshapes          return self.store.NumShapes()
210    
211      def ShapeType(self):      def ShapeType(self):
212          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
213          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.  
214            The return value is one of the SHAPETYPE_* constants defined in
215            Thuban.Model.data.
216          """          """
217          return self.shapetype          return self.store.ShapeType()
218    
219      def Shape(self, index):      def Shape(self, index):
220          """Return the shape with index index"""          """Return the shape with index index"""
221          shape = self.shapefile.read_object(index)          return self.store.Shape(index)
   
         if self.shapetype == SHAPETYPE_POINT:  
             points = shape.vertices()  
         else:  
             #for poly in shape.vertices():  
             poly = shape.vertices()[0]  
             points = []  
             for x, y in poly:  
                 points.append((x, y))  
   
         return Shape(points)  
222    
223      def ShapesInRegion(self, box):      def ShapesInRegion(self, bbox):
224          """Return the ids of the shapes that overlap the box.          """Return an iterable over the shapes that overlap the bounding box.
225    
226          Box is a tuple (left, bottom, right, top) in unprojected coordinates.          The bbox parameter should be the bounding box as a tuple in the
227            form (minx, miny, maxx, maxy) in unprojected coordinates.
228          """          """
         left, bottom, right, top = box  
   
229          if self.projection is not None:          if self.projection is not None:
230              left,  bottom = self.projection.Forward(left, bottom)              # Ensure that region lies within the layer's bounding box
231              right, top    = self.projection.Forward(right, top)              # Otherwise projection of the region would lead to incorrect
232                # values.
233          return self.shapetree.find_shapes((left, bottom), (right, top))              clipbbox = self.ClipBoundingBox(bbox)
234                bbox = self.projection.ForwardBBox(clipbbox)
235            return self.store.ShapesInRegion(bbox)
236    
237        def GetClassificationColumn(self):
238            return self.classification_column
239    
240        def SetClassificationColumn(self, column):
241            """Set the column to classifiy on, or None. If column is not None
242            and the column does not exist in the table, raise a ValueError.
243            """
244            if column:
245                columnType = self.GetFieldType(column)
246                if columnType is None:
247                    raise ValueError()
248            changed = self.classification_column != column
249            self.classification_column = column
250            if changed:
251                self.changed(LAYER_CHANGED, self)
252    
253      def HasClassification(self):      def HasClassification(self):
254          return True          return True
# Line 327  class Layer(BaseLayer): Line 257  class Layer(BaseLayer):
257          return self.__classification          return self.__classification
258    
259      def SetClassification(self, clazz):      def SetClassification(self, clazz):
260          """Set the classification to 'clazz'          """Set the classification used by this layer to 'clazz'
261    
262          If 'clazz' is None a default classification is created          If 'clazz' is None a default classification is created.
         """  
263    
264          # prevent infinite recursion when calling SetLayer()          This issues a LAYER_CHANGED event.
265          if self.__setClassLock: return          """
266    
267          self.__setClassLock = True          if self.__classification is not None:
268                self.__classification.Unsubscribe(CLASS_CHANGED,
269                                                  self._classification_changed)
270    
271          if clazz is None:          if clazz is None:
272              if self.__classification is not None:              clazz = classification.Classification()
                 self.__classification.SetLayer(None)  
             self.__classification = classification.Classification()  
         else:  
             self.__classification = clazz  
             try:  
                 self.__classification.SetLayer(self)  
             except ValueError:  
                 self.__setClassLock = False  
                 raise ValueError  
273    
274          self.changed(LAYER_CHANGED, self)          self.__classification = clazz
275            self.__classification.Subscribe(CLASS_CHANGED,
276                                            self._classification_changed)
277    
278          self.__setClassLock = False          self._classification_changed()
279    
280      def ClassChanged(self):      def _classification_changed(self):
281          """Called from the classification object when it has changed."""          """Called from the classification object when it has changed."""
282          self.changed(LAYER_CHANGED, self)          self.changed(LAYER_CHANGED, self)
283    
284      def TreeInfo(self):      def TreeInfo(self):
285          items = []          items = []
286    
287          if hasattr(self, 'filename'):          items.append(_("Filename: %s") % self.ShapeStore().FileName())
             items.append(_("Filename: %s") % self.filename)  
288    
289          if self.Visible():          if self.Visible():
290              items.append(_("Shown"))              items.append(_("Shown"))
# Line 371  class Layer(BaseLayer): Line 294  class Layer(BaseLayer):
294    
295          bbox = self.LatLongBoundingBox()          bbox = self.LatLongBoundingBox()
296          if bbox is not None:          if bbox is not None:
297              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
298          else:          else:
299              items.append(_("Extent (lat-lon):"))              items.append(_("Extent (lat-lon):"))
300          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
# Line 384  class Layer(BaseLayer): Line 307  class Layer(BaseLayer):
307    
308          return (_("Layer '%s'") % self.Title(), items)          return (_("Layer '%s'") % self.Title(), items)
309    
310        def ClipBoundingBox(self, bbox):
311            """ Clip bbox to layer's bounding box.
312    
313            Returns that part of bbox that lies within the layers bounding box.
314            If bbox is completely outside of the layers bounding box, bbox is
315            returned.  It is assumed that bbox has sensible values, i.e. bminx
316            < bmaxx and bminy < bmaxy.
317            """
318            bminx, bminy, bmaxx, bmaxy = bbox
319            lminx, lminy, lmaxx, lmaxy = self.LatLongBoundingBox()
320            if bminx > lmaxx or bmaxx < lminx:
321                left, right = bminx, bmaxx
322            else:
323                left = max(lminx, bminx)
324                right = min(lmaxx, bmaxx)
325            if bminy > lmaxy or bmaxy < lminy:
326                bottom, top = bminy, bmaxy
327            else:
328                bottom = max(lminy, bminy)
329                top = min(lmaxy, bmaxy)
330            
331            return (left, bottom, right, top)
332    
333    
334    if resource.has_gdal_support():
335        import gdal
336        from gdalconst import GA_ReadOnly
337    
338  class RasterLayer(BaseLayer):  class RasterLayer(BaseLayer):
339    
# Line 406  class RasterLayer(BaseLayer): Line 356  class RasterLayer(BaseLayer):
356          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title, visible = visible)
357    
358          self.projection = projection          self.projection = projection
359          self.filename = filename          self.filename = os.path.abspath(filename)
360    
361          self.bbox = -1          self.bbox = -1
362    
363          #          if resource.has_gdal_support():
364          # temporarily open the file so that GDAL can test if it's valid.              #
365          #              # temporarily open the file so that GDAL can test if it's valid.
366          dataset = gdal.Open(self.filename, GA_ReadOnly)              #
367                dataset = gdal.Open(self.filename, GA_ReadOnly)
368    
369          if dataset is None:              if dataset is None:
370              raise IOError()                  raise IOError()
371    
372          self.UnsetModified()          self.UnsetModified()
373    
374      def BoundingBox(self):      def BoundingBox(self):
375          """Return the layer's bounding box in the intrinsic coordinate system.          """Return the layer's bounding box in the intrinsic coordinate system.
376    
377          If the layer has no shapes, return None.          If the there is no support for images, or the file cannot
378            be read, or there is no geographics information available, return None.
379          """          """
380            if not resource.has_gdal_support():
381                return None
382    
383          if self.bbox == -1:          if self.bbox == -1:
384              dataset = gdal.Open(self.filename, GA_ReadOnly)              dataset = gdal.Open(self.filename, GA_ReadOnly)
385              if dataset is None:              if dataset is None:
# Line 463  class RasterLayer(BaseLayer): Line 418  class RasterLayer(BaseLayer):
418          if bbox is None:          if bbox is None:
419              return None              return None
420    
         llx, lly, urx, ury = bbox  
421          if self.projection is not None:          if self.projection is not None:
422              llx, lly = self.projection.Inverse(llx, lly)              bbox = self.projection.InverseBBox(bbox)
             urx, ury = self.projection.Inverse(urx, ury)  
423    
424          return llx, lly, urx, ury          return bbox
425    
426      def GetImageFilename(self):      def GetImageFilename(self):
427          return self.filename          return self.filename
# Line 476  class RasterLayer(BaseLayer): Line 429  class RasterLayer(BaseLayer):
429      def TreeInfo(self):      def TreeInfo(self):
430          items = []          items = []
431    
432            items.append(_("Filename: %s") % self.GetImageFilename())
433    
434          if self.Visible():          if self.Visible():
435              items.append(_("Shown"))              items.append(_("Shown"))
436          else:          else:
437              items.append(_("Hidden"))              items.append(_("Hidden"))
         items.append(_("Shapes: %d") % self.NumShapes())  
438    
439          bbox = self.LatLongBoundingBox()          bbox = self.LatLongBoundingBox()
440          if bbox is not None:          if bbox is not None:

Legend:
Removed from v.1117  
changed lines
  Added in v.2339

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26