/[thuban]/trunk/thuban/Thuban/Model/layer.py
ViewVC logotype

Diff of /trunk/thuban/Thuban/Model/layer.py

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

revision 147 by bh, Tue May 7 16:39:52 2002 UTC revision 2228 by bh, Thu Jun 3 15:17:54 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  #  #
6  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
7  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
8    
9  __version__ = "$Revision$"  __version__ = "$Revision$"
10    
11  import shapelib, shptree  import os
12    import warnings
13    
14  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from Thuban import _
      LAYER_VISIBILITY_CHANGED  
15    
16  from color import Color  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17  # Some predefined colors for internal use       LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
 _black = Color(0, 0, 0)  
18    
19    import classification
20    
21  from table import Table  from color import Transparent, Black
   
22  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
23    from data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
24    
25  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  
   
   
26    
 # 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}  
27    
28  shapetype_names = {SHAPETYPE_POINT: "Point",  shapetype_names = {SHAPETYPE_POINT: "Point",
29                     SHAPETYPE_ARC: "Arc",                     SHAPETYPE_ARC: "Arc",
# Line 63  class BaseLayer(TitledObject, Modifiable Line 33  class BaseLayer(TitledObject, Modifiable
33    
34      """Base class for the layers."""      """Base class for the layers."""
35    
36      def __init__(self, title, visible = 1):      def __init__(self, title, visible = True, projection = None):
37          """Initialize the layer.          """Initialize the layer.
38    
39          title -- the title          title -- the title
# Line 72  class BaseLayer(TitledObject, Modifiable Line 42  class BaseLayer(TitledObject, Modifiable
42          TitledObject.__init__(self, title)          TitledObject.__init__(self, title)
43          Modifiable.__init__(self)          Modifiable.__init__(self)
44          self.visible = visible          self.visible = visible
45            self.projection = projection
46    
47      def Visible(self):      def Visible(self):
48          """Return true if layer is visible"""          """Return true if layer is visible"""
# Line 81  class BaseLayer(TitledObject, Modifiable Line 52  class BaseLayer(TitledObject, Modifiable
52          """Set the layer's visibility."""          """Set the layer's visibility."""
53          self.visible = visible          self.visible = visible
54          self.issue(LAYER_VISIBILITY_CHANGED, self)          self.issue(LAYER_VISIBILITY_CHANGED, self)
55            
56                def HasClassification(self):
57            """Determine if this layer support classifications."""
58            return False
59    
60        def HasShapes(self):
61            """Determine if this layer supports shapes."""
62            return False
63    
64        def GetProjection(self):
65            """Return the layer's projection."""
66            return self.projection
67    
68        def SetProjection(self, projection):
69            """Set the layer's projection"""
70            self.projection = projection
71            self.changed(LAYER_PROJECTION_CHANGED, self)
72    
73  class Layer(BaseLayer):  class Layer(BaseLayer):
74    
75      """Represent the information of one geodata file (currently a shapefile)      """Represent the information of one geodata file (currently a shapefile)
# Line 90  class Layer(BaseLayer): Line 77  class Layer(BaseLayer):
77      All children of the layer have the same type.      All children of the layer have the same type.
78    
79      A layer has fill and stroke colors. Colors should be instances of      A layer has fill and stroke colors. Colors should be instances of
80      Color. They can also be None, indicating no fill or no stroke.      Color. They can also be Transparent, indicating no fill or no stroke.
81    
82      The layer objects send the following events, all of which have the      The layer objects send the following events, all of which have the
83      layer object as parameter:      layer object as parameter:
84    
85          TITLE_CHANGED -- The title has changed.          TITLE_CHANGED -- The title has changed.
86          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
         LAYER_LEGEND_CHANGED -- the fill or stroke attributes have changed  
   
87      """      """
88    
89      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
90                   fill = None, stroke = _black, stroke_width = 1, visible = 1):                   fill = Transparent,
91                     stroke = Black,
92                     lineWidth = 1,
93                     visible = True):
94          """Initialize the layer.          """Initialize the layer.
95    
96          title -- the title          title -- the title
97          filename -- the name of the shapefile          data -- datastore object for the shape data shown by the layer
98          projection -- the projection object. Its Inverse method is          projection -- the projection object. Its Inverse method is
99                 assumed to map the layer's coordinates to lat/long                 assumed to map the layer's coordinates to lat/long
100                 coordinates                 coordinates
101          fill -- the fill color or None if the shapes are not filled          fill -- the fill color or Transparent if the shapes are
102          stroke -- the stroke color or None if the shapes are not stroked                  not filled
103            stroke -- the stroke color or Transparent if the shapes
104                    are not stroked
105          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
106    
107          colors are expected to be instances of Color class          colors are expected to be instances of Color class
108          """          """
109          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title,
110          self.filename = filename                                   visible = visible,
111          self.projection = projection                                   projection = projection)
112          self.fill = fill  
113          self.stroke = stroke          self.__classification = None
114          self.stroke_width = stroke_width          self.store = None
115          self.shapefile = None  
116          self.shapetree = None          self.SetShapeStore(data)
117          self.open_shapefile()  
118          # shapetable is the table associated with the shapefile, while          self.classification_column = None
119          # table is the default table used to look up attributes for          self.SetClassificationColumn(None)
120          # display          self.SetClassification(None)
121          self.shapetable = Table(filename)  
122          self.table = self.shapetable          self.__classification.SetDefaultLineColor(stroke)
123            self.__classification.SetDefaultLineWidth(lineWidth)
124      def open_shapefile(self):          self.__classification.SetDefaultFill(fill)
125          if self.shapefile is None:  
126              self.shapefile = shapelib.ShapeFile(self.filename)          self.UnsetModified()
127              numshapes, shapetype, mins, maxs = self.shapefile.info()  
128              self.numshapes = numshapes      def SetShapeStore(self, store):
129              self.shapetype = shapelib_shapetypes[shapetype]          # Set the classification to None if there is a classification
130              self.bbox = mins[:2] + maxs[:2]          # and the new shapestore doesn't have a table with a suitable
131              #print "building tree for", self.filename, "..."          # column, i.e one with the same name and type as before
132              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2, 0)          # FIXME: Maybe we should keep it the same if the type is
133              #print "done"          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
134            if self.__classification is not None:
135                columnname = self.classification_column
136                columntype = self.GetFieldType(columnname)
137                table = store.Table()
138                if (columnname is not None
139                    and (not table.HasColumn(columnname)
140                         or table.Column(columnname).type != columntype)):
141                    self.SetClassification(None)
142    
143            self.store = store
144    
145            self.changed(LAYER_SHAPESTORE_REPLACED, self)
146    
147        def ShapeStore(self):
148            return self.store
149    
150        def Destroy(self):
151            BaseLayer.Destroy(self)
152            if self.__classification is not None:
153                self.__classification.Unsubscribe(CLASS_CHANGED,
154                                                  self._classification_changed)
155    
156      def BoundingBox(self):      def BoundingBox(self):
157          """Return the bounding box of the layer's shapes in their default          """Return the layer's bounding box in the intrinsic coordinate system.
158          coordinate system"""  
159          self.open_shapefile()          If the layer has no shapes, return None.
160          return self.bbox          """
161            return self.store.BoundingBox()
162    
163      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
164          """Return the layer's bounding box in lat/long coordinates"""          """Return the layer's bounding box in lat/long coordinates.
165          llx, lly, urx, ury = self.BoundingBox()  
166          if self.projection is not None:          Return None, if the layer doesn't contain any shapes.
167              llx, lly = self.projection.Inverse(llx, lly)          """
168              urx, ury = self.projection.Inverse(urx, ury)          bbox = self.BoundingBox()
169          return llx, lly, urx, ury          if bbox is not None and self.projection is not None:
170                bbox = self.projection.InverseBBox(bbox)
171            return bbox
172    
173        def ShapesBoundingBox(self, shapes):
174            """Return a bounding box in lat/long coordinates for the given
175            list of shape ids.
176    
177            If shapes is None or empty, return None.
178            """
179    
180            if shapes is None or len(shapes) == 0: return None
181    
182            xs = []
183            ys = []
184    
185            for id in shapes:
186                bbox = self.Shape(id).compute_bbox()
187                if self.projection is not None:
188                    bbox = self.projection.InverseBBox(bbox)
189                left, bottom, right, top = bbox
190                xs.append(left); xs.append(right)
191                ys.append(bottom); ys.append(top)
192    
193            return (min(xs), min(ys), max(xs), max(ys))
194    
195    
196        def GetFieldType(self, fieldName):
197            if self.store:
198                table = self.store.Table()
199                if table.HasColumn(fieldName):
200                    return table.Column(fieldName).type
201            return None
202    
203        def HasShapes(self):
204            return True
205    
206      def NumShapes(self):      def NumShapes(self):
207          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
208          self.open_shapefile()          return self.store.NumShapes()
         return self.numshapes  
209    
210      def ShapeType(self):      def ShapeType(self):
211          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
212          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.  
213            The return value is one of the SHAPETYPE_* constants defined in
214            Thuban.Model.data.
215          """          """
216          self.open_shapefile()          return self.store.ShapeType()
         return self.shapetype  
217    
218      def Shape(self, index):      def Shape(self, index):
219          """Return the shape with index index"""          """Return the shape with index index"""
220          self.open_shapefile()          return self.store.Shape(index)
221          shape = self.shapefile.read_object(index)  
222          if self.shapetype == SHAPETYPE_POINT:      def ShapesInRegion(self, bbox):
223              points = shape.vertices()          """Return an iterable over the shapes that overlap the bounding box.
224    
225            The bbox parameter should be the bounding box as a tuple in the
226            form (minx, miny, maxx, maxy) in unprojected coordinates.
227            """
228            if self.projection is not None:
229                bbox = self.projection.ForwardBBox(bbox)
230            return self.store.ShapesInRegion(bbox)
231    
232        def GetClassificationColumn(self):
233            return self.classification_column
234    
235        def SetClassificationColumn(self, column):
236            """Set the column to classifiy on, or None. If column is not None
237            and the column does not exist in the table, raise a ValueError.
238            """
239            if column:
240                columnType = self.GetFieldType(column)
241                if columnType is None:
242                    raise ValueError()
243            changed = self.classification_column != column
244            self.classification_column = column
245            if changed:
246                self.changed(LAYER_CHANGED, self)
247    
248        def HasClassification(self):
249            return True
250    
251        def GetClassification(self):
252            return self.__classification
253    
254        def SetClassification(self, clazz):
255            """Set the classification used by this layer to 'clazz'
256    
257            If 'clazz' is None a default classification is created.
258    
259            This issues a LAYER_CHANGED event.
260            """
261    
262            if self.__classification is not None:
263                self.__classification.Unsubscribe(CLASS_CHANGED,
264                                                  self._classification_changed)
265    
266            if clazz is None:
267                clazz = classification.Classification()
268    
269            self.__classification = clazz
270            self.__classification.Subscribe(CLASS_CHANGED,
271                                            self._classification_changed)
272    
273            self._classification_changed()
274    
275        def _classification_changed(self):
276            """Called from the classification object when it has changed."""
277            self.changed(LAYER_CHANGED, self)
278    
279        def TreeInfo(self):
280            items = []
281    
282            items.append(_("Filename: %s") % self.ShapeStore().FileName())
283    
284            if self.Visible():
285                items.append(_("Shown"))
286            else:
287                items.append(_("Hidden"))
288            items.append(_("Shapes: %d") % self.NumShapes())
289    
290            bbox = self.LatLongBoundingBox()
291            if bbox is not None:
292                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
293          else:          else:
294              #for poly in shape.vertices():              items.append(_("Extent (lat-lon):"))
295              poly = shape.vertices()[0]          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
296              points = []  
297              for x, y in poly:          if self.projection and len(self.projection.params) > 0:
298                  points.append((x, y))              items.append((_("Projection"),
299          return Shape(points)                          [str(param) for param in self.projection.params]))
300    
301            items.append(self.__classification)
302    
303            return (_("Layer '%s'") % self.Title(), items)
304    
305    
306    if resource.has_gdal_support():
307        import gdal
308        from gdalconst import GA_ReadOnly
309    
310    class RasterLayer(BaseLayer):
311    
312        def __init__(self, title, filename, projection = None, visible = True):
313            """Initialize the Raster Layer.
314    
315            title -- title for the layer.
316    
317      def ShapesInRegion(self, box):          filename -- file name of the source image.
         """Return the ids of the shapes that overlap the box.  
318    
319          Box is a tuple (left, bottom, right, top) in the coordinate          projection -- Projection object describing the projection which
320          system used by the layer's shapefile.                        the source image is in.
321    
322            visible -- True is the layer should initially be visible.
323    
324            Throws IOError if the filename is invalid or points to a file that
325            is not in a format GDAL can use.
326          """          """
         left, bottom, right, top = box  
         return self.shapetree.find_shapes((left, bottom), (right, top))  
327    
328      def SetProjection(self, projection):          BaseLayer.__init__(self, title, visible = visible)
329          """Set the layer's projection"""  
330          self.projection = projection          self.projection = projection
331          self.changed(LAYER_PROJECTION_CHANGED, self)          self.filename = os.path.abspath(filename)
332    
333            self.bbox = -1
334    
335            if resource.has_gdal_support():
336                #
337                # temporarily open the file so that GDAL can test if it's valid.
338                #
339                dataset = gdal.Open(self.filename, GA_ReadOnly)
340    
341                if dataset is None:
342                    raise IOError()
343    
344            self.UnsetModified()
345    
346        def BoundingBox(self):
347            """Return the layer's bounding box in the intrinsic coordinate system.
348    
349            If the there is no support for images, or the file cannot
350            be read, or there is no geographics information available, return None.
351            """
352            if not resource.has_gdal_support():
353                return None
354    
355            if self.bbox == -1:
356                dataset = gdal.Open(self.filename, GA_ReadOnly)
357                if dataset is None:
358                    self.bbox = None
359                else:
360                    geotransform = dataset.GetGeoTransform()
361                    if geotransform is None:
362                        return None
363    
364                    x = 0
365                    y = dataset.RasterYSize
366                    left = geotransform[0] +        \
367                           geotransform[1] * x +    \
368                           geotransform[2] * y
369    
370                    bottom = geotransform[3] +      \
371                             geotransform[4] * x +  \
372                             geotransform[5] * y
373    
374                    x = dataset.RasterXSize
375                    y = 0
376                    right = geotransform[0] +       \
377                            geotransform[1] * x +   \
378                            geotransform[2] * y
379    
380                    top = geotransform[3] +         \
381                          geotransform[4] * x +     \
382                          geotransform[5] * y
383    
384                    self.bbox = (left, bottom, right, top)
385    
386            return self.bbox
387    
388        def LatLongBoundingBox(self):
389            bbox = self.BoundingBox()
390            if bbox is None:
391                return None
392    
393            if self.projection is not None:
394                bbox = self.projection.InverseBBox(bbox)
395    
396            return bbox
397    
398        def GetImageFilename(self):
399            return self.filename
400    
401        def TreeInfo(self):
402            items = []
403    
404            items.append(_("Filename: %s") % self.GetImageFilename())
405    
406            if self.Visible():
407                items.append(_("Shown"))
408            else:
409                items.append(_("Hidden"))
410    
411            bbox = self.LatLongBoundingBox()
412            if bbox is not None:
413                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
414            else:
415                items.append(_("Extent (lat-lon):"))
416    
417            if self.projection and len(self.projection.params) > 0:
418                items.append((_("Projection"),
419                            [str(param) for param in self.projection.params]))
420    
421            return (_("Layer '%s'") % self.Title(), items)
422    
     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.147  
changed lines
  Added in v.2228

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26