/[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 2552 by jonathan, Fri Jan 28 15:54:00 2005 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    def proj_params_to_str(proj):
80        "Build a string suitable for GDAL describing the given projection"
81        str = ""
82        if proj is not None:
83            for p in proj.GetAllParameters():
84                str += "+" + p + " "
85        return str
86    
87    #
88    #       Base Renderer
89    #
90    
91  class BaseRenderer:  class BaseRenderer:
92    
93      """Basic Renderer Infrastructure for Thuban Maps      """Basic Renderer Infrastructure for Thuban Maps
# Line 49  class BaseRenderer: Line 106  class BaseRenderer:
106      TRANSPARENT_PEN = None      TRANSPARENT_PEN = None
107      TRANSPARENT_BRUSH = None      TRANSPARENT_BRUSH = None
108    
109      def __init__(self, dc, scale, offset, resolution = 72.0,      def __init__(self, dc, map, scale, offset, region = None,
110                   honor_visibility = None):                   resolution = 72.0, honor_visibility = None):
111          """Inititalize the renderer.          """Inititalize the renderer.
112    
113          dc -- the device context to render on.          dc -- the device context to render on.
# Line 58  class BaseRenderer: Line 115  class BaseRenderer:
115          scale, offset -- the scale factor and translation to convert          scale, offset -- the scale factor and translation to convert
116                  between projected coordinates and the DC coordinates                  between projected coordinates and the DC coordinates
117    
118            region -- The region to draw as a (x, y, width, height) tuple in
119                      the map's coordinate system. Default is None meaning
120                      to draw everything.
121    
122          resolution -- the assumed resolution of the DC. Used to convert          resolution -- the assumed resolution of the DC. Used to convert
123                  absolute lengths like font sizes to DC coordinates. The                  absolute lengths like font sizes to DC coordinates. The
124                  defauult is 72.0                  default is 72.0. If given, this parameter must be
125                    provided as a keyword argument.
126    
127          honor_visibility -- boolean. If true, honor the visibility flag          honor_visibility -- boolean. If true, honor the visibility flag
128                  of the layers, otherwise draw all layers. If None (the                  of the layers, otherwise draw all layers. If None (the
129                  default), use the renderer's default.                  default), use the renderer's default. If given, this
130                    parameter must be provided as a keyword argument.
131          """          """
132          # resolution in pixel/inch          # resolution in pixel/inch
   
133          self.dc = dc          self.dc = dc
134            self.map = map
135          self.scale = scale          self.scale = scale
136          self.offset = offset          self.offset = offset
137            self.region = region
138          if honor_visibility is not None:          if honor_visibility is not None:
139              self.honor_visibility = honor_visibility              self.honor_visibility = honor_visibility
140          # 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 149  class BaseRenderer:
149          """          """
150          raise NotImplementedError          raise NotImplementedError
151    
152      def render_map(self, map):      def render_map(self):
153          """Render the map onto the DC the renderer was instantiated with          """Render the map onto the DC.
154    
155            Both map and DC are the ones the renderer was instantiated with.
156    
157            This method is just a front-end for render_map_incrementally
158            which does all rendering in one go. It also calls the dc's
159            BeginDrawing and EndDrawing methods before and after calling
160            render_map_incrementally.
161            """
162    
163            self.dc.BeginDrawing()
164            try:
165                for cont in self.render_map_incrementally():
166                    pass
167            finally:
168                self.dc.EndDrawing()
169    
170        def render_map_incrementally(self):
171            """Render the map incrementally.
172    
173            Return an iterator whose next method should be called until it
174            returns False. After returning False it will raise StopIteration
175            so that you could also use it in a for loop (implementation
176            detail: this method is implemented as a generator).
177    
178          Iterate through all layers and draw them. Layers containing          Iterate through all layers and draw them. Layers containing
179          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 184  class BaseRenderer:
184          that methods especially in derived classes have access to the          that methods especially in derived classes have access to the
185          map if necessary.          map if necessary.
186          """          """
         # Some method have to have access to the map so we store it in  
         # self.map.  
         self.map = map  
187    
188          # Whether the raster layer has already been drawn. See below for          for layer in self.map.Layers():
189          # the optimization this is used for              # if honor_visibility is true, only draw visible layers,
190          seenRaster = True              # otherwise draw all layers
191                if not self.honor_visibility or layer.Visible():
192                    if isinstance(layer, Layer):
193                        for i in self.draw_shape_layer_incrementally(layer):
194                            yield True
195                    elif isinstance(layer, RasterLayer) \
196                        and Thuban.Model.resource.has_gdal_support():
197                        self.draw_raster_layer(layer)
198                        yield True
199                    else:
200                        # look it up in the renderer extensions
201                        for cls, func in _renderer_extensions:
202                            if isinstance(layer, cls):
203                                for i in func(self, layer):
204                                    yield True
205                                break
206                        else:
207                            # No renderer found. Print a message about it
208                            print >>sys.stderr, ("Drawing layer %r not supported"
209                                                 % layer)
210                yield True
211    
212          self.dc.BeginDrawing()          self.draw_label_layer(self.map.LabelLayer())
213            yield False
214    
215          try:      def draw_shape_layer_incrementally(self, layer):
216              #          """Draw the shape layer layer onto the map incrementally.
             # 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  
217    
218              self.draw_label_layer(map.LabelLayer())          This method is a generator which yields True after every 500
219          finally:          shapes.
             self.dc.EndDrawing()  
   
     def draw_shape_layer(self, layer):  
         """Draw the shape layer layer onto the map.  
   
         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().  
220          """          """
221          scale = self.scale          scale = self.scale
222          offx, offy = self.offset          offx, offy = self.offset
# Line 159  class BaseRenderer: Line 234  class BaseRenderer:
234          defaultGroup = lc.GetDefaultGroup()          defaultGroup = lc.GetDefaultGroup()
235          table = layer.ShapeStore().Table()          table = layer.ShapeStore().Table()
236    
237            if lc.GetNumGroups() == 0:
238                # There's only the default group, so we can pretend that
239                # there is no field to classifiy on which makes things
240                # faster since we don't need the attribute information at
241                # all.
242                field = None
243    
244          # Determine which render function to use.          # Determine which render function to use.
245          draw_func, draw_func_param = self.low_level_renderer(layer)          useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
246    
247            #
248          # Iterate through all shapes that have to be drawn.          # Iterate through all shapes that have to be drawn.
249          for i in self.layer_ids(layer):          #
250    
251            # Count the shapes drawn so that we can yield every few hundred
252            # shapes
253            count = 0
254    
255            # Cache the tools (pens and brushes) for the classification
256            # groups. This is a mapping from the group's ids to the a tuple
257            # (pen, brush)
258            tool_cache = {}
259    
260            for shape in self.layer_shapes(layer):
261                count += 1
262              if field is None:              if field is None:
263                  group = defaultGroup                  group = defaultGroup
264              else:              else:
265                  record = table.ReadRowAsDict(i)                  value = table.ReadValue(shape.ShapeID(), field)
266                  assert record is not None                  group = lc.FindGroup(value)
                 group = lc.FindGroup(record[field])  
267    
268              if not group.IsVisible():              if not group.IsVisible():
269                  continue                  continue
270    
271              # don't recreate new objects if they are the same as before              try:
272              if group is not old_group:                  pen, brush = tool_cache[id(group)]
273                  old_group = group              except KeyError:
274                    pen, brush = tool_cache[id(group)] \
275                  prop = group.GetProperties()                               = self.tools_for_property(group.GetProperties())
276    
277                  if prop != old_prop:              if useraw:
278                      pen, brush = self.tools_for_property(prop)                  data = shape.RawData()
279                else:
280              draw_func(draw_func_param, i, pen, brush)                  data = shape.Points()
281                if draw_func == self.draw_point_shape:
282                     draw_func(draw_func_param, data, pen, brush,
283                               size = group.GetProperties().GetSize())
284                else:
285                     draw_func(draw_func_param, data, pen, brush)
286                if count % 500 == 0:
287                    yield True
288    
289      def layer_ids(self, layer):      def layer_shapes(self, layer):
290          """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.
291    
292          The default implementation simply returns all ids in the layer.          The default implementation simply returns all ids in the layer.
293          Override in derived classes to be more precise.          Override in derived classes to be more precise.
294          """          """
295          return range(layer.NumShapes())          return layer.ShapeStore().AllShapes()
296    
297      def low_level_renderer(self, layer):      def low_level_renderer(self, layer):
298          """Return the low-level renderer for the layer for draw_shape_layer          """Return the low-level renderer for the layer for draw_shape_layer
299    
300          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
301          (func, param) where func is a callable object and param is          (useraw, func, param) where useraw is a boolean indicating
302          passed as the first parameter to func. The draw_shape_layer          whether the function uses the raw shape data, func is a callable
303          method will call func like this:          object and param is passed as the first parameter to func. The
304            draw_shape_layer method will call func like this:
305              func(param, rawshape, pen, brush)  
306                func(param, shapedata, pen, brush)
307          where rawshape is currently the shapeid. pen and brush are the  
308          pen and brush to use to draw the shape on the dc.          where shapedata is the return value of the RawData method of the
309            shape object if useraw is true or the return value of the Points
310            method if it's false. pen and brush are the pen and brush to use
311            to draw the shape on the dc.
312    
313          The default implementation returns one of          The default implementation returns one of
314          self.draw_polygon_shape, self.draw_arc_shape or          self.draw_polygon_shape, self.draw_arc_shape or
315          self.draw_point_shape as func and layer as param. Derived          self.draw_point_shape as func and layer as param. None of the
316          classes can override this method to return more efficient low          method use the raw shape data. Derived classes can override this
317          level renderers.          method to return more efficient low level renderers.
318          """          """
319          shapetype = layer.ShapeType()          shapetype = layer.ShapeType()
320          if shapetype == SHAPETYPE_POINT:          if shapetype == SHAPETYPE_POINT:
# Line 220  class BaseRenderer: Line 323  class BaseRenderer:
323              func = self.draw_arc_shape              func = self.draw_arc_shape
324          else:          else:
325              func = self.draw_polygon_shape              func = self.draw_polygon_shape
326          return func, layer          return False, func, layer
327    
328      def make_point(self, x, y):      def make_point(self, x, y):
329          """Convert (x, y) to a point object.          """Convert (x, y) to a point object.
# Line 229  class BaseRenderer: Line 332  class BaseRenderer:
332          """          """
333          raise NotImplementedError          raise NotImplementedError
334    
335      def projected_points(self, layer, shapeid):      def projected_points(self, layer, points):
336          """Return the projected coordinates of shape shapeid in layer.          """Return the projected coordinates of the points taken from layer.
337    
338          Read the shape from the layer through its Shape method and          Transform all the points in the list of lists of coordinate
339          transform all the points in the list of lists of coordinate          pairs in points.
         pairs returned by the shape's Points method.  
340    
341          The transformation applies the inverse of the layer's projection          The transformation applies the inverse of the layer's projection
342          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 355  class BaseRenderer:
355              inverse = proj.Inverse              inverse = proj.Inverse
356          else:          else:
357              inverse = None              inverse = None
358          shape = layer.Shape(shapeid)          result = []
         points = []  
359          scale = self.scale          scale = self.scale
360          offx, offy = self.offset          offx, offy = self.offset
361          make_point = self.make_point          make_point = self.make_point
362          for part in shape.Points():  
363              points.append([])          for part in points:
364                result.append([])
365              for x, y in part:              for x, y in part:
366                  if inverse:                  if inverse:
367                      x, y = inverse(x, y)                      x, y = inverse(x, y)
368                  if forward:                  if forward:
369                      x, y = forward(x, y)                      x, y = forward(x, y)
370                  points[-1].append(make_point(x * scale + offx,                  result[-1].append(make_point(x * scale + offx,
371                                               -y * scale + offy))                                               -y * scale + offy))
372          return points          return result
373    
374      def draw_polygon_shape(self, layer, index, pen, brush):      def draw_polygon_shape(self, layer, points, pen, brush):
375          """Draw a polygon shape with the given brush and pen          """Draw a polygon shape from layer with the given brush and pen
376    
377          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
378          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
379            DC's coordinate system are determined with
380          self.projected_points.          self.projected_points.
381    
382            For a description of the algorithm look in wxproj.cpp.
383          """          """
384          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
385    
386          if brush is not self.TRANSPARENT_BRUSH:          if brush is not self.TRANSPARENT_BRUSH:
387              polygon = []              polygon = []
388              for part in points:              for part in points:
389                  polygon.extend(part)                  polygon.extend(part)
390    
391                # missing back vertices for correct filling.
392              insert_index = len(polygon)              insert_index = len(polygon)
393              for part in points[:-1]:              for part in points[:-1]:
394                  polygon.insert(insert_index, part[0])                  polygon.insert(insert_index, part[0])
# Line 298  class BaseRenderer: Line 404  class BaseRenderer:
404              for part in points:              for part in points:
405                  self.dc.DrawLines(part)                  self.dc.DrawLines(part)
406    
407      def draw_arc_shape(self, layer, index, pen, brush):      def draw_arc_shape(self, layer, points, pen, brush):
408          """Draw an arc shape with the given brush and pen          """Draw an arc shape from layer with the given brush and pen
409    
410          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
411          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
412            DC's coordinate system are determined with
413          self.projected_points.          self.projected_points.
414          """          """
415          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
416          self.dc.SetBrush(brush)          self.dc.SetBrush(brush)
417          self.dc.SetPen(pen)          self.dc.SetPen(pen)
418          for part in points:          for part in points:
419              self.dc.DrawLines(part)              self.dc.DrawLines(part)
420    
421      def draw_point_shape(self, layer, index, pen, brush):      def draw_point_shape(self, layer, points, pen, brush, size = 5):
422          """Draw a point shape with the given brush and pen          """Draw a point shape from layer with the given brush and pen
423    
424          The shape is indicated by its id (index) and the layer. The          The shape is given by points argument which is a the return
425          coordinates in the DC's coordinate system are determined with          value of the shape's Points() method. The coordinates in the
426            DC's coordinate system are determined with
427          self.projected_points.          self.projected_points.
428    
429          The point is drawn as a circle centered on the point.          The point is drawn as a circle centered on the point.
430          """          """
431          points = self.projected_points(layer, index)          points = self.projected_points(layer, points)
432          if not points:          if not points:
433              return              return
434    
435          radius = self.resolution * 5          radius = int(round(self.resolution * size))
436          self.dc.SetBrush(brush)          self.dc.SetBrush(brush)
437          self.dc.SetPen(pen)          self.dc.SetPen(pen)
438          for part in points:          for part in points:
# Line 344  class BaseRenderer: Line 452  class BaseRenderer:
452          offx, offy = self.offset          offx, offy = self.offset
453          width, height = self.dc.GetSizeTuple()          width, height = self.dc.GetSizeTuple()
454    
455          in_proj = ""          in_proj  = proj_params_to_str(layer.GetProjection())
456          proj = layer.GetProjection()          out_proj = proj_params_to_str(self.map.GetProjection())
         if proj is not None:  
             for p in proj.GetAllParameters():  
                 in_proj += "+" + p + " "  
457    
458          out_proj = ""          # True  -- warp the image to the size of the whole screen
459          proj = self.map.GetProjection()          # False -- only use the bound box of the layer (currently inaccurate)
460          if proj is not None:          if True:
461              for p in proj.GetAllParameters():          #if False:
462                  out_proj += "+" + p + " "              pmin = [0,height]
463                pmax = [width, 0]
464            else:
465                bb = layer.LatLongBoundingBox()
466                bb = [[[bb[0], bb[1]], [bb[2], bb[3]],],]
467                pmin, pmax = self.projected_points(layer, bb)[0]
468    
469            #print bb
470            #print pmin, pmax
471    
472            fmin = [max(0, min(pmin[0], pmax[0])) - offx,
473                    offy - min(height, max(pmin[1], pmax[1]))]
474    
475            fmax = [min(width, max(pmin[0], pmax[0])) - offx,
476                    offy - max(0, min(pmin[1], pmax[1]))]
477    
478            xmin = fmin[0]/self.scale
479            ymin = fmin[1]/self.scale
480            xmax = fmax[0]/self.scale
481            ymax = fmax[1]/self.scale
482    
483          xmin = (0 - offx) / self.scale          width  = int(min(width,  round(fmax[0] - fmin[0] + 1)))
484          ymin = (offy - height) / self.scale          height = int(min(height, round(fmax[1] - fmin[1] + 1)))
         xmax = (width - offx) / self.scale  
         ymax = (offy - 0) / self.scale  
485    
486          try:          try:
487              data = ProjectRasterFile(layer.GetImageFilename(),              options = 0
488                                       in_proj, out_proj,              if layer.UseMask(): options = options | 1
489                                       (xmin, ymin, xmax, ymax), "",  
490                                       (width, height))              project_params = (layer.GetImageFilename(), in_proj, out_proj,
491          except (IOError, AttributeError, ValueError):                                (xmin, ymin, xmax, ymax), "", (width, height),
492                                  options)
493    
494                data = (width, height, apply(ProjectRasterFile, project_params))
495    
496            except (MemoryError, IOError, AttributeError, ValueError):
497              # Why does this catch AttributeError and ValueError?              # Why does this catch AttributeError and ValueError?
498              # FIXME: The exception should be communicated to the user              # FIXME: The exception should be communicated to the user
499              # better.              # better.
500              traceback.print_exc()              traceback.print_exc()
501          else:          else:
502              self.draw_raster_data(data)              self.draw_raster_data(fmin[0]+offx, offy-fmax[1], data, "RAW")
503                data = None
     def draw_raster_data(self, data):  
         """Draw the raster image in data onto the DC  
   
         The raster image data is a string holding the data in BMP  
         format. The data is exactly the size of the dc and covers it  
         completely.  
504    
505          This method has to be implemented by derived classes.      def draw_raster_data(self, x, y, data, format="BMP"):
506            """Draw the raster image in data onto the DC with the top
507            left corner at (x,y)
508    
509            The raster image data is a tuple of the form
510                (width, height, (image_data, mask_data))
511            
512            holding the image width, height, image data, and mask data.
513            mask_data may be None if a mask should not be used. If
514            format is 'RAW' the data will be RGB values and the mask
515            will be in XMB format. Otherwise, both kinds
516            of data are assumed to be in the format specified in format.
517    
518            The format parameter is a string with the name of the format.
519            The following format names should be used:
520    
521              'RAW'  -- an array of RGB values (len=3*width*height)
522              'BMP'  -- Windows Bitmap
523              'JPEG' -- JPEG Image
524    
525            The default format is 'BMP'.
526    
527            This method has to be implemented by derived classes. The
528            implementation in the derived class should try to support at
529            least the formats specified above and may support more.
530          """          """
531          raise NotImplementedError          raise NotImplementedError
532    
# Line 411  class BaseRenderer: Line 556  class BaseRenderer:
556              text = label.text              text = label.text
557              if forward:              if forward:
558                  x, y = forward(x, y)                  x, y = forward(x, y)
559              x = x * scale + offx              x = int(round(x * scale + offx))
560              y = -y * scale + offy              y = int(round(-y * scale + offy))
561              width, height = self.dc.GetTextExtent(text)              width, height = self.dc.GetTextExtent(text)
562              if label.halign == ALIGN_LEFT:              if label.halign == ALIGN_LEFT:
563                  # nothing to be done                  # nothing to be done

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26