/[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 389 by jonathan, Mon Feb 10 15:25:30 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]>
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 _  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    import classification
20    
21  from color import Color  from color import Color
22  # Some predefined colors for internal use  from base import TitledObject, Modifiable
 _black = Color(0, 0, 0)  
23    
24  from classification import Classification  import resource
25    
 from table import Table  
   
 from base import TitledObject, Modifiable  
26    
27  class Shape:  class Shape:
28    
# Line 34  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 46  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 69  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 78  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 88  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 103  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                                     projection = projection)
149    
150            #
151            # this is really important so that when the classification class
152            # tries to set its parent layer the variable will exist
153            #
154            self.__classification = None
155            self.__setClassLock = False
156    
157            self.SetShapeStore(data)
158    
159            self.SetClassification(None)
160    
161            self.__classification.SetDefaultLineColor(stroke)
162            self.__classification.SetDefaultLineWidth(lineWidth)
163            self.__classification.SetDefaultFill(fill)
164            self.__classification.SetLayer(self)
165    
166          # Make the filename absolute. The filename will be          self.UnsetModified()
         # 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)  
167    
         self.projection = projection  
         self.fill = fill  
         self.stroke = stroke  
         self.stroke_width = stroke_width  
         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  
168    
169          self.classification = Classification(self)      def SetShapeStore(self, store):
170          self.classification.SetDefaultStroke(stroke)          self.store = store
171          self.classification.SetDefaultStrokeWidth(stroke_width)          self.shapefile = self.store.Shapefile()
172          self.classification.SetDefaultFill(fill)          self.shapetable = self.store.Table()
173            if hasattr(self.store, "FileName"):
174                self.filename = self.store.FileName()
175            self.table = self.shapetable
176    
177          self.UnsetModified()          numshapes, shapetype, mins, maxs = self.shapefile.info()
178            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      def open_shapefile(self):          # estimate a good depth for the quad tree. Each depth
189          if self.shapefile is None:          # multiplies the number of nodes by four, therefore we
190              self.shapefile = shapelib.ShapeFile(self.filename)          # basically take the base 4 logarithm of the number of
191              numshapes, shapetype, mins, maxs = self.shapefile.info()          # shapes.
192              self.numshapes = numshapes          if self.numshapes < 4:
193              self.shapetype = shapelib_shapetypes[shapetype]              maxdepth = 1
194            else:
195              # if there are shapes, set the bbox accordinly. Otherwise              maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
             # set it to None.  
             if self.numshapes:  
                 self.bbox = mins[:2] + maxs[:2]  
             else:  
                 self.bbox = None  
196    
197              # estimate a good depth for the quad tree. Each depth          self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
198              # multiplies the number of nodes by four, therefore we                                           maxdepth)
199              # basically take the base 4 logarithm of the number of          # Set the classification to None if there is a classification
200              # shapes.          # and the new shapestore doesn't have a table with a suitable
201              if self.numshapes < 4:          # column, i.e one with the same name and type as before
202                  maxdepth = 1          # FIXME: Maybe we should keep it the same if the type is
203              else:          # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
204                  maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))          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 209  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:
# Line 240  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):
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):      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 267  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          def color_string(color):          if self.projection and len(self.projection.params) > 0:
377              if color is None:              items.append((_("Projection"),
378                  return "None"                          [str(param) for param in self.projection.params]))
379              return "(%.3f, %.3f, %.3f)" % (color.red, color.green, color.blue)  
380            items.append(self.__classification)
381    
382          # layers will always have a classification with at least a NULL data set          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          #items.append((_("Fill: %s") % color_string(self.fill), self.fill))          if self.bbox == -1:
434          #items.append((_("Outline: %s") % color_string(self.stroke), self.stroke))              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          items.append(self.classification)          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)          return (_("Layer '%s'") % self.Title(), items)
501    

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26