/[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 171 by bh, Tue May 14 14:16:24 2002 UTC revision 2339 by silke, Fri Aug 20 16:59:21 2004 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 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]>
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  import shapelib, shptree  from Thuban import _
16    
17  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18       LAYER_VISIBILITY_CHANGED       LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
19    
20  from color import Color  import classification
 # Some predefined colors for internal use  
 _black = Color(0, 0, 0)  
   
   
 from table import Table  
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  class Shape:  import resource
   
     """Represent one shape"""  
   
     def __init__(self, points):  
         self.points = points  
         #self.compute_bbox()  
   
     def compute_bbox(self):  
         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)  
   
     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 65  class BaseLayer(TitledObject, Modifiable Line 34  class BaseLayer(TitledObject, Modifiable
34    
35      """Base class for the layers."""      """Base class for the layers."""
36    
37      def __init__(self, title, visible = 1):      def __init__(self, title, visible = True, projection = None):
38          """Initialize the layer.          """Initialize the layer.
39    
40          title -- the title          title -- the title
# Line 74  class BaseLayer(TitledObject, Modifiable Line 43  class BaseLayer(TitledObject, Modifiable
43          TitledObject.__init__(self, title)          TitledObject.__init__(self, title)
44          Modifiable.__init__(self)          Modifiable.__init__(self)
45          self.visible = visible          self.visible = visible
46            self.projection = projection
47    
48      def Visible(self):      def Visible(self):
49          """Return true if layer is visible"""          """Return true if layer is visible"""
# Line 83  class BaseLayer(TitledObject, Modifiable Line 53  class BaseLayer(TitledObject, Modifiable
53          """Set the layer's visibility."""          """Set the layer's visibility."""
54          self.visible = visible          self.visible = visible
55          self.issue(LAYER_VISIBILITY_CHANGED, self)          self.issue(LAYER_VISIBILITY_CHANGED, self)
56            
57                def HasClassification(self):
58            """Determine if this layer support classifications."""
59            return False
60    
61        def HasShapes(self):
62            """Determine if this layer supports shapes."""
63            return False
64    
65        def GetProjection(self):
66            """Return the layer's projection."""
67            return self.projection
68    
69        def SetProjection(self, projection):
70            """Set the layer's projection"""
71            self.projection = projection
72            self.changed(LAYER_PROJECTION_CHANGED, self)
73    
74  class Layer(BaseLayer):  class Layer(BaseLayer):
75    
76      """Represent the information of one geodata file (currently a shapefile)      """Represent the information of one geodata file (currently a shapefile)
# Line 92  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:
85    
86          TITLE_CHANGED -- The title has changed.          TITLE_CHANGED -- The title has changed.
87          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
         LAYER_LEGEND_CHANGED -- the fill or stroke attributes have changed  
   
88      """      """
89    
90      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
91                   fill = None, stroke = _black, stroke_width = 1, visible = 1):                   fill = Transparent,
92                     stroke = Black,
93                     lineWidth = 1,
94                     visible = True):
95          """Initialize the layer.          """Initialize the layer.
96    
97          title -- the title          title -- the title
98          filename -- the name of the shapefile          data -- datastore object for the shape data shown by the layer
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 None if the shapes are not filled          fill -- the fill color or Transparent if the shapes are
103          stroke -- the stroke color or None if the shapes are not stroked                  not filled
104            stroke -- the stroke color or Transparent if the shapes
105                    are not stroked
106          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
107    
108          colors are expected to be instances of Color class          colors are expected to be instances of Color class
109          """          """
110          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title,
111          self.filename = filename                                   visible = visible,
112          self.projection = projection                                   projection = projection)
113          self.fill = fill  
114          self.stroke = stroke          self.__classification = None
115          self.stroke_width = stroke_width          self.store = None
116          self.shapefile = None  
117          self.shapetree = None          self.SetShapeStore(data)
118          self.open_shapefile()  
119          # shapetable is the table associated with the shapefile, while          self.classification_column = None
120          # table is the default table used to look up attributes for          self.SetClassificationColumn(None)
121          # display          self.SetClassification(None)
122          self.shapetable = Table(filename)  
123          self.table = self.shapetable          self.__classification.SetDefaultLineColor(stroke)
124            self.__classification.SetDefaultLineWidth(lineWidth)
125      def open_shapefile(self):          self.__classification.SetDefaultFill(fill)
126          if self.shapefile is None:  
127              self.shapefile = shapelib.ShapeFile(self.filename)          self.UnsetModified()
128              numshapes, shapetype, mins, maxs = self.shapefile.info()  
129              self.numshapes = numshapes      def SetShapeStore(self, store):
130              self.shapetype = shapelib_shapetypes[shapetype]          # Set the classification to None if there is a classification
131              self.bbox = mins[:2] + maxs[:2]          # 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
133              # estimate a good depth for the quad tree. Each depth          # FIXME: Maybe we should keep it the same if the type is
134              # multiplies the number of nodes by four, therefore we          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
135              # basically take the base 4 logarithm of the number of          if self.__classification is not None:
136              # shapes.              columnname = self.classification_column
137              if self.numshapes < 4:              columntype = self.GetFieldType(columnname)
138                  maxdepth = 1              table = store.Table()
139              else:              if (columnname is not None
140                  maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))                  and (not table.HasColumn(columnname)
141                         or table.Column(columnname).type != columntype)):
142              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,                  self.SetClassification(None)
143                                               maxdepth)  
144            self.store = store
145    
146            self.changed(LAYER_SHAPESTORE_REPLACED, self)
147    
148        def ShapeStore(self):
149            return self.store
150    
151        def Destroy(self):
152            BaseLayer.Destroy(self)
153            if self.__classification is not None:
154                self.__classification.Unsubscribe(CLASS_CHANGED,
155                                                  self._classification_changed)
156    
157      def BoundingBox(self):      def BoundingBox(self):
158          """Return the bounding box of the layer's shapes in their default          """Return the layer's bounding box in the intrinsic coordinate system.
159          coordinate system"""  
160          self.open_shapefile()          If the layer has no shapes, return None.
161          return self.bbox          """
162            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.
166          llx, lly, urx, ury = self.BoundingBox()  
167          if self.projection is not None:          Return None, if the layer doesn't contain any shapes.
168              llx, lly = self.projection.Inverse(llx, lly)          """
169              urx, ury = self.projection.Inverse(urx, ury)          bbox = self.BoundingBox()
170          return llx, lly, urx, ury          if bbox is not None and self.projection is not None:
171                bbox = self.projection.InverseBBox(bbox)
172            return bbox
173    
174        def ShapesBoundingBox(self, shapes):
175            """Return a bounding box in lat/long coordinates for the given
176            list of shape ids.
177    
178            If shapes is None or empty, return None.
179            """
180    
181            if shapes is None or len(shapes) == 0: return None
182    
183            xs = []
184            ys = []
185    
186            for id in shapes:
187                bbox = self.Shape(id).compute_bbox()
188                if self.projection is not None:
189                    bbox = self.projection.InverseBBox(bbox)
190                left, bottom, right, top = bbox
191                xs.append(left); xs.append(right)
192                ys.append(bottom); ys.append(top)
193    
194            return (min(xs), min(ys), max(xs), max(ys))
195    
196    
197        def GetFieldType(self, fieldName):
198            if self.store:
199                table = self.store.Table()
200                if table.HasColumn(fieldName):
201                    return table.Column(fieldName).type
202            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          self.open_shapefile()          return self.store.NumShapes()
         return self.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          self.open_shapefile()          return self.store.ShapeType()
         return self.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          self.open_shapefile()          return self.store.Shape(index)
         shape = self.shapefile.read_object(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 the coordinate          The bbox parameter should be the bounding box as a tuple in the
227          system used by the layer's shapefile.          form (minx, miny, maxx, maxy) in unprojected coordinates.
228          """          """
229          left, bottom, right, top = box          if self.projection is not None:
230          return self.shapetree.find_shapes((left, bottom), (right, top))              # Ensure that region lies within the layer's bounding box
231                # Otherwise projection of the region would lead to incorrect
232                # values.
233                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):
254            return True
255    
256        def GetClassification(self):
257            return self.__classification
258    
259        def SetClassification(self, clazz):
260            """Set the classification used by this layer to 'clazz'
261    
262            If 'clazz' is None a default classification is created.
263    
264            This issues a LAYER_CHANGED event.
265            """
266    
267            if self.__classification is not None:
268                self.__classification.Unsubscribe(CLASS_CHANGED,
269                                                  self._classification_changed)
270    
271            if clazz is None:
272                clazz = classification.Classification()
273    
274            self.__classification = clazz
275            self.__classification.Subscribe(CLASS_CHANGED,
276                                            self._classification_changed)
277    
278            self._classification_changed()
279    
280        def _classification_changed(self):
281            """Called from the classification object when it has changed."""
282            self.changed(LAYER_CHANGED, self)
283    
284        def TreeInfo(self):
285            items = []
286    
287            items.append(_("Filename: %s") % self.ShapeStore().FileName())
288    
289            if self.Visible():
290                items.append(_("Shown"))
291            else:
292                items.append(_("Hidden"))
293            items.append(_("Shapes: %d") % self.NumShapes())
294    
295            bbox = self.LatLongBoundingBox()
296            if bbox is not None:
297                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
298            else:
299                items.append(_("Extent (lat-lon):"))
300            items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
301    
302            if self.projection and len(self.projection.params) > 0:
303                items.append((_("Projection"),
304                            [str(param) for param in self.projection.params]))
305    
306            items.append(self.__classification)
307    
308            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):
339    
340        def __init__(self, title, filename, projection = None, visible = True):
341            """Initialize the Raster Layer.
342    
343            title -- title for the layer.
344    
345            filename -- file name of the source image.
346    
347            projection -- Projection object describing the projection which
348                          the source image is in.
349    
350            visible -- True is the layer should initially be visible.
351    
352            Throws IOError if the filename is invalid or points to a file that
353            is not in a format GDAL can use.
354            """
355    
356            BaseLayer.__init__(self, title, visible = visible)
357    
     def SetProjection(self, projection):  
         """Set the layer's projection"""  
358          self.projection = projection          self.projection = projection
359          self.changed(LAYER_PROJECTION_CHANGED, self)          self.filename = os.path.abspath(filename)
360    
361            self.bbox = -1
362    
363            if resource.has_gdal_support():
364                #
365                # temporarily open the file so that GDAL can test if it's valid.
366                #
367                dataset = gdal.Open(self.filename, GA_ReadOnly)
368    
369                if dataset is None:
370                    raise IOError()
371    
372            self.UnsetModified()
373    
374        def BoundingBox(self):
375            """Return the layer's bounding box in the intrinsic coordinate system.
376    
377            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:
384                dataset = gdal.Open(self.filename, GA_ReadOnly)
385                if dataset is None:
386                    self.bbox = None
387                else:
388                    geotransform = dataset.GetGeoTransform()
389                    if geotransform is None:
390                        return None
391    
392                    x = 0
393                    y = dataset.RasterYSize
394                    left = geotransform[0] +        \
395                           geotransform[1] * x +    \
396                           geotransform[2] * y
397    
398                    bottom = geotransform[3] +      \
399                             geotransform[4] * x +  \
400                             geotransform[5] * y
401    
402                    x = dataset.RasterXSize
403                    y = 0
404                    right = geotransform[0] +       \
405                            geotransform[1] * x +   \
406                            geotransform[2] * y
407    
408                    top = geotransform[3] +         \
409                          geotransform[4] * x +     \
410                          geotransform[5] * y
411    
412                    self.bbox = (left, bottom, right, top)
413    
414            return self.bbox
415    
416        def LatLongBoundingBox(self):
417            bbox = self.BoundingBox()
418            if bbox is None:
419                return None
420    
421            if self.projection is not None:
422                bbox = self.projection.InverseBBox(bbox)
423    
424            return bbox
425    
426        def GetImageFilename(self):
427            return self.filename
428    
429        def TreeInfo(self):
430            items = []
431    
432            items.append(_("Filename: %s") % self.GetImageFilename())
433    
434            if self.Visible():
435                items.append(_("Shown"))
436            else:
437                items.append(_("Hidden"))
438    
439            bbox = self.LatLongBoundingBox()
440            if bbox is not None:
441                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
442            else:
443                items.append(_("Extent (lat-lon):"))
444    
445            if self.projection and len(self.projection.params) > 0:
446                items.append((_("Projection"),
447                            [str(param) for param in self.projection.params]))
448    
449            return (_("Layer '%s'") % self.Title(), items)
450    
     def SetFill(self, fill):  
         """Set the layer's fill color. None means the shapes are not filled"""  
         self.fill = fill  
         self.changed(LAYER_LEGEND_CHANGED, self)  
   
     def SetStroke(self, stroke):  
         """Set the layer's stroke color. None means the shapes are not  
         stroked."""  
         self.stroke = stroke  
         self.changed(LAYER_LEGEND_CHANGED, self)  
   
     def SetStrokeWidth(self, width):  
         """Set the layer's stroke width."""  
         self.stroke_width = width  
         self.changed(LAYER_LEGEND_CHANGED, self)  

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26