/[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 171 by bh, Tue May 14 14:16:24 2002 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.
# Line 9  __version__ = "$Revision$" Line 10  __version__ = "$Revision$"
10    
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)  
   
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 30  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 42  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 65  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 83  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 99  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          self.filename = 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      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          # estimate a good depth for the quad tree. Each depth
175              # multiplies the number of nodes by four, therefore we          # multiplies the number of nodes by four, therefore we
176              # basically take the base 4 logarithm of the number of          # basically take the base 4 logarithm of the number of
177              # shapes.          # shapes.
178              if self.numshapes < 4:          if self.numshapes < 4:
179                  maxdepth = 1              maxdepth = 1
180              else:          else:
181                  maxdepth = int(ceil(log(self.numshapes / 4.0) / log(4)))              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):
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            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:
275              points = shape.vertices()              points = shape.vertices()
276          else:          else:
# Line 191  class Layer(BaseLayer): Line 279  class Layer(BaseLayer):
279              points = []              points = []
280              for x, y in poly:              for x, y in poly:
281                  points.append((x, y))                  points.append((x, y))
282    
283          return Shape(points)          return Shape(points)
284    
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
317      def SetStrokeWidth(self, width):  
318          """Set the layer's stroke width."""          self.__setClassLock = True
319          self.stroke_width = width  
320          self.changed(LAYER_LEGEND_CHANGED, self)          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):
341            items = []
342    
343            if self.Visible():
344                items.append(_("Shown"))
345            else:
346                items.append(_("Hidden"))
347            items.append(_("Shapes: %d") % self.NumShapes())
348    
349            bbox = self.LatLongBoundingBox()
350            if bbox is not None:
351                items.append(_("Extent (lat-lon): (%g, %g, %g, %g)") % bbox)
352            else:
353                items.append(_("Extent (lat-lon):"))
354            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    
364    

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26