/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/baserenderer.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/UI/baserenderer.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1554 by bh, Wed Aug 6 17:24:30 2003 UTC revision 2377 by jan, Sun Oct 3 21:45:48 2004 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4  # Jonathan Coles <[email protected]>  # Jonathan Coles <[email protected]>
# Line 14  be tested reasonably easily and it could Line 14  be tested reasonably easily and it could
14  renderers.  renderers.
15  """  """
16    
17    from __future__ import generators
18    
19  __version__ = "$Revision$"  __version__ = "$Revision$"
20  # $Source$  # $Source$
21  # $Id$  # $Id$
22    
23    import sys
24  import traceback  import traceback
25    
26  from Thuban.Model.layer import Layer, RasterLayer  from Thuban.Model.layer import Layer, RasterLayer
# Line 31  if Thuban.Model.resource.has_gdal_suppor Line 34  if Thuban.Model.resource.has_gdal_suppor
34      from gdalwarp import ProjectRasterFile      from gdalwarp import ProjectRasterFile
35    
36    
37    #
38    #       Renderer Extensions
39    #
40    # The renderer extensions provide a way to render layer types defined in
41    # Thuban extensions. The renderer extensions are stored as a list with
42    # (layer_class, draw_function) pairs. If the renderer has to draw a
43    # non-builtin layer type, i.e. a layer that is not a subclass of Layer
44    # or RasterLayer, it iterates through that list, tests whether the layer
45    # to be drawn is an instance of layer_class and if so calls
46    # draw_function with the renderer and the layer as arguments. Since
47    # drawing is done incrementally, the draw_function should return an
48    # iterable. The easiest way is to simply implement the draw_function as
49    # a generator and to yield in suitable places, or to return the empty
50    # tuple.
51    #
52    # New renderer extensions should be added with add_renderer_extension().
53    # If necessary the extensions list can be reset with
54    # init_renderer_extensions().
55    
56    _renderer_extensions = []
57    
58    def add_renderer_extension(layer_class, function):
59        """Add a renderer extension for the layer class layer_class
60    
61        When an instance of layer_class is to be drawn by the renderer the
62        renderer will call function with the renderer and the layer_class
63        instance as arguments. Since drawing is done incrementally, the
64        function should return an iterable. The easiest way is to simply
65        implement the draw_function as a generator and to yield True in
66        suitable places, or to return the empty tuple if it's not possible
67        to do the rendering incrementally.
68        """
69        _renderer_extensions.append((layer_class, function))
70    
71    def init_renderer_extensions():
72        """(Re)initialize the list of renderer extensions
73    
74        Calling this function outside of the test suite is probably not
75        useful.
76        """
77        del _renderer_extensions[:]
78    
79    
80    #
81    #       Base Renderer
82    #
83    
84  class BaseRenderer:  class BaseRenderer:
85    
86      """Basic Renderer Infrastructure for Thuban Maps      """Basic Renderer Infrastructure for Thuban Maps
# Line 49  class BaseRenderer: Line 99  class BaseRenderer:
99      TRANSPARENT_PEN = None      TRANSPARENT_PEN = None
100      TRANSPARENT_BRUSH = None      TRANSPARENT_BRUSH = None
101    
102      def __init__(self, dc, scale, offset, resolution = 72.0,      def __init__(self, dc, map, scale, offset, region = None,
103                   honor_visibility = None):                   resolution = 72.0, honor_visibility = None):
104          """Inititalize the renderer.          """Inititalize the renderer.
105    
106          dc -- the device context to render on.          dc -- the device context to render on.
# Line 58  class BaseRenderer: Line 108  class BaseRenderer:
108          scale, offset -- the scale factor and translation to convert          scale, offset -- the scale factor and translation to convert
109                  between projected coordinates and the DC coordinates                  between projected coordinates and the DC coordinates
110    
111            region -- The region to draw as a (x, y, width, height) tuple in
112                      the map's coordinate system. Default is None meaning
113                      to draw everything.
114    
115          resolution -- the assumed resolution of the DC. Used to convert          resolution -- the assumed resolution of the DC. Used to convert
116                  absolute lengths like font sizes to DC coordinates. The                  absolute lengths like font sizes to DC coordinates. The
117                  defauult is 72.0                  default is 72.0. If given, this parameter must be
118                    provided as a keyword argument.
119    
120          honor_visibility -- boolean. If true, honor the visibility flag          honor_visibility -- boolean. If true, honor the visibility flag
121                  of the layers, otherwise draw all layers. If None (the                  of the layers, otherwise draw all layers. If None (the
122                  default), use the renderer's default.                  default), use the renderer's default. If given, this
123                    parameter must be provided as a keyword argument.
124          """          """
125          # resolution in pixel/inch          # resolution in pixel/inch
   
126          self.dc = dc          self.dc = dc
127            self.map = map
128          self.scale = scale          self.scale = scale
129          self.offset = offset          self.offset = offset
130            self.region = region
131          if honor_visibility is not None:          if honor_visibility is not None:
132              self.honor_visibility = honor_visibility              self.honor_visibility = honor_visibility
133          # store the resolution in pixel/point because it's more useful          # store the resolution in pixel/point because it's more useful
# Line 85  class BaseRenderer: Line 142  class BaseRenderer:
142          """          """
143          raise NotImplementedError          raise NotImplementedError
144    
145      def render_map(self, map):      def render_map(self):
146          """Render the map onto the DC the renderer was instantiated with          """Render the map onto the DC.
147    
148            Both map and DC are the ones the renderer was instantiated with.
149    
150            This method is just a front-end for render_map_incrementally
151            which does all rendering in one go. It also calls the dc's
152            BeginDrawing and EndDrawing methods before and after calling
153            render_map_incrementally.
154            """
155    
156            self.dc.BeginDrawing()
157            try:
158                for cont in self.render_map_incrementally():
159                    pass
160            finally:
161                self.dc.EndDrawing()
162    
163        def render_map_incrementally(self):
164            """Render the map incrementally.
165    
166            Return an iterator whose next method should be called until it
167            returns False. After returning False it will raise StopIteration
168            so that you could also use it in a for loop (implementation
169            detail: this method is implemented as a generator).
170    
171          Iterate through all layers and draw them. Layers containing          Iterate through all layers and draw them. Layers containing
172          vector data are drawn with the draw_shape_layer method, raster          vector data are drawn with the draw_shape_layer method, raster
# Line 97  class BaseRenderer: Line 177  class BaseRenderer:
177          that methods especially in derived classes have access to the          that methods especially in derived classes have access to the
178          map if necessary.          map if necessary.
179          """          """
         # Some method have to have access to the map so we store it in  
         # self.map.  
         self.map = map  
   
180          # Whether the raster layer has already been drawn. See below for          # Whether the raster layer has already been drawn. See below for
181          # the optimization this is used for          # the optimization this is used for
182          seenRaster = True          seenRaster = True
183    
184          self.dc.BeginDrawing()          #
185            # This is only a good optimization if there is only one
186            # raster layer and the image covers the entire window (as
187            # it currently does). We note if there is a raster layer
188            # and only begin drawing layers once we have drawn it.
189            # That way we avoid drawing layers that won't be seen.
190            #
191            if Thuban.Model.resource.has_gdal_support():
192                for layer in self.map.Layers():
193                    if isinstance(layer, RasterLayer) and layer.Visible():
194                        seenRaster = False
195                        break
196    
197            for layer in self.map.Layers():
198                # if honor_visibility is true, only draw visible layers,
199                # otherwise draw all layers
200                if not self.honor_visibility or layer.Visible():
201                    if isinstance(layer, Layer):
202                        if seenRaster:
203                            for i in self.draw_shape_layer_incrementally(layer):
204                                yield True
205                    elif isinstance(layer, RasterLayer) \
206                        and Thuban.Model.resource.has_gdal_support():
207                        self.draw_raster_layer(layer)
208                        seenRaster = True
209                        yield True
210                    else:
211                        # look it up in the renderer extensions
212                        for cls, func in _renderer_extensions:
213                            if isinstance(layer, cls):
214                                for i in func(self, layer):
215                                    yield True
216                                break
217                        else:
218                            # No renderer found. Print a message about it
219                            print >>sys.stderr, ("Drawing layer %r not supported"
220                                                 % layer)
221                yield True
222    
223          try:          self.draw_label_layer(self.map.LabelLayer())
224              #          yield False
             # This is only a good optimization if there is only one  
             # raster layer and the image covers the entire window (as  
             # it currently does). We note if there is a raster layer  
             # and only begin drawing layers once we have drawn it.  
             # That way we avoid drawing layers that won't be seen.  
             #  
             if Thuban.Model.resource.has_gdal_support():  
                 for layer in map.Layers():  
                     if isinstance(layer, RasterLayer) and layer.Visible():  
                         seenRaster = False  
                         break  
   
             for layer in map.Layers():  
                 # if honor_visibility is true, only draw visible layers,  
                 # otherwise draw all layers  
                 if not self.honor_visibility or layer.Visible():  
                     if isinstance(layer, Layer) and seenRaster:  
                         self.draw_shape_layer(layer)  
                     elif isinstance(layer, RasterLayer) \  
                         and Thuban.Model.resource.has_gdal_support():  
                         self.draw_raster_layer(layer)  
                         seenRaster = True  
225    
226              self.draw_label_layer(map.LabelLayer())      def draw_shape_layer_incrementally(self, layer):
227          finally:          """Draw the shape layer layer onto the map incrementally.
             self.dc.EndDrawing()  
228    
229      def draw_shape_layer(self, layer):          This method is a generator which yields True after every 500
230          """Draw the shape layer layer onto the map.          shapes.
   
         Automatically called by render_map. Iterate through all shapes  
         as indicated by self.layer_ids() and draw them, using low-level  
         renderers returned by self.low_level_renderer().  
231          """          """
232          scale = self.scale          scale = self.scale
233          offx, offy = self.offset          offx, offy = self.offset
# Line 159  class BaseRenderer: Line 245  class BaseRenderer:
245          defaultGroup = lc.GetDefaultGroup()          defaultGroup = lc.GetDefaultGroup()
246          table = layer.ShapeStore().Table()          table = layer.ShapeStore().Table()
247    
248            if lc.GetNumGroups() == 0:
249                # There's only the default group, so we can pretend that
250                # there is no field to classifiy on which makes things
251                # faster since we don't need the attribute information at
252                # all.
253                field = None
254    
255          # Determine which render function to use.          # Determine which render function to use.
256          draw_func, draw_func_param = self.low_level_renderer(layer)          useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
257    
258            #
259          # Iterate through all shapes that have to be drawn.          # Iterate through all shapes that have to be drawn.
260          for i in self.layer_ids(layer):          #
261    
262            # Count the shapes drawn so that we can yield every few hundred
263            # shapes
264            count = 0
265    
266            # Cache the tools (pens and brushes) for the classification
267            # groups. This is a mapping from the group's ids to the a tuple
268            # (pen, brush)
269            tool_cache = {}
270    
271            for shape in self.layer_shapes(layer):
272                count += 1
273              if field is None:              if field is None:
274                  group = defaultGroup                  group = defaultGroup
275              else:              else:
276                  record = table.ReadRowAsDict(i)                  value = table.ReadValue(shape.ShapeID(), field)
277                  assert record is not None                  group = lc.FindGroup(value)
                 group = lc.FindGroup(record[field])  
278    
279              if not group.IsVisible():              if not group.IsVisible():
280                  continue                  continue
281    
282              # don't recreate new objects if they are the same as before              try:
283              if group is not old_group:                  pen, brush = tool_cache[id(group)]
284                  old_group = group              except KeyError:
285                    pen, brush = tool_cache[id(group)] \
286                  prop = group.GetProperties()                               = self.tools_for_property(group.GetProperties())
   
                 if prop != old_prop:  
                     pen, brush = self.tools_for_property(prop)  
287    
288              draw_func(draw_func_param, i, pen, brush)              if useraw:
289                    data = shape.RawData()
290                else:
291                    data = shape.Points()
292                if draw_func == self.draw_point_shape:
293                     draw_func(draw_func_param, data, pen, brush,
294                               size = group.GetProperties().GetSize())
295                else:
296                     draw_func(draw_func_param, data, pen, brush)
297                if count % 500 == 0:
298                    yield True
299    
300      def layer_ids(self, layer):      def layer_shapes(self, layer):
301          """Return the shape ids of the given layer that have to be drawn.          """Return an iterable over the shapes to be drawn from the given layer.
302    
303          The default implementation simply returns all ids in the layer.          The default implementation simply returns all ids in the layer.
304          Override in derived classes to be more precise.          Override in derived classes to be more precise.
305          """          """
306          return range(layer.NumShapes())          return layer.ShapeStore().AllShapes()
307    
308      def low_level_renderer(self, layer):      def low_level_renderer(self, layer):
309          """Return the low-level renderer for the layer for draw_shape_layer          """Return the low-level renderer for the layer for draw_shape_layer
310    
311          The low level renderer to be returned by this method is a tuple          The low level renderer to be returned by this method is a tuple
312          (func, param) where func is a callable object and param is          (useraw, func, param) where useraw is a boolean indicating
313          passed as the first parameter to func. The draw_shape_layer          whether the function uses the raw shape data, func is a callable
314          method will call func like this:          object and param is passed as the first parameter to func. The
315            draw_shape_layer method will call func like this:
316              func(param, rawshape, pen, brush)  
317                func(param, shapedata, pen, brush)
318          where rawshape is currently the shapeid. pen and brush are the  
319          pen and brush to use to draw the shape on the dc.          where shapedata is the return value of the RawData method of the
320            shape object if useraw is true or the return value of the Points
321            method if it's false. pen and brush are the pen and brush to use
322            to draw the shape on the dc.
323    
324          The default implementation returns one of          The default implementation returns one of
325          self.draw_polygon_shape, self.draw_arc_shape or          self.draw_polygon_shape, self.draw_arc_shape or
326          self.draw_point_shape as func and layer as param. Derived          self.draw_point_shape as func and layer as param. None of the
327          classes can override this method to return more efficient low          method use the raw shape data. Derived classes can override this
328          level renderers.          method to return more efficient low level renderers.
329          """          """
330          shapetype = layer.ShapeType()          shapetype = layer.ShapeType()
331          if shapetype == SHAPETYPE_POINT:          if shapetype == SHAPETYPE_POINT:
# Line 220  class BaseRenderer: Line 334  class BaseRenderer:
334              func = self.draw_arc_shape              func = self.draw_arc_shape
335          else:          else:
336              func = self.draw_polygon_shape              func = self.draw_polygon_shape
337          return func, layer          return False, func, layer
338    
339      def make_point(self, x, y):      def make_point(self, x, y):
340          """Convert (x, y) to a point object.          """Convert (x, y) to a point object.
# Line 229  class BaseRenderer: Line 343  class BaseRenderer:
343          """          """
344          raise NotImplementedError          raise NotImplementedError
345    
346      def projected_points(self, layer, shapeid):      def projected_points(self, layer, points):
347          """Return the projected coordinates of shape shapeid in layer.          """Return the projected coordinates of the points taken from layer.
348    
349          Read the shape from the layer through its Shape method and          Transform all the points in the list of lists of coordinate
350          transform all the points in the list of lists of coordinate          pairs in points.
         pairs returned by the shape's Points method.  
351    
352          The transformation applies the inverse of the layer's projection          The transformation applies the inverse of the layer's projection
353          if any, then the map's projection if any and finally applies          if any, then the map's projection if any and finally applies
# Line 253  class BaseRenderer: Line 366  class BaseRenderer:
366              inverse = proj.Inverse              inverse = proj.Inverse
367          else:          else:
368              inverse = None              inverse = None
369          shape = layer.Shape(shapeid)          result = []
         points = []  
370          scale = self.scale          scale = self.scale
371          offx, offy = self.offset          offx, offy = self.offset
372          make_point = self.make_point          make_point = self.make_point
373          for part in shape.Points():          for part in points:
374              points.append([])              result.append([])
375              for x, y in part:              for x, y in part:
376                  if inverse:                  if inverse:
377                      x, y = inverse(x, y)                      x, y = inverse(x, y)
378                  if forward:                  if forward:
379                      x, y = forward(x, y)                      x, y = forward(x, y)
380                  points[-1].append(make_point(x * scale + offx,                  result[-1].append(make_point(x * scale + offx,
381                                               -y * scale + offy))                                               -y * scale + offy))
382          return points          return result
383    
384      def draw_polygon_shape(self, layer, index, pen, brush):      def draw_polygon_shape(self, layer, points, pen, brush):
385          """Draw a polygon shape with the given brush and pen          """Draw a polygon shape from layer with the given brush and pen
386    
387          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
388          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
389            DC's coordinate system are determined with
390          self.projected_points.          self.projected_points.
391          """          """
392          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
393    
394          if brush is not self.TRANSPARENT_BRUSH:          if brush is not self.TRANSPARENT_BRUSH:
395              polygon = []              polygon = []
# Line 298  class BaseRenderer: Line 411  class BaseRenderer:
411              for part in points:              for part in points:
412                  self.dc.DrawLines(part)                  self.dc.DrawLines(part)
413    
414      def draw_arc_shape(self, layer, index, pen, brush):      def draw_arc_shape(self, layer, points, pen, brush):
415          """Draw an arc shape with the given brush and pen          """Draw an arc shape from layer with the given brush and pen
416    
417          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
418          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
419            DC's coordinate system are determined with
420          self.projected_points.          self.projected_points.
421          """          """
422          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
423          self.dc.SetBrush(brush)          self.dc.SetBrush(brush)
424          self.dc.SetPen(pen)          self.dc.SetPen(pen)
425          for part in points:          for part in points:
426              self.dc.DrawLines(part)              self.dc.DrawLines(part)
427    
428      def draw_point_shape(self, layer, index, pen, brush):      def draw_point_shape(self, layer, points, pen, brush, size = 5):
429          """Draw a point shape with the given brush and pen          """Draw a point shape from layer with the given brush and pen
430    
431          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
432          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
433            DC's coordinate system are determined with
434          self.projected_points.          self.projected_points.
435    
436          The point is drawn as a circle centered on the point.          The point is drawn as a circle centered on the point.
437          """          """
438          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
439          if not points:          if not points:
440              return              return
441    
442          radius = self.resolution * 5          radius = int(round(self.resolution * size))
443          self.dc.SetBrush(brush)          self.dc.SetBrush(brush)
444          self.dc.SetPen(pen)          self.dc.SetPen(pen)
445          for part in points:          for part in points:
# Line 372  class BaseRenderer: Line 487  class BaseRenderer:
487              # better.              # better.
488              traceback.print_exc()              traceback.print_exc()
489          else:          else:
490              self.draw_raster_data(data)              self.draw_raster_data(data, "BMP")
491    
492      def draw_raster_data(self, data):      def draw_raster_data(self, data, format="BMP"):
493          """Draw the raster image in data onto the DC          """Draw the raster image in data onto the DC
494    
495          The raster image data is a string holding the data in BMP          The raster image data is a string holding the data in the format
496          format. The data is exactly the size of the dc and covers it          indicated by the format parameter. The image is assumed to be
497          completely.          exactly the size of the dc and to cover it completely.
498    
499            The format parameter is a string with the name of the format.
500            The following format names should be used:
501    
502              'BMP' -- Windows Bitmap
503              'JPEG' -- Jpeg
504    
505            The default format is 'bmp'.
506    
507          This method has to be implemented by derived classes.          This method has to be implemented by derived classes. The
508            implementation in the derived class should try to support at
509            least the formats specified above and may support more.
510          """          """
511          raise NotImplementedError          raise NotImplementedError
512    
# Line 411  class BaseRenderer: Line 536  class BaseRenderer:
536              text = label.text              text = label.text
537              if forward:              if forward:
538                  x, y = forward(x, y)                  x, y = forward(x, y)
539              x = x * scale + offx              x = int(round(x * scale + offx))
540              y = -y * scale + offy              y = int(round(-y * scale + offy))
541              width, height = self.dc.GetTextExtent(text)              width, height = self.dc.GetTextExtent(text)
542              if label.halign == ALIGN_LEFT:              if label.halign == ALIGN_LEFT:
543                  # nothing to be done                  # nothing to be done

Legend:
Removed from v.1554  
changed lines
  Added in v.2377

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26