/[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 6 by bh, Tue Aug 28 15:41:52 2001 UTC revision 839 by bh, Tue May 6 15:54:18 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 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 _
      LAYER_VISIBILITY_CHANGED  
14    
15  from color import Color  import shapelib, shptree
16  # Some predefined colors for internal use  
17  _black = Color(0, 0, 0)  from messages import LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED, \
18         LAYER_CHANGED
19    
20    from color import Color
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 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):
78          """Initialize the layer.          """Initialize the layer.
79    
80          title -- the title          title -- the title
# Line 81  class BaseLayer(TitledObject, Modifiable Line 92  class BaseLayer(TitledObject, Modifiable
92          """Set the layer's visibility."""          """Set the layer's visibility."""
93          self.visible = visible          self.visible = visible
94          self.issue(LAYER_VISIBILITY_CHANGED, self)          self.issue(LAYER_VISIBILITY_CHANGED, self)
95            
96            
97  class Layer(BaseLayer):  class Layer(BaseLayer):
98    
99      """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 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, 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          self.filename = filename  
135          self.projection = projection          self.projection = projection
136          self.fill = fill  
137          self.stroke = stroke          #
138          self.shapefile = None          # this is really important so that when the classification class
139          # shapetable is the table associated with the shapefile, while          # tries to set its parent layer the variable will exist
140          # table is the default table used to look up attributes for          #
141          # display          self.__classification = None
142          self.shapetable = Table(filename)          self.__setClassLock = False
143    
144            self.SetShapeStore(data)
145    
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      def open_shapefile(self):          numshapes, shapetype, mins, maxs = self.shapefile.info()
164          if self.shapefile is None:          self.numshapes = numshapes
165              self.shapefile = shapelib.ShapeFile(self.filename)          self.shapetype = shapelib_shapetypes[shapetype]
166              numshapes, shapetype, mins, maxs = self.shapefile.info()  
167              self.numshapes = numshapes          # if there are shapes, set the bbox accordingly. Otherwise
168              self.shapetype = shapelib_shapetypes[shapetype]          # set it to None.
169            if self.numshapes:
170              self.bbox = mins[:2] + maxs[:2]              self.bbox = mins[:2] + maxs[:2]
171            else:
172                self.bbox = None
173    
174            # estimate a good depth for the quad tree. Each depth
175            # multiplies the number of nodes by four, therefore we
176            # basically take the base 4 logarithm of the number of
177            # shapes.
178            if self.numshapes < 4:
179                maxdepth = 1
180            else:
181                maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))
182    
183            self.shapetree = shptree.SHPTree(self.shapefile.cobject(), 2,
184                                             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().HasColumn(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):
196            BaseLayer.Destroy(self)
197            self.SetClassification(None)
198    
199      def BoundingBox(self):      def BoundingBox(self):
200          """Return the bounding box of the layer's shapes in their default          """Return the layer's bounding box in the intrinsic coordinate system.
201          coordinate system"""  
202          self.open_shapefile()          If the layer has no shapes, return None.
203            """
204          return self.bbox          return self.bbox
205    
206      def LatLongBoundingBox(self):      def LatLongBoundingBox(self):
207          """Return the layer's bounding box in lat/long coordinates"""          """Return the layer's bounding box in lat/long coordinates.
208          llx, lly, urx, ury = self.BoundingBox()  
209            Return None, if the layer doesn't contain any shapes.
210            """
211            bbox = self.BoundingBox()
212            if bbox is not None:
213                llx, lly, urx, ury = bbox
214                if self.projection is not None:
215                    llx, lly = self.projection.Inverse(llx, lly)
216                    urx, ury = self.projection.Inverse(urx, ury)
217                return llx, lly, urx, ury
218            else:
219                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:          if self.projection is not None:
236              llx, lly = self.projection.Inverse(llx, lly)              inverse = lambda x, y: self.projection.Inverse(x, y)
237              urx, ury = self.projection.Inverse(urx, ury)          else:
238          return llx, lly, urx, ury              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            if self.table.HasColumn(fieldName):
255                return self.table.Column(fieldName).type
256            return None
257    
258      def NumShapes(self):      def NumShapes(self):
259          """Return the number of shapes in the layer"""          """Return the number of shapes in the layer"""
         self.open_shapefile()  
260          return self.numshapes          return self.numshapes
261    
262      def ShapeType(self):      def ShapeType(self):
263          """Return the type of the shapes in the layer.          """Return the type of the shapes in the layer.
264          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.          This is either SHAPETYPE_POINT, SHAPETYPE_ARC or SHAPETYPE_POLYGON.
265          """          """
         self.open_shapefile()  
266          return self.shapetype          return self.shapetype
267    
268      def Shape(self, index):      def Shape(self, index):
269          """Return the shape with index index"""          """Return the shape with index index"""
         self.open_shapefile()  
270          shape = self.shapefile.read_object(index)          shape = self.shapefile.read_object(index)
271    
272          if self.shapetype == SHAPETYPE_POINT:          if self.shapetype == SHAPETYPE_POINT:
273              points = shape.vertices()              points = shape.vertices()
274          else:          else:
# Line 173  class Layer(BaseLayer): Line 276  class Layer(BaseLayer):
276              poly = shape.vertices()[0]              poly = shape.vertices()[0]
277              points = []              points = []
278              for x, y in poly:              for x, y in poly:
279                  points.append(x, y)                  points.append((x, y))
280    
281          return Shape(points)          return Shape(points)
282    
283        def ShapesInRegion(self, box):
284            """Return the ids of the shapes that overlap the box.
285    
286            Box is a tuple (left, bottom, right, top) in unprojected coordinates.
287            """
288            left, bottom, right, top = box
289    
290            if self.projection is not None:
291                left,  bottom = self.projection.Forward(left, bottom)
292                right, top    = self.projection.Forward(right, top)
293    
294            return self.shapetree.find_shapes((left, bottom), (right, top))
295    
296        def GetProjection(self):
297            return self.projection
298    
299      def SetProjection(self, projection):      def SetProjection(self, projection):
300          """Set the layer's projection"""          """Set the layer's projection"""
301          self.projection = projection          self.projection = projection
302          self.changed(LAYER_PROJECTION_CHANGED, self)          self.changed(LAYER_PROJECTION_CHANGED, self)
303    
304      def SetFill(self, fill):      def GetClassification(self):
305          """Set the layer's fill color. None means the shapes are not filled"""          return self.__classification
306          self.fill = fill  
307          self.changed(LAYER_LEGEND_CHANGED, self)      def SetClassification(self, clazz):
308            """Set the classification to 'clazz'
309      def SetStroke(self, stroke):  
310          """Set the layer's stroke color. None means the shapes are not          If 'clazz' is None a default classification is created
311          stroked."""          """
312          self.stroke = stroke  
313          self.changed(LAYER_LEGEND_CHANGED, self)          # prevent infinite recursion when calling SetLayer()
314            if self.__setClassLock: return
315    
316            self.__setClassLock = True
317    
318            if clazz is None:
319                if self.__classification is not None:
320                    self.__classification.SetLayer(None)
321                self.__classification = classification.Classification()
322            else:
323                self.__classification = clazz
324                try:
325                    self.__classification.SetLayer(self)
326                except ValueError:
327                    self.__setClassLock = False
328                    raise ValueError
329    
330            self.changed(LAYER_CHANGED, self)
331    
332            self.__setClassLock = False
333    
334        def ClassChanged(self):
335            """Called from the classification object when it has changed."""
336            self.changed(LAYER_CHANGED, self)
337    
338        def TreeInfo(self):
339            items = []
340    
341            if self.Visible():
342                items.append(_("Shown"))
343            else:
344                items.append(_("Hidden"))
345            items.append(_("Shapes: %d") % self.NumShapes())
346    
347            bbox = self.LatLongBoundingBox()
348            if bbox is not None:
349                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
350            else:
351                items.append(_("Extent (lat-lon):"))
352            items.append(_("Shapetype: %s") % shapetype_names[self.ShapeType()])
353    
354            if self.projection and len(self.projection.params) > 0:
355                items.append((_("Projection"),
356                            [str(param) for param in self.projection.params]))
357    
358            items.append(self.__classification)
359    
360            return (_("Layer '%s'") % self.Title(), items)
361    
362    

Legend:
Removed from v.6  
changed lines
  Added in v.839

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26