/[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 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    
11    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    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    import resource
25    
 from table import Table  
   
 from base import TitledObject, Modifiable  
26    
27  class Shape:  class Shape:
28    
# Line 28  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 40  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 63  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 72  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 81  class BaseLayer(TitledObject, Modifiable Line 93  class BaseLayer(TitledObject, Modifiable
93          """Set the layer's visibility."""          """Set the layer's visibility."""
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    
112      """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 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          self.filename = filename                                   visible = visible,
148          self.projection = projection                                   projection = projection)
149          self.fill = fill  
150          self.stroke = stroke          #
151          self.stroke_width = stroke_width          # this is really important so that when the classification class
152          self.shapefile = None          # tries to set its parent layer the variable will exist
153          self.shapetree = None          #
154          self.open_shapefile()          self.__classification = None
155          # shapetable is the table associated with the shapefile, while          self.__setClassLock = False
156          # table is the default table used to look up attributes for  
157          # display          self.SetShapeStore(data)
158          self.shapetable = Table(filename)  
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            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              self.bbox = mins[:2] + maxs[:2]              self.bbox = mins[:2] + maxs[:2]
185              #print "building tree for", self.filename, "..."          else:
186              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2, 0)              self.bbox = None
187              #print "done"  
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):
218            BaseLayer.Destroy(self)
219            self.SetClassification(None)
220            self.store = self.shapetree = None
221            self.table = self.shapefile = self.shapetable = None
222    
223      def BoundingBox(self):      def BoundingBox(self):
224          """Return the bounding box of the layer's shapes in their default          """Return the layer's bounding box in the intrinsic coordinate system.
225          coordinate system"""  
226          self.open_shapefile()          If the layer has no shapes, return None.
227            """
228          return self.bbox          return self.bbox
229    
230      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
231          """Return the layer's bounding box in lat/long coordinates"""          """Return the layer's bounding box in lat/long coordinates.
232          llx, lly, urx, ury = self.BoundingBox()  
233            Return None, if the layer doesn't contain any shapes.
234            """
235            bbox = self.BoundingBox()
236            if bbox is not None:
237                llx, lly, urx, ury = bbox
238                if self.projection is not None:
239                    llx, lly = self.projection.Inverse(llx, lly)
240                    urx, ury = self.projection.Inverse(urx, ury)
241                return llx, lly, urx, ury
242            else:
243                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:          if self.projection is not None:
260              llx, lly = self.projection.Inverse(llx, lly)              inverse = lambda x, y: self.projection.Inverse(x, y)
261              urx, ury = self.projection.Inverse(urx, ury)          else:
262          return llx, lly, urx, ury              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 180  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            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    
     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.1158

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26