/[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 701 by bh, Thu Apr 17 16:18:48 2003 UTC revision 2228 by bh, Thu Jun 3 15:17:54 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]>
# Line 9  Line 9 
9  __version__ = "$Revision$"  __version__ = "$Revision$"
10    
11  import os  import os
12  from math import log, ceil  import warnings
13    
14  from Thuban import _  from Thuban import _
15    
 import shapelib, shptree  
   
16  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17       LAYER_CHANGED       LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED, CLASS_CHANGED
   
 from color import Color  
18    
19  import classification  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    
27    
   
 # 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}  
   
28  shapetype_names = {SHAPETYPE_POINT: "Point",  shapetype_names = {SHAPETYPE_POINT: "Point",
29                     SHAPETYPE_ARC: "Arc",                     SHAPETYPE_ARC: "Arc",
30                     SHAPETYPE_POLYGON: "Polygon"}                     SHAPETYPE_POLYGON: "Polygon"}
# Line 68  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 77  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 87  class BaseLayer(TitledObject, Modifiable Line 53  class BaseLayer(TitledObject, Modifiable
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    
# Line 95  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:
# Line 104  class Layer(BaseLayer): Line 86  class Layer(BaseLayer):
86          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
87      """      """
88    
89      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
90                   fill = Color.Transparent,                   fill = Transparent,
91                   stroke = Color.Black,                   stroke = Black,
92                   lineWidth = 1,                   lineWidth = 1,
93                   visible = 1):                   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 Color.Transparent if the shapes are          fill -- the fill color or Transparent if the shapes are
102                  not filled                  not filled
103          stroke -- the stroke color or Color.Transparent if the shapes          stroke -- the stroke color or Transparent if the shapes
104                  are not stroked                  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                                     visible = visible,
111                                     projection = projection)
112    
113          # Make the filename absolute. The filename will be          self.__classification = None
114          # interpreted relative to that anyway, but when saving a          self.store = None
         # session we need to compare absolute paths and it's usually  
         # safer to always work with absolute paths.  
         self.filename = os.path.abspath(filename)  
   
         self.projection = projection  
         self.shapefile = None  
         self.shapetree = None  
         self.open_shapefile()  
         # shapetable is the table associated with the shapefile, while  
         # table is the default table used to look up attributes for  
         # display  
         self.shapetable = Table(filename)  
         self.table = self.shapetable  
   
         #  
         # this is really important so that when the classification class  
         # tries to set its parent layer the variable will exist  
         #  
         self.__classification = None  
         self.__setClassLock = False  
115    
116            self.SetShapeStore(data)
117    
118            self.classification_column = None
119            self.SetClassificationColumn(None)
120          self.SetClassification(None)          self.SetClassification(None)
121    
122          self.__classification.SetDefaultLineColor(stroke)          self.__classification.SetDefaultLineColor(stroke)
123          self.__classification.SetDefaultLineWidth(lineWidth)          self.__classification.SetDefaultLineWidth(lineWidth)
124          self.__classification.SetDefaultFill(fill)          self.__classification.SetDefaultFill(fill)
         self.__classification.SetLayer(self)  
125    
126          self.UnsetModified()          self.UnsetModified()
127    
128      def open_shapefile(self):      def SetShapeStore(self, store):
129          if self.shapefile is None:          # Set the classification to None if there is a classification
130              self.shapefile = shapelib.ShapeFile(self.filename)          # and the new shapestore doesn't have a table with a suitable
131              numshapes, shapetype, mins, maxs = self.shapefile.info()          # column, i.e one with the same name and type as before
132              self.numshapes = numshapes          # FIXME: Maybe we should keep it the same if the type is
133              self.shapetype = shapelib_shapetypes[shapetype]          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
134            if self.__classification is not None:
135              # if there are shapes, set the bbox accordinly. Otherwise              columnname = self.classification_column
136              # set it to None.              columntype = self.GetFieldType(columnname)
137              if self.numshapes:              table = store.Table()
138                  self.bbox = mins[:2] + maxs[:2]              if (columnname is not None
139              else:                  and (not table.HasColumn(columnname)
140                  self.bbox = None                       or table.Column(columnname).type != columntype)):
141                    self.SetClassification(None)
142    
143              # estimate a good depth for the quad tree. Each depth          self.store = store
144              # multiplies the number of nodes by four, therefore we  
145              # basically take the base 4 logarithm of the number of          self.changed(LAYER_SHAPESTORE_REPLACED, self)
             # shapes.  
             if self.numshapes < 4:  
                 maxdepth = 1  
             else:  
                 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))  
146    
147              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,      def ShapeStore(self):
148                                               maxdepth)          return self.store
149    
150      def Destroy(self):      def Destroy(self):
151          BaseLayer.Destroy(self)          BaseLayer.Destroy(self)
152          if self.shapefile is not None:          if self.__classification is not None:
153              self.shapefile.close()              self.__classification.Unsubscribe(CLASS_CHANGED,
154              self.shapefile = None                                                self._classification_changed)
             self.shapetree = None  
         self.SetClassification(None)  
         self.table.Destroy()  
155    
156      def BoundingBox(self):      def BoundingBox(self):
157          """Return the layer's bounding box in the intrinsic coordinate system.          """Return the layer's bounding box in the intrinsic coordinate system.
158    
159          If the layer has no shapes, return None.          If the layer has no shapes, return None.
160          """          """
161          return self.bbox          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.
# Line 207  class Layer(BaseLayer): Line 166  class Layer(BaseLayer):
166          Return None, if the layer doesn't contain any shapes.          Return None, if the layer doesn't contain any shapes.
167          """          """
168          bbox = self.BoundingBox()          bbox = self.BoundingBox()
169          if bbox is not None:          if bbox is not None and self.projection is not None:
170              llx, lly, urx, ury = bbox              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:              if self.projection is not None:
188                  llx, lly = self.projection.Inverse(llx, lly)                  bbox = self.projection.InverseBBox(bbox)
189                  urx, ury = self.projection.Inverse(urx, ury)              left, bottom, right, top = bbox
190              return llx, lly, urx, ury              xs.append(left); xs.append(right)
191          else:              ys.append(bottom); ys.append(top)
192              return None  
193            return (min(xs), min(ys), max(xs), max(ys))
194    
195    
196      def GetFieldType(self, fieldName):      def GetFieldType(self, fieldName):
197          info = self.table.field_info_by_name(fieldName)          if self.store:
198          if info is not None:              table = self.store.Table()
199              return info[0]              if table.HasColumn(fieldName):
200          else:                  return table.Column(fieldName).type
201              return None          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          return self.numshapes          return self.store.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          return self.shapetype          return self.store.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          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)  
221    
222      def ShapesInRegion(self, box):      def ShapesInRegion(self, bbox):
223          """Return the ids of the shapes that overlap the box.          """Return an iterable over the shapes that overlap the bounding box.
224    
225          Box is a tuple (left, bottom, right, top) in the coordinate          The bbox parameter should be the bounding box as a tuple in the
226          system used by the layer's shapefile.          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          left, bottom, right, top = box          if column:
240          return self.shapetree.find_shapes((left, bottom), (right, top))              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 SetProjection(self, projection):      def HasClassification(self):
249          """Set the layer's projection"""          return True
         self.projection = projection  
         self.changed(LAYER_PROJECTION_CHANGED, self)  
250    
251      def GetClassification(self):      def GetClassification(self):
252          return self.__classification          return self.__classification
253    
254      def SetClassification(self, clazz):      def SetClassification(self, clazz):
255          """Set the classification to 'clazz'          """Set the classification used by this layer to 'clazz'
256    
257          If 'clazz' is None a default classification is created          If 'clazz' is None a default classification is created.
         """  
258    
259          # prevent infinite recursion when calling SetLayer()          This issues a LAYER_CHANGED event.
260          if self.__setClassLock: return          """
261    
262          self.__setClassLock = True          if self.__classification is not None:
263                self.__classification.Unsubscribe(CLASS_CHANGED,
264                                                  self._classification_changed)
265    
266          if clazz is None:          if clazz is None:
267              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  
268    
269          self.changed(LAYER_CHANGED, self)          self.__classification = clazz
270            self.__classification.Subscribe(CLASS_CHANGED,
271                                            self._classification_changed)
272    
273          self.__setClassLock = False          self._classification_changed()
274    
275      def ClassChanged(self):      def _classification_changed(self):
276          """Called from the classification object when it has changed."""          """Called from the classification object when it has changed."""
277          self.changed(LAYER_CHANGED, self)          self.changed(LAYER_CHANGED, self)
278    
279      def TreeInfo(self):      def TreeInfo(self):
280          items = []          items = []
281    
282            items.append(_("Filename: %s") % self.ShapeStore().FileName())
283    
284          if self.Visible():          if self.Visible():
285              items.append(_("Shown"))              items.append(_("Shown"))
286          else:          else:
# Line 307  class Layer(BaseLayer): Line 289  class Layer(BaseLayer):
289    
290          bbox = self.LatLongBoundingBox()          bbox = self.LatLongBoundingBox()
291          if bbox is not None:          if bbox is not None:
292              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % tuple(bbox))
293          else:          else:
294              items.append(_("Extent (lat-lon):"))              items.append(_("Extent (lat-lon):"))
295          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
296    
297            if self.projection and len(self.projection.params) > 0:
298                items.append((_("Projection"),
299                            [str(param) for param in self.projection.params]))
300    
301          items.append(self.__classification)          items.append(self.__classification)
302    
303          return (_("Layer '%s'") % self.Title(), items)          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            filename -- file name of the source image.
318    
319            projection -- Projection object describing the projection which
320                          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            """
327    
328            BaseLayer.__init__(self, title, visible = visible)
329    
330            self.projection = projection
331            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    

Legend:
Removed from v.701  
changed lines
  Added in v.2228

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26