/[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 610 by jonathan, Fri Apr 4 13:56:59 2003 UTC revision 1158 by jonathan, Thu Jun 12 12:40:11 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 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 8  Line 8 
8    
9  __version__ = "$Revision$"  __version__ = "$Revision$"
10    
 import os  
11  from math import log, ceil  from math import log, ceil
12    
13  from Thuban import _  from Thuban import _
   
14  import shapelib, shptree  import shapelib, shptree
15    
16  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17       LAYER_VISIBILITY_CHANGED, LAYER_CHANGED       LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED
   
 from color import Color  
18    
19  import classification  import classification
20    
21  from table import Table  from color import Color
   
22  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
23    
24    import resource
25    
26    
27  class Shape:  class Shape:
28    
29      """Represent one shape"""      """Represent one shape"""
# Line 33  class Shape: Line 31  class Shape:
31      def __init__(self, points):      def __init__(self, points):
32          self.points = points          self.points = points
33          #self.compute_bbox()          #self.compute_bbox()
34            self.bbox = None
35    
36      def compute_bbox(self):      def compute_bbox(self):
37            if self.bbox is not None:
38                return self.bbox
39    
40          xs = []          xs = []
41          ys = []          ys = []
42          for x, y in self.points:          for x, y in self.points:
# Line 45  class Shape: Line 47  class Shape:
47          self.urx = max(xs)          self.urx = max(xs)
48          self.ury = max(ys)          self.ury = max(ys)
49    
50            self.bbox = (self.llx, self.lly, self.urx, self.ury)
51    
52            return self.bbox
53    
54      def Points(self):      def Points(self):
55          return self.points          return self.points
56    
# Line 68  class BaseLayer(TitledObject, Modifiable Line 74  class BaseLayer(TitledObject, Modifiable
74    
75      """Base class for the layers."""      """Base class for the layers."""
76    
77      def __init__(self, title, visible = 1):      def __init__(self, title, visible = True, projection = None):
78          """Initialize the layer.          """Initialize the layer.
79    
80          title -- the title          title -- the title
# Line 77  class BaseLayer(TitledObject, Modifiable Line 83  class BaseLayer(TitledObject, Modifiable
83          TitledObject.__init__(self, title)          TitledObject.__init__(self, title)
84          Modifiable.__init__(self)          Modifiable.__init__(self)
85          self.visible = visible          self.visible = visible
86            self.projection = projection
87    
88      def Visible(self):      def Visible(self):
89          """Return true if layer is visible"""          """Return true if layer is visible"""
# Line 87  class BaseLayer(TitledObject, Modifiable Line 94  class BaseLayer(TitledObject, Modifiable
94          self.visible = visible          self.visible = visible
95          self.issue(LAYER_VISIBILITY_CHANGED, self)          self.issue(LAYER_VISIBILITY_CHANGED, self)
96    
97        def HasClassification(self):
98            """Determine if this layer support classifications."""
99            return False
100    
101        def GetProjection(self):
102            """Return the layer's projection."""
103            return self.projection
104    
105        def SetProjection(self, projection):
106            """Set the layer's projection"""
107            self.projection = projection
108            self.changed(LAYER_PROJECTION_CHANGED, self)
109    
110  class Layer(BaseLayer):  class Layer(BaseLayer):
111    
# Line 102  class Layer(BaseLayer): Line 121  class Layer(BaseLayer):
121    
122          TITLE_CHANGED -- The title has changed.          TITLE_CHANGED -- The title has changed.
123          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
         LAYER_LEGEND_CHANGED -- the fill or stroke attributes have changed  
   
124      """      """
125    
126      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
127                   fill = Color.Transparent,                   fill = Color.Transparent,
128                   stroke = Color.Black,                   stroke = Color.Black,
129                   lineWidth = 1,                   lineWidth = 1,
130                   visible = 1):                   visible = True):
131          """Initialize the layer.          """Initialize the layer.
132    
133          title -- the title          title -- the title
134          filename -- the name of the shapefile          data -- datastore object for the shape data shown by the layer
135          projection -- the projection object. Its Inverse method is          projection -- the projection object. Its Inverse method is
136                 assumed to map the layer's coordinates to lat/long                 assumed to map the layer's coordinates to lat/long
137                 coordinates                 coordinates
# Line 126  class Layer(BaseLayer): Line 143  class Layer(BaseLayer):
143    
144          colors are expected to be instances of Color class          colors are expected to be instances of Color class
145          """          """
146          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title,
147                                     visible = visible,
148          # Make the filename absolute. The filename will be                                   projection = projection)
         # interpreted relative to that anyway, but when saving a  
         # 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  
149    
150          #          #
151          # this is really important so that when the classification class          # this is really important so that when the classification class
152          # tries to set its parent layer the variable will exist          # tries to set its parent layer the variable will exist
153          #          #
154          self.__classification = None          self.__classification = None
155          self.__setClassLock = False          self.__setClassLock = False
156    
157            self.SetShapeStore(data)
158    
159          self.SetClassification(None)          self.SetClassification(None)
160    
# Line 161  class Layer(BaseLayer): Line 165  class Layer(BaseLayer):
165    
166          self.UnsetModified()          self.UnsetModified()
167    
     def open_shapefile(self):  
         if self.shapefile is None:  
             self.shapefile = shapelib.ShapeFile(self.filename)  
             numshapes, shapetype, mins, maxs = self.shapefile.info()  
             self.numshapes = numshapes  
             self.shapetype = shapelib_shapetypes[shapetype]  
   
             # if there are shapes, set the bbox accordinly. Otherwise  
             # set it to None.  
             if self.numshapes:  
                 self.bbox = mins[:2] + maxs[:2]  
             else:  
                 self.bbox = None  
168    
169              # estimate a good depth for the quad tree. Each depth      def SetShapeStore(self, store):
170              # multiplies the number of nodes by four, therefore we          self.store = store
171              # basically take the base 4 logarithm of the number of          self.shapefile = self.store.Shapefile()
172              # shapes.          self.shapetable = self.store.Table()
173              if self.numshapes < 4:          if hasattr(self.store, "FileName"):
174                  maxdepth = 1              self.filename = self.store.FileName()
175              else:          self.table = self.shapetable
                 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))  
176    
177              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,          numshapes, shapetype, mins, maxs = self.shapefile.info()
178                                               maxdepth)          self.numshapes = numshapes
179            self.shapetype = shapelib_shapetypes[shapetype]
180    
181            # if there are shapes, set the bbox accordingly. Otherwise
182            # set it to None.
183            if self.numshapes:
184                self.bbox = mins[:2] + maxs[:2]
185            else:
186                self.bbox = None
187    
188            # estimate a good depth for the quad tree. Each depth
189            # multiplies the number of nodes by four, therefore we
190            # basically take the base 4 logarithm of the number of
191            # shapes.
192            if self.numshapes < 4:
193                maxdepth = 1
194            else:
195                maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
196    
197            self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
198                                             maxdepth)
199            # Set the classification to None if there is a classification
200            # and the new shapestore doesn't have a table with a suitable
201            # column, i.e one with the same name and type as before
202            # FIXME: Maybe we should keep it the same if the type is
203            # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
204            if self.__classification is not None:
205                fieldname = self.__classification.GetField()
206                fieldtype = self.__classification.GetFieldType()
207                table = self.store.Table()
208                if (fieldname is not None
209                    and (not table.HasColumn(fieldname)
210                         or table.Column(fieldname).type != fieldtype)):
211                    self.SetClassification(None)
212            self.changed(LAYER_SHAPESTORE_REPLACED, self)
213    
214        def ShapeStore(self):
215            return self.store
216    
217      def Destroy(self):      def Destroy(self):
218          BaseLayer.Destroy(self)          BaseLayer.Destroy(self)
         if self.shapefile is not None:  
             self.shapefile.close()  
             self.shapefile = None  
             self.shapetree = None  
219          self.SetClassification(None)          self.SetClassification(None)
220          self.table.Destroy()          self.store = self.shapetree = None
221            self.table = self.shapefile = self.shapetable = None
222    
223      def BoundingBox(self):      def BoundingBox(self):
224          """Return the layer's bounding box in the intrinsic coordinate system.          """Return the layer's bounding box in the intrinsic coordinate system.
225    
226          If the layer has no shapes, return None.          If the layer has no shapes, return None.
227          """          """
         # The bbox will be set by open_shapefile just as we need it  
         # here.  
         self.open_shapefile()  
228          return self.bbox          return self.bbox
229    
230      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
# Line 221  class Layer(BaseLayer): Line 242  class Layer(BaseLayer):
242          else:          else:
243              return None              return None
244    
245      def GetFieldType(self, fieldName):      def ShapesBoundingBox(self, shapes):
246          self.open_shapefile()          """Return a bounding box in lat/long coordinates for the given
247          info = self.table.field_info_by_name(fieldName)          list of shape ids.
248          if info is not None:  
249              return info[0]          If shapes is None or empty, return None.
250            """
251    
252            if shapes is None or len(shapes) == 0: return None
253    
254            llx = []
255            lly = []
256            urx = []
257            ury = []
258    
259            if self.projection is not None:
260                inverse = lambda x, y: self.projection.Inverse(x, y)
261          else:          else:
262              return None              inverse = lambda x, y: (x, y)
263    
264            for id in shapes:
265                left, bottom, right, top = self.Shape(id).compute_bbox()
266    
267                left, bottom = inverse(left, bottom)
268                right, top   = inverse(right, top)
269    
270                llx.append(left)
271                lly.append(bottom)
272                urx.append(right)
273                ury.append(top)
274    
275            return (min(llx), min(lly), max(urx), max(ury))
276    
277        def GetFieldType(self, fieldName):
278            if self.table.HasColumn(fieldName):
279                return self.table.Column(fieldName).type
280            return None
281    
282      def NumShapes(self):      def NumShapes(self):
283          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
         self.open_shapefile()  
284          return self.numshapes          return self.numshapes
285    
286      def ShapeType(self):      def ShapeType(self):
287          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
288          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
289          """          """
         self.open_shapefile()  
290          return self.shapetype          return self.shapetype
291    
292      def Shape(self, index):      def Shape(self, index):
293          """Return the shape with index index"""          """Return the shape with index index"""
         self.open_shapefile()  
294          shape = self.shapefile.read_object(index)          shape = self.shapefile.read_object(index)
295    
296          if self.shapetype == SHAPETYPE_POINT:          if self.shapetype == SHAPETYPE_POINT:
# Line 260  class Layer(BaseLayer): Line 307  class Layer(BaseLayer):
307      def ShapesInRegion(self, box):      def ShapesInRegion(self, box):
308          """Return the ids of the shapes that overlap the box.          """Return the ids of the shapes that overlap the box.
309    
310          Box is a tuple (left, bottom, right, top) in the coordinate          Box is a tuple (left, bottom, right, top) in unprojected coordinates.
         system used by the layer's shapefile.  
311          """          """
312          left, bottom, right, top = box          left, bottom, right, top = box
313    
314            if self.projection is not None:
315                left,  bottom = self.projection.Forward(left, bottom)
316                right, top    = self.projection.Forward(right, top)
317    
318          return self.shapetree.find_shapes((left, bottom), (right, top))          return self.shapetree.find_shapes((left, bottom), (right, top))
319    
320      def SetProjection(self, projection):      def HasClassification(self):
321          """Set the layer's projection"""          return True
         self.projection = projection  
         self.changed(LAYER_PROJECTION_CHANGED, self)  
322    
323      def GetClassification(self):      def GetClassification(self):
324          return self.__classification          return self.__classification
# Line 308  class Layer(BaseLayer): Line 357  class Layer(BaseLayer):
357      def TreeInfo(self):      def TreeInfo(self):
358          items = []          items = []
359    
360            if hasattr(self, 'filename'):
361                items.append(_("Filename: %s") % self.filename)
362    
363          if self.Visible():          if self.Visible():
364              items.append(_("Shown"))              items.append(_("Shown"))
365          else:          else:
# Line 321  class Layer(BaseLayer): Line 373  class Layer(BaseLayer):
373              items.append(_("Extent (lat-lon):"))              items.append(_("Extent (lat-lon):"))
374          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
375    
376            if self.projection and len(self.projection.params) > 0:
377                items.append((_("Projection"),
378                            [str(param) for param in self.projection.params]))
379    
380          items.append(self.__classification)          items.append(self.__classification)
381    
382          return (_("Layer '%s'") % self.Title(), items)          return (_("Layer '%s'") % self.Title(), items)
383    
384    
385    if resource.has_gdal_support():
386        import gdal
387        from gdalconst import GA_ReadOnly
388    
389    class RasterLayer(BaseLayer):
390    
391        def __init__(self, title, filename, projection = None, visible = True):
392            """Initialize the Raster Layer.
393    
394            title -- title for the layer.
395    
396            filename -- file name of the source image.
397    
398            projection -- Projection object describing the projection which
399                          the source image is in.
400    
401            visible -- True is the layer should initially be visible.
402    
403            Throws IOError if the filename is invalid or points to a file that
404            is not in a format GDAL can use.
405            """
406    
407            BaseLayer.__init__(self, title, visible = visible)
408    
409            self.projection = projection
410            self.filename = filename
411    
412            self.bbox = -1
413    
414            if resource.has_gdal_support():
415                #
416                # temporarily open the file so that GDAL can test if it's valid.
417                #
418                dataset = gdal.Open(self.filename, GA_ReadOnly)
419    
420                if dataset is None:
421                    raise IOError()
422    
423            self.UnsetModified()
424    
425        def BoundingBox(self):
426            """Return the layer's bounding box in the intrinsic coordinate system.
427    
428            If the layer has no shapes, return None.
429            """
430            if not resource.has_gdal_support():
431                return None
432    
433            if self.bbox == -1:
434                dataset = gdal.Open(self.filename, GA_ReadOnly)
435                if dataset is None:
436                    self.bbox = None
437                else:
438                    geotransform = dataset.GetGeoTransform()
439                    if geotransform is None:
440                        return None
441    
442                    x = 0
443                    y = dataset.RasterYSize
444                    left = geotransform[0] +        \
445                           geotransform[1] * x +    \
446                           geotransform[2] * y
447    
448                    bottom = geotransform[3] +      \
449                             geotransform[4] * x +  \
450                             geotransform[5] * y
451    
452                    x = dataset.RasterXSize
453                    y = 0
454                    right = geotransform[0] +       \
455                            geotransform[1] * x +   \
456                            geotransform[2] * y
457    
458                    top = geotransform[3] +         \
459                          geotransform[4] * x +     \
460                          geotransform[5] * y
461    
462                    self.bbox = (left, bottom, right, top)
463    
464            return self.bbox
465    
466        def LatLongBoundingBox(self):
467            bbox = self.BoundingBox()
468            if bbox is None:
469                return None
470    
471            llx, lly, urx, ury = bbox
472            if self.projection is not None:
473                llx, lly = self.projection.Inverse(llx, lly)
474                urx, ury = self.projection.Inverse(urx, ury)
475    
476            return llx, lly, urx, ury
477    
478        def GetImageFilename(self):
479            return self.filename
480    
481        def TreeInfo(self):
482            items = []
483    
484            if self.Visible():
485                items.append(_("Shown"))
486            else:
487                items.append(_("Hidden"))
488            items.append(_("Shapes: %d") % self.NumShapes())
489    
490            bbox = self.LatLongBoundingBox()
491            if bbox is not None:
492                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
493            else:
494                items.append(_("Extent (lat-lon):"))
495    
496            if self.projection and len(self.projection.params) > 0:
497                items.append((_("Projection"),
498                            [str(param) for param in self.projection.params]))
499    
500            return (_("Layer '%s'") % self.Title(), items)
501    

Legend:
Removed from v.610  
changed lines
  Added in v.1158

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26