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

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

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

revision 147 by bh, Tue May 7 16:39:52 2002 UTC revision 1088 by bh, Wed May 28 12:42:23 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    
11    from math import log, ceil
12    
13    from Thuban import _
14    
15  import shapelib, shptree  import shapelib, shptree
16    
17  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  import gdal
18       LAYER_VISIBILITY_CHANGED  from gdalconst import GA_ReadOnly
19    
20  from color import Color  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
21  # Some predefined colors for internal use       LAYER_CHANGED
 _black = Color(0, 0, 0)  
22    
23    from color import Color
24    
25  from table import Table  import classification
26    
27  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
28    
29    
30  class Shape:  class Shape:
31    
32      """Represent one shape"""      """Represent one shape"""
# Line 28  class Shape: Line 34  class Shape:
34      def __init__(self, points):      def __init__(self, points):
35          self.points = points          self.points = points
36          #self.compute_bbox()          #self.compute_bbox()
37            self.bbox = None
38    
39      def compute_bbox(self):      def compute_bbox(self):
40            if self.bbox is not None:
41                return self.bbox
42    
43          xs = []          xs = []
44          ys = []          ys = []
45          for x, y in self.points:          for x, y in self.points:
# Line 40  class Shape: Line 50  class Shape:
50          self.urx = max(xs)          self.urx = max(xs)
51          self.ury = max(ys)          self.ury = max(ys)
52    
53            self.bbox = (self.llx, self.lly, self.urx, self.ury)
54    
55            return self.bbox
56    
57      def Points(self):      def Points(self):
58          return self.points          return self.points
59    
# Line 63  class BaseLayer(TitledObject, Modifiable Line 77  class BaseLayer(TitledObject, Modifiable
77    
78      """Base class for the layers."""      """Base class for the layers."""
79    
80      def __init__(self, title, visible = 1):      def __init__(self, title, visible = True, projection = None):
81          """Initialize the layer.          """Initialize the layer.
82    
83          title -- the title          title -- the title
# Line 72  class BaseLayer(TitledObject, Modifiable Line 86  class BaseLayer(TitledObject, Modifiable
86          TitledObject.__init__(self, title)          TitledObject.__init__(self, title)
87          Modifiable.__init__(self)          Modifiable.__init__(self)
88          self.visible = visible          self.visible = visible
89            self.projection = projection
90    
91      def Visible(self):      def Visible(self):
92          """Return true if layer is visible"""          """Return true if layer is visible"""
# Line 81  class BaseLayer(TitledObject, Modifiable Line 96  class BaseLayer(TitledObject, Modifiable
96          """Set the layer's visibility."""          """Set the layer's visibility."""
97          self.visible = visible          self.visible = visible
98          self.issue(LAYER_VISIBILITY_CHANGED, self)          self.issue(LAYER_VISIBILITY_CHANGED, self)
99            
100                def HasClassification(self):
101            """Determine if this layer support classifications."""
102            return False
103    
104        def GetProjection(self):
105            """Return the layer's projection."""
106            return self.projection
107    
108        def SetProjection(self, projection):
109            """Set the layer's projection"""
110            self.projection = projection
111            self.changed(LAYER_PROJECTION_CHANGED, self)
112    
113  class Layer(BaseLayer):  class Layer(BaseLayer):
114    
115      """Represent the information of one geodata file (currently a shapefile)      """Represent the information of one geodata file (currently a shapefile)
# Line 97  class Layer(BaseLayer): Line 124  class Layer(BaseLayer):
124    
125          TITLE_CHANGED -- The title has changed.          TITLE_CHANGED -- The title has changed.
126          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
         LAYER_LEGEND_CHANGED -- the fill or stroke attributes have changed  
   
127      """      """
128    
129      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
130                   fill = None, stroke = _black, stroke_width = 1, visible = 1):                   fill = Color.Transparent,
131                     stroke = Color.Black,
132                     lineWidth = 1,
133                     visible = True):
134          """Initialize the layer.          """Initialize the layer.
135    
136          title -- the title          title -- the title
137          filename -- the name of the shapefile          data -- datastore object for the shape data shown by the layer
138          projection -- the projection object. Its Inverse method is          projection -- the projection object. Its Inverse method is
139                 assumed to map the layer's coordinates to lat/long                 assumed to map the layer's coordinates to lat/long
140                 coordinates                 coordinates
141          fill -- the fill color or None if the shapes are not filled          fill -- the fill color or Color.Transparent if the shapes are
142          stroke -- the stroke color or None if the shapes are not stroked                  not filled
143            stroke -- the stroke color or Color.Transparent if the shapes
144                    are not stroked
145          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
146    
147          colors are expected to be instances of Color class          colors are expected to be instances of Color class
148          """          """
149          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title,
150          self.filename = filename                                   visible = visible,
151          self.projection = projection                                   projection = projection)
152          self.fill = fill  
153          self.stroke = stroke          #
154          self.stroke_width = stroke_width          # this is really important so that when the classification class
155          self.shapefile = None          # tries to set its parent layer the variable will exist
156          self.shapetree = None          #
157          self.open_shapefile()          self.__classification = None
158          # shapetable is the table associated with the shapefile, while          self.__setClassLock = False
159          # table is the default table used to look up attributes for  
160          # display          self.SetShapeStore(data)
161          self.shapetable = Table(filename)  
162            self.SetClassification(None)
163    
164            self.__classification.SetDefaultLineColor(stroke)
165            self.__classification.SetDefaultLineWidth(lineWidth)
166            self.__classification.SetDefaultFill(fill)
167            self.__classification.SetLayer(self)
168    
169            self.UnsetModified()
170    
171    
172        def SetShapeStore(self, store):
173            self.store = store
174            self.shapefile = self.store.Shapefile()
175            self.shapetable = self.store.Table()
176            if hasattr(self.store, "FileName"):
177                self.filename = self.store.FileName()
178          self.table = self.shapetable          self.table = self.shapetable
179    
180      def open_shapefile(self):          numshapes, shapetype, mins, maxs = self.shapefile.info()
181          if self.shapefile is None:          self.numshapes = numshapes
182              self.shapefile = shapelib.ShapeFile(self.filename)          self.shapetype = shapelib_shapetypes[shapetype]
183              numshapes, shapetype, mins, maxs = self.shapefile.info()  
184              self.numshapes = numshapes          # if there are shapes, set the bbox accordingly. Otherwise
185              self.shapetype = shapelib_shapetypes[shapetype]          # set it to None.
186            if self.numshapes:
187              self.bbox = mins[:2] + maxs[:2]              self.bbox = mins[:2] + maxs[:2]
188              #print "building tree for", self.filename, "..."          else:
189              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2, 0)              self.bbox = None
190              #print "done"  
191            # estimate a good depth for the quad tree. Each depth
192            # multiplies the number of nodes by four, therefore we
193            # basically take the base 4 logarithm of the number of
194            # shapes.
195            if self.numshapes < 4:
196                maxdepth = 1
197            else:
198                maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
199    
200            self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
201                                             maxdepth)
202            # Set the classification to None if there is a classification
203            # and the new shapestore doesn't have a table with a suitable
204            # column, i.e one with the same name and type as before
205            # FIXME: Maybe we should keep it the same if the type is
206            # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
207            if self.__classification is not None:
208                fieldname = self.__classification.GetField()
209                fieldtype = self.__classification.GetFieldType()
210                table = self.store.Table()
211                if (fieldname is not None
212                    and (not table.HasColumn(fieldname)
213                         or table.Column(fieldname).type != fieldtype)):
214                    self.SetClassification(None)
215            self.changed(LAYER_CHANGED, self)
216    
217        def ShapeStore(self):
218            return self.store
219    
220        def Destroy(self):
221            BaseLayer.Destroy(self)
222            self.SetClassification(None)
223    
224      def BoundingBox(self):      def BoundingBox(self):
225          """Return the bounding box of the layer's shapes in their default          """Return the layer's bounding box in the intrinsic coordinate system.
226          coordinate system"""  
227          self.open_shapefile()          If the layer has no shapes, return None.
228            """
229          return self.bbox          return self.bbox
230    
231      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
232          """Return the layer's bounding box in lat/long coordinates"""          """Return the layer's bounding box in lat/long coordinates.
233          llx, lly, urx, ury = self.BoundingBox()  
234            Return None, if the layer doesn't contain any shapes.
235            """
236            bbox = self.BoundingBox()
237            if bbox is not None:
238                llx, lly, urx, ury = bbox
239                if self.projection is not None:
240                    llx, lly = self.projection.Inverse(llx, lly)
241                    urx, ury = self.projection.Inverse(urx, ury)
242                return llx, lly, urx, ury
243            else:
244                return None
245    
246        def ShapesBoundingBox(self, shapes):
247            """Return a bounding box in lat/long coordinates for the given
248            list of shape ids.
249    
250            If shapes is None or empty, return None.
251            """
252    
253            if shapes is None or len(shapes) == 0: return None
254    
255            llx = []
256            lly = []
257            urx = []
258            ury = []
259    
260          if self.projection is not None:          if self.projection is not None:
261              llx, lly = self.projection.Inverse(llx, lly)              inverse = lambda x, y: self.projection.Inverse(x, y)
262              urx, ury = self.projection.Inverse(urx, ury)          else:
263          return llx, lly, urx, ury              inverse = lambda x, y: (x, y)
264    
265            for id in shapes:
266                left, bottom, right, top = self.Shape(id).compute_bbox()
267    
268                left, bottom = inverse(left, bottom)
269                right, top   = inverse(right, top)
270    
271                llx.append(left)
272                lly.append(bottom)
273                urx.append(right)
274                ury.append(top)
275    
276            return (min(llx), min(lly), max(urx), max(ury))
277    
278        def GetFieldType(self, fieldName):
279            if self.table.HasColumn(fieldName):
280                return self.table.Column(fieldName).type
281            return None
282    
283      def NumShapes(self):      def NumShapes(self):
284          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
         self.open_shapefile()  
285          return self.numshapes          return self.numshapes
286    
287      def ShapeType(self):      def ShapeType(self):
288          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
289          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
290          """          """
         self.open_shapefile()  
291          return self.shapetype          return self.shapetype
292    
293      def Shape(self, index):      def Shape(self, index):
294          """Return the shape with index index"""          """Return the shape with index index"""
         self.open_shapefile()  
295          shape = self.shapefile.read_object(index)          shape = self.shapefile.read_object(index)
296    
297          if self.shapetype == SHAPETYPE_POINT:          if self.shapetype == SHAPETYPE_POINT:
298              points = shape.vertices()              points = shape.vertices()
299          else:          else:
# Line 180  class Layer(BaseLayer): Line 302  class Layer(BaseLayer):
302              points = []              points = []
303              for x, y in poly:              for x, y in poly:
304                  points.append((x, y))                  points.append((x, y))
305    
306          return Shape(points)          return Shape(points)
307    
308      def ShapesInRegion(self, box):      def ShapesInRegion(self, box):
309          """Return the ids of the shapes that overlap the box.          """Return the ids of the shapes that overlap the box.
310    
311          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.  
312          """          """
313          left, bottom, right, top = box          left, bottom, right, top = box
314    
315            if self.projection is not None:
316                left,  bottom = self.projection.Forward(left, bottom)
317                right, top    = self.projection.Forward(right, top)
318    
319          return self.shapetree.find_shapes((left, bottom), (right, top))          return self.shapetree.find_shapes((left, bottom), (right, top))
320    
321      def SetProjection(self, projection):      def HasClassification(self):
322          """Set the layer's projection"""          return True
323    
324        def GetClassification(self):
325            return self.__classification
326    
327        def SetClassification(self, clazz):
328            """Set the classification to 'clazz'
329    
330            If 'clazz' is None a default classification is created
331            """
332    
333            # prevent infinite recursion when calling SetLayer()
334            if self.__setClassLock: return
335    
336            self.__setClassLock = True
337    
338            if clazz is None:
339                if self.__classification is not None:
340                    self.__classification.SetLayer(None)
341                self.__classification = classification.Classification()
342            else:
343                self.__classification = clazz
344                try:
345                    self.__classification.SetLayer(self)
346                except ValueError:
347                    self.__setClassLock = False
348                    raise ValueError
349    
350            self.changed(LAYER_CHANGED, self)
351    
352            self.__setClassLock = False
353    
354        def ClassChanged(self):
355            """Called from the classification object when it has changed."""
356            self.changed(LAYER_CHANGED, self)
357    
358        def TreeInfo(self):
359            items = []
360    
361            if hasattr(self, 'filename'):
362                items.append(_("Filename: %s") % self.filename)
363    
364            if self.Visible():
365                items.append(_("Shown"))
366            else:
367                items.append(_("Hidden"))
368            items.append(_("Shapes: %d") % self.NumShapes())
369    
370            bbox = self.LatLongBoundingBox()
371            if bbox is not None:
372                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
373            else:
374                items.append(_("Extent (lat-lon):"))
375            items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
376    
377            if self.projection and len(self.projection.params) > 0:
378                items.append((_("Projection"),
379                            [str(param) for param in self.projection.params]))
380    
381            items.append(self.__classification)
382    
383            return (_("Layer '%s'") % self.Title(), items)
384    
385    
386    class RasterLayer(BaseLayer):
387    
388        def __init__(self, title, filename, projection = None, visible = True):
389            """Initialize the Raster Layer.
390    
391            title -- title for the layer.
392    
393            filename -- file name of the source image.
394    
395            projection -- Projection object describing the projection which
396                          the source image is in.
397    
398            visible -- True is the layer should initially be visible.
399    
400            Throws IOError if the filename is invalid or points to a file that
401            is not in a format GDAL can use.
402            """
403    
404            BaseLayer.__init__(self, title, visible = visible)
405    
406          self.projection = projection          self.projection = projection
407          self.changed(LAYER_PROJECTION_CHANGED, self)          self.filename = filename
408    
409            self.bbox = -1
410    
411            #
412            # temporarily open the file so that GDAL can test if it's valid.
413            #
414            dataset = gdal.Open(self.filename, GA_ReadOnly)
415    
416            if dataset is None:
417                raise IOError()
418    
419            self.UnsetModified()
420    
421        def BoundingBox(self):
422            """Return the layer's bounding box in the intrinsic coordinate system.
423    
424            If the layer has no shapes, return None.
425            """
426            if self.bbox == -1:
427                dataset = gdal.Open(self.filename, GA_ReadOnly)
428                if dataset is None:
429                    self.bbox = None
430                else:
431                    geotransform = dataset.GetGeoTransform()
432                    if geotransform is None:
433                        return None
434    
435                    x = 0
436                    y = dataset.RasterYSize
437                    left = geotransform[0] +        \
438                           geotransform[1] * x +    \
439                           geotransform[2] * y
440    
441                    bottom = geotransform[3] +      \
442                             geotransform[4] * x +  \
443                             geotransform[5] * y
444    
445                    x = dataset.RasterXSize
446                    y = 0
447                    right = geotransform[0] +       \
448                            geotransform[1] * x +   \
449                            geotransform[2] * y
450    
451                    top = geotransform[3] +         \
452                          geotransform[4] * x +     \
453                          geotransform[5] * y
454    
455                    self.bbox = (left, bottom, right, top)
456    
457            return self.bbox
458    
459        def LatLongBoundingBox(self):
460            bbox = self.BoundingBox()
461            if bbox is None:
462                return None
463    
464            llx, lly, urx, ury = bbox
465            if self.projection is not None:
466                llx, lly = self.projection.Inverse(llx, lly)
467                urx, ury = self.projection.Inverse(urx, ury)
468    
469            return llx, lly, urx, ury
470    
471        def GetImageFilename(self):
472            return self.filename
473    
474        def TreeInfo(self):
475            items = []
476    
477            if self.Visible():
478                items.append(_("Shown"))
479            else:
480                items.append(_("Hidden"))
481            items.append(_("Shapes: %d") % self.NumShapes())
482    
483            bbox = self.LatLongBoundingBox()
484            if bbox is not None:
485                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
486            else:
487                items.append(_("Extent (lat-lon):"))
488    
489            if self.projection and len(self.projection.params) > 0:
490                items.append((_("Projection"),
491                            [str(param) for param in self.projection.params]))
492    
493            return (_("Layer '%s'") % self.Title(), items)
494    
     def SetFill(self, fill):  
         """Set the layer's fill color. None means the shapes are not filled"""  
         self.fill = fill  
         self.changed(LAYER_LEGEND_CHANGED, self)  
   
     def SetStroke(self, stroke):  
         """Set the layer's stroke color. None means the shapes are not  
         stroked."""  
         self.stroke = stroke  
         self.changed(LAYER_LEGEND_CHANGED, self)  
   
     def SetStrokeWidth(self, width):  
         """Set the layer's stroke width."""  
         self.stroke_width = width  
         self.changed(LAYER_LEGEND_CHANGED, self)  

Legend:
Removed from v.147  
changed lines
  Added in v.1088

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26