/[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 364 by jonathan, Mon Jan 27 11:47:12 2003 UTC revision 828 by jonathan, Tue May 6 12:06:12 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4    # Jonathan Coles <[email protected]>
5  #  #
6  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
7  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
8    
9  __version__ = "$Revision$"  __version__ = "$Revision$"
10    
 import os  
11  from math import log, ceil  from math import log, ceil
12    
13    from Thuban import _
14    
15  import shapelib, shptree  import shapelib, shptree
16    
17  from messages import LAYER_PROJECTION_CHANGED, LAYER_LEGEND_CHANGED, \  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18       LAYER_VISIBILITY_CHANGED       LAYER_CHANGED
19    
20  from color import Color  from color import Color
 # Some predefined colors for internal use  
 _black = Color(0, 0, 0)  
   
 from classification import Classification  
21    
22  from table import Table  import classification
23    
24  from base import TitledObject, Modifiable  from base import TitledObject, Modifiable
25    
26    
27  class Shape:  class Shape:
28    
29      """Represent one shape"""      """Represent one shape"""
# Line 32  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 44  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 67  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):
78          """Initialize the layer.          """Initialize the layer.
79    
80          title -- the title          title -- the title
# Line 101  class Layer(BaseLayer): Line 108  class Layer(BaseLayer):
108    
109          TITLE_CHANGED -- The title has changed.          TITLE_CHANGED -- The title has changed.
110          LAYER_PROJECTION_CHANGED -- the projection has changed.          LAYER_PROJECTION_CHANGED -- the projection has changed.
         LAYER_LEGEND_CHANGED -- the fill or stroke attributes have changed  
   
111      """      """
112    
113      def __init__(self, title, filename, projection = None,      def __init__(self, title, data, projection = None,
114                   fill = None, stroke = _black, stroke_width = 1, visible = 1):                   fill = Color.Transparent,
115                     stroke = Color.Black,
116                     lineWidth = 1,
117                     visible = True):
118          """Initialize the layer.          """Initialize the layer.
119    
120          title -- the title          title -- the title
121          filename -- the name of the shapefile          data -- datastore object for the shape data shown by the layer
122          projection -- the projection object. Its Inverse method is          projection -- the projection object. Its Inverse method is
123                 assumed to map the layer's coordinates to lat/long                 assumed to map the layer's coordinates to lat/long
124                 coordinates                 coordinates
125          fill -- the fill color or None if the shapes are not filled          fill -- the fill color or Color.Transparent if the shapes are
126          stroke -- the stroke color or None if the shapes are not stroked                  not filled
127            stroke -- the stroke color or Color.Transparent if the shapes
128                    are not stroked
129          visible -- boolean. If true the layer is visible.          visible -- boolean. If true the layer is visible.
130    
131          colors are expected to be instances of Color class          colors are expected to be instances of Color class
132          """          """
133          BaseLayer.__init__(self, title, visible = visible)          BaseLayer.__init__(self, title, visible = visible)
134    
         # Make the filename absolute. The filename will be  
         # 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)  
   
135          self.projection = projection          self.projection = projection
136          self.fill = fill  
137          self.stroke = stroke          #
138          self.stroke_width = stroke_width          # this is really important so that when the classification class
139          self.shapefile = None          # tries to set its parent layer the variable will exist
140          self.shapetree = None          #
141          self.open_shapefile()          self.__classification = None
142          # shapetable is the table associated with the shapefile, while          self.__setClassLock = False
143          # table is the default table used to look up attributes for  
144          # display          self.SetShapeStore(data)
145          self.shapetable = Table(filename)  
146            self.SetClassification(None)
147    
148            self.__classification.SetDefaultLineColor(stroke)
149            self.__classification.SetDefaultLineWidth(lineWidth)
150            self.__classification.SetDefaultFill(fill)
151            self.__classification.SetLayer(self)
152    
153            self.UnsetModified()
154    
155    
156        def SetShapeStore(self, store):
157            self.store = store
158            self.shapefile = self.store.Shapefile()
159            self.shapetable = self.store.Table()
160            self.filename = self.store.filename
161          self.table = self.shapetable          self.table = self.shapetable
162    
163          self.classification = Classification()          numshapes, shapetype, mins, maxs = self.shapefile.info()
164          self.classification.setNull(          self.numshapes = numshapes
165              {'stroke':stroke, 'stroke_width':stroke_width, 'fill':fill})          self.shapetype = shapelib_shapetypes[shapetype]
166    
167      def open_shapefile(self):          # if there are shapes, set the bbox accordingly. Otherwise
168          if self.shapefile is None:          # set it to None.
169              self.shapefile = shapelib.ShapeFile(self.filename)          if self.numshapes:
170              numshapes, shapetype, mins, maxs = self.shapefile.info()              self.bbox = mins[:2] + maxs[:2]
171              self.numshapes = numshapes          else:
172              self.shapetype = shapelib_shapetypes[shapetype]              self.bbox = None
173    
174              # if there are shapes, set the bbox accordinly. Otherwise          # estimate a good depth for the quad tree. Each depth
175              # set it to None.          # multiplies the number of nodes by four, therefore we
176              if self.numshapes:          # basically take the base 4 logarithm of the number of
177                  self.bbox = mins[:2] + maxs[:2]          # shapes.
178              else:          if self.numshapes < 4:
179                  self.bbox = None              maxdepth = 1
180            else:
181              # estimate a good depth for the quad tree. Each depth              maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
             # multiplies the number of nodes by four, therefore we  
             # basically take the base 4 logarithm of the number of  
             # shapes.  
             if self.numshapes < 4:  
                 maxdepth = 1  
             else:  
                 maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))  
182    
183              self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,          self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
184                                               maxdepth)                                           maxdepth)
185            if self.__classification is not None:
186                fieldname = self.__classification.GetField()
187                if fieldname is not None and \
188                   not self.store.Table().field_info_by_name(fieldname):
189                    self.SetClassification(None)
190            self.changed(LAYER_CHANGED, self)
191    
192        def ShapeStore(self):
193            return self.store
194    
195      def Destroy(self):      def Destroy(self):
196          BaseLayer.Destroy(self)          BaseLayer.Destroy(self)
197          if self.shapefile is not None:          self.SetClassification(None)
             self.shapefile.close()  
             self.shapefile = None  
             self.shapetree = None  
         self.table.Destroy()  
198    
199      def BoundingBox(self):      def BoundingBox(self):
200          """Return the layer's bounding box in the intrinsic coordinate system.          """Return the layer's bounding box in the intrinsic coordinate system.
201    
202          If the layer has no shapes, return None.          If the layer has no shapes, return None.
203          """          """
         # The bbox will be set by open_shapefile just as we need it  
         # here.  
         self.open_shapefile()  
204          return self.bbox          return self.bbox
205    
206      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
# Line 204  class Layer(BaseLayer): Line 218  class Layer(BaseLayer):
218          else:          else:
219              return None              return None
220    
221        def ShapesBoundingBox(self, shapes):
222            """Return a bounding box in lat/long coordinates for the given
223            list of shape ids.
224    
225            If shapes is None or empty, return None.
226            """
227    
228            if shapes is None or len(shapes) == 0: return None
229    
230            llx = []
231            lly = []
232            urx = []
233            ury = []
234    
235            if self.projection is not None:
236                inverse = lambda x, y: self.projection.Inverse(x, y)
237            else:
238                inverse = lambda x, y: (x, y)
239    
240            for id in shapes:
241                left, bottom, right, top = self.Shape(id).compute_bbox()
242    
243                left, bottom = inverse(left, bottom)
244                right, top   = inverse(right, top)
245    
246                llx.append(left)
247                lly.append(bottom)
248                urx.append(right)
249                ury.append(top)
250    
251            return (min(llx), min(lly), max(urx), max(ury))
252    
253        def GetFieldType(self, fieldName):
254            info = self.table.field_info_by_name(fieldName)
255            if info is not None:
256                return info[0]
257            else:
258                return None
259    
260      def NumShapes(self):      def NumShapes(self):
261          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
         self.open_shapefile()  
262          return self.numshapes          return self.numshapes
263    
264      def ShapeType(self):      def ShapeType(self):
265          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
266          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
267          """          """
         self.open_shapefile()  
268          return self.shapetype          return self.shapetype
269    
270      def Shape(self, index):      def Shape(self, index):
271          """Return the shape with index index"""          """Return the shape with index index"""
         self.open_shapefile()  
272          shape = self.shapefile.read_object(index)          shape = self.shapefile.read_object(index)
273    
274          if self.shapetype == SHAPETYPE_POINT:          if self.shapetype == SHAPETYPE_POINT:
# Line 235  class Layer(BaseLayer): Line 285  class Layer(BaseLayer):
285      def ShapesInRegion(self, box):      def ShapesInRegion(self, box):
286          """Return the ids of the shapes that overlap the box.          """Return the ids of the shapes that overlap the box.
287    
288          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.  
289          """          """
290          left, bottom, right, top = box          left, bottom, right, top = box
291    
292            if self.projection is not None:
293                left,  bottom = self.projection.Forward(left, bottom)
294                right, top    = self.projection.Forward(right, top)
295    
296          return self.shapetree.find_shapes((left, bottom), (right, top))          return self.shapetree.find_shapes((left, bottom), (right, top))
297    
298        def GetProjection(self):
299            return self.projection
300    
301      def SetProjection(self, projection):      def SetProjection(self, projection):
302          """Set the layer's projection"""          """Set the layer's projection"""
303          self.projection = projection          self.projection = projection
304          self.changed(LAYER_PROJECTION_CHANGED, self)          self.changed(LAYER_PROJECTION_CHANGED, self)
305    
306      def SetFill(self, fill):      def GetClassification(self):
307          """Set the layer's fill color. None means the shapes are not filled"""          return self.__classification
308          self.fill = fill  
309          self.changed(LAYER_LEGEND_CHANGED, self)      def SetClassification(self, clazz):
310            """Set the classification to 'clazz'
311      def SetStroke(self, stroke):  
312          """Set the layer's stroke color. None means the shapes are not          If 'clazz' is None a default classification is created
313          stroked."""          """
314          self.stroke = stroke  
315          self.changed(LAYER_LEGEND_CHANGED, self)          # prevent infinite recursion when calling SetLayer()
316            if self.__setClassLock: return
     def SetStrokeWidth(self, width):  
         """Set the layer's stroke width."""  
         self.stroke_width = width  
         self.changed(LAYER_LEGEND_CHANGED, self)  
317    
318            self.__setClassLock = True
319    
320            if clazz is None:
321                if self.__classification is not None:
322                    self.__classification.SetLayer(None)
323                self.__classification = classification.Classification()
324            else:
325                self.__classification = clazz
326                try:
327                    self.__classification.SetLayer(self)
328                except ValueError:
329                    self.__setClassLock = False
330                    raise ValueError
331    
332            self.changed(LAYER_CHANGED, self)
333    
334            self.__setClassLock = False
335    
336        def ClassChanged(self):
337            """Called from the classification object when it has changed."""
338            self.changed(LAYER_CHANGED, self)
339    
340      def TreeInfo(self):      def TreeInfo(self):
341          items = []          items = []
342    
343          if self.Visible():          if self.Visible():
344              items.append("Shown")              items.append(_("Shown"))
345          else:          else:
346              items.append("Hidden")              items.append(_("Hidden"))
347          items.append("Shapes: %d" % self.NumShapes())          items.append(_("Shapes: %d") % self.NumShapes())
348    
349          bbox = self.LatLongBoundingBox()          bbox = self.LatLongBoundingBox()
350          if bbox is not None:          if bbox is not None:
351              items.append("Extent (lat-lon): (%g, %g, %g, %g)" % bbox)              items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
352          else:          else:
353              items.append("Extent (lat-lon):")              items.append(_("Extent (lat-lon):"))
354          items.append("Shapetype: %s" % shapetype_names[self.ShapeType()])          items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
355    
356            if self.projection and len(self.projection.params) > 0:
357                items.append((_("Projection"),
358                            [str(param) for param in self.projection.params]))
359    
360            items.append(self.__classification)
361    
362            return (_("Layer '%s'") % self.Title(), items)
363    
         def color_string(color):  
             if color is None:  
                 return "None"  
             return "(%.3f, %.3f, %.3f)" % (color.red, color.green, color.blue)  
         items.append("Fill: " + color_string(self.fill))  
         items.append("Outline: " + color_string(self.stroke))  
364    
         return ("Layer '%s'" % self.Title(), items)  

Legend:
Removed from v.364  
changed lines
  Added in v.828

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26