/[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 82 by bh, Fri Feb 15 17:11:04 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  import shapelib  from math import log, ceil
12    
13  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from Thuban import _
14       LAYER_VISIBILITY_CHANGED  import shapelib, shptree
   
 from color import Color  
 # Some predefined colors for internal use  
 _black = Color(0, 0, 0)  
15    
16    from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
17         LAYER_CHANGED, LAYER_SHAPESTORE_REPLACED
18    
19  from table import Table  import classification
20    
21    from color import Color
22  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
23    
24    import resource
25    
26    
27  class Shape:  class Shape:
28    
29      """Represent one shape"""      """Represent one shape"""
# Line 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.open_shapefile()          #
154          # shapetable is the table associated with the shapefile, while          self.__classification = None
155          # table is the default table used to look up attributes for          self.__setClassLock = False
156          # display  
157          self.shapetable = Table(filename)          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            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            else:
186                self.bbox = None
187    
188            # estimate a good depth for the quad tree. Each depth
189            # multiplies the number of nodes by four, therefore we
190            # basically take the base 4 logarithm of the number of
191            # shapes.
192            if self.numshapes < 4:
193                maxdepth = 1
194            else:
195                maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
196    
197            self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
198                                             maxdepth)
199            # Set the classification to None if there is a classification
200            # and the new shapestore doesn't have a table with a suitable
201            # column, i.e one with the same name and type as before
202            # FIXME: Maybe we should keep it the same if the type is
203            # compatible enough such as FIELDTYPE_DOUBLE and FIELDTYPE_INT
204            if self.__classification is not None:
205                fieldname = self.__classification.GetField()
206                fieldtype = self.__classification.GetFieldType()
207                table = self.store.Table()
208                if (fieldname is not None
209                    and (not table.HasColumn(fieldname)
210                         or table.Column(fieldname).type != fieldtype)):
211                    self.SetClassification(None)
212            self.changed(LAYER_SHAPESTORE_REPLACED, self)
213    
214        def ShapeStore(self):
215            return self.store
216    
217        def Destroy(self):
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 176  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 SetProjection(self, projection):      def ShapesInRegion(self, box):
308          """Set the layer's projection"""          """Return the ids of the shapes that overlap the box.
309    
310            Box is a tuple (left, bottom, right, top) in unprojected coordinates.
311            """
312            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))
319    
320        def HasClassification(self):
321            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.82  
changed lines
  Added in v.1158

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26