/[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 276 by bh, Fri Aug 23 15:25:07 2002 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]>
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    
 import os  
11  from math import log, ceil  from math import log, ceil
12    
13    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_SHAPESTORE_REPLACED
18    
19  from color import Color  import classification
 # Some predefined colors for internal use  
 _black = Color(0, 0, 0)  
20    
21    from color import Color
22    from base import TitledObject, Modifiable
23    
24  from table import Table  import resource
25    
 from base import TitledObject, Modifiable  
26    
27  class Shape:  class Shape:
28    
# Line 31  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 43  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 66  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 75  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 85  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 100  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 = None, stroke = _black, stroke_width = 1, visible = 1):                   fill = Color.Transparent,
128                     stroke = Color.Black,
129                     lineWidth = 1,
130                     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
138          fill -- the fill color or None if the shapes are not filled          fill -- the fill color or Color.Transparent if the shapes are
139          stroke -- the stroke color or None if the shapes are not stroked                  not filled
140            stroke -- the stroke color or Color.Transparent if the shapes
141                    are not stroked
142          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
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)
149          # interpreted relative to that anyway, but when saving a  
150          # session we need to compare absolute paths and it's usually          #
151          # safer to always work with absolute paths.          # this is really important so that when the classification class
152          self.filename = os.path.abspath(filename)          # tries to set its parent layer the variable will exist
153            #
154          self.projection = projection          self.__classification = None
155          self.fill = fill          self.__setClassLock = False
156          self.stroke = stroke  
157          self.stroke_width = stroke_width          self.SetShapeStore(data)
158          self.shapefile = None  
159          self.shapetree = None          self.SetClassification(None)
160          self.open_shapefile()  
161          # shapetable is the table associated with the shapefile, while          self.__classification.SetDefaultLineColor(stroke)
162          # table is the default table used to look up attributes for          self.__classification.SetDefaultLineWidth(lineWidth)
163          # display          self.__classification.SetDefaultFill(fill)
164          self.shapetable = Table(filename)          self.__classification.SetLayer(self)
165    
166            self.UnsetModified()
167    
168    
169        def SetShapeStore(self, store):
170            self.store = store
171            self.shapefile = self.store.Shapefile()
172            self.shapetable = self.store.Table()
173            if hasattr(self.store, "FileName"):
174                self.filename = self.store.FileName()
175          self.table = self.shapetable          self.table = self.shapetable
176    
177      def open_shapefile(self):          numshapes, shapetype, mins, maxs = self.shapefile.info()
178          if self.shapefile is None:          self.numshapes = numshapes
179              self.shapefile = shapelib.ShapeFile(self.filename)          self.shapetype = shapelib_shapetypes[shapetype]
180              numshapes, shapetype, mins, maxs = self.shapefile.info()  
181              self.numshapes = numshapes          # if there are shapes, set the bbox accordingly. Otherwise
182              self.shapetype = shapelib_shapetypes[shapetype]          # set it to None.
183            if self.numshapes:
184              # if there are shapes, set the bbox accordinly. Otherwise              self.bbox = mins[:2] + maxs[:2]
185              # set it to None.          else:
186              if self.numshapes:              self.bbox = None
                 self.bbox = mins[:2] + maxs[:2]  
             else:  
                 self.bbox = None  
187    
188              # estimate a good depth for the quad tree. Each depth          # estimate a good depth for the quad tree. Each depth
189              # multiplies the number of nodes by four, therefore we          # multiplies the number of nodes by four, therefore we
190              # basically take the base 4 logarithm of the number of          # basically take the base 4 logarithm of the number of
191              # shapes.          # shapes.
192              if self.numshapes < 4:          if self.numshapes < 4:
193                  maxdepth = 1              maxdepth = 1
194              else:          else:
195                  maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))              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              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,      def ShapeStore(self):
215                                               maxdepth)          return self.store
216    
217      def Destroy(self):      def Destroy(self):
218          BaseLayer.Destroy(self)          BaseLayer.Destroy(self)
219          if self.shapefile is not None:          self.SetClassification(None)
220              self.shapefile.close()          self.store = self.shapetree = None
221              self.shapefile = None          self.table = self.shapefile = self.shapetable = None
             self.shapetree = None  
         self.table.Destroy()  
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 199  class Layer(BaseLayer): Line 242  class Layer(BaseLayer):
242          else:          else:
243              return None              return None
244    
245        def ShapesBoundingBox(self, shapes):
246            """Return a bounding box in lat/long coordinates for the given
247            list of shape ids.
248    
249            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:
262                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:
297              points = shape.vertices()              points = shape.vertices()
298          else:          else:
# Line 223  class Layer(BaseLayer): Line 301  class Layer(BaseLayer):
301              points = []              points = []
302              for x, y in poly:              for x, y in poly:
303                  points.append((x, y))                  points.append((x, y))
304    
305          return Shape(points)          return Shape(points)
306    
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
322    
323        def GetClassification(self):
324            return self.__classification
325    
326        def SetClassification(self, clazz):
327            """Set the classification to 'clazz'
328    
329            If 'clazz' is None a default classification is created
330            """
331    
332            # prevent infinite recursion when calling SetLayer()
333            if self.__setClassLock: return
334    
335            self.__setClassLock = True
336    
337            if clazz is None:
338                if self.__classification is not None:
339                    self.__classification.SetLayer(None)
340                self.__classification = classification.Classification()
341            else:
342                self.__classification = clazz
343                try:
344                    self.__classification.SetLayer(self)
345                except ValueError:
346                    self.__setClassLock = False
347                    raise ValueError
348    
349            self.changed(LAYER_CHANGED, self)
350    
351            self.__setClassLock = False
352    
353        def ClassChanged(self):
354            """Called from the classification object when it has changed."""
355            self.changed(LAYER_CHANGED, self)
356    
357        def TreeInfo(self):
358            items = []
359    
360            if hasattr(self, 'filename'):
361                items.append(_("Filename: %s") % self.filename)
362    
363            if self.Visible():
364                items.append(_("Shown"))
365            else:
366                items.append(_("Hidden"))
367            items.append(_("Shapes: %d") % self.NumShapes())
368    
369            bbox = self.LatLongBoundingBox()
370            if bbox is not None:
371                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
372            else:
373                items.append(_("Extent (lat-lon):"))
374            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)
381    
382            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          self.projection = projection
410          self.changed(LAYER_PROJECTION_CHANGED, self)          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      def SetFill(self, fill):          self.UnsetModified()
424          """Set the layer's fill color. None means the shapes are not filled"""  
425          self.fill = fill      def BoundingBox(self):
426          self.changed(LAYER_LEGEND_CHANGED, self)          """Return the layer's bounding box in the intrinsic coordinate system.
427    
428      def SetStroke(self, stroke):          If the layer has no shapes, return None.
429          """Set the layer's stroke color. None means the shapes are not          """
430          stroked."""          if not resource.has_gdal_support():
431          self.stroke = stroke              return None
432          self.changed(LAYER_LEGEND_CHANGED, self)  
433            if self.bbox == -1:
434      def SetStrokeWidth(self, width):              dataset = gdal.Open(self.filename, GA_ReadOnly)
435          """Set the layer's stroke width."""              if dataset is None:
436          self.stroke_width = width                  self.bbox = None
437          self.changed(LAYER_LEGEND_CHANGED, self)              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):      def TreeInfo(self):
482          items = []          items = []
483    
484          if self.Visible():          if self.Visible():
485              items.append("Shown")              items.append(_("Shown"))
486          else:          else:
487              items.append("Hidden")              items.append(_("Hidden"))
488          items.append("Shapes: %d" % self.NumShapes())          items.append(_("Shapes: %d") % self.NumShapes())
489    
490          bbox = self.LatLongBoundingBox()          bbox = self.LatLongBoundingBox()
491          if bbox is not None:          if bbox is not None:
492              items.append("Extent (lat-lon): (%g, %g, %g, %g)" % bbox)              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
493          else:          else:
494              items.append("Extent (lat-lon):")              items.append(_("Extent (lat-lon):"))
495          items.append("Shapetype: %s" % shapetype_names[self.ShapeType()])  
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          def color_string(color):          return (_("Layer '%s'") % self.Title(), items)
             if color is None:  
                 return "None"  
             return "(%.3f, %.3f, %.3f)" % (color.red, color.green, color.blue)  
         items.append("Fill: " + color_string(self.fill))  
         items.append("Outline: " + color_string(self.stroke))  
501    
         return ("Layer '%s'" % self.Title(), items)  

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26