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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1866 - (hide annotations)
Mon Oct 27 13:01:58 2003 UTC (21 years, 4 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 16735 byte(s)
Several rendering changes:

 - Render the selection into a separate bitmap so that only that
   bitmap needs to be redrawn when the selection changes

 - Render incrementally showing previews and allowing interaction
   before rendering is complete

 - Update the renderer interface a bit. Most parameters of
   RenderMap are now parameters of the constructor

* Thuban/UI/baserenderer.py (BaseRenderer.__init__): Add the map
and the update region as parameters. Update the doc-string
(BaseRenderer.render_map_incrementally): New. Generator function
to renders the map incrementally
(BaseRenderer.render_map): Remove the map argument (it's now in
the constructor) and simply iterate over the
render_map_incrementally generator to draw the map.
(BaseRenderer.draw_shape_layer_incrementally)
(BaseRenderer.draw_shape_layer): Renamed to
draw_shape_layer_incrementally and changed into a generator that
yields True every 500 shapes. Used by render_map_incrementally to
render shape layers incrementally

* Thuban/UI/renderer.py (ScreenRenderer.RenderMap): Removed the
map and region parameters which are now in the constructor
(ScreenRenderer.RenderMapIncrementally): New. Public frontend for
the inherited render_map_incrementally.
(BaseRenderer.draw_shape_layer): Removed.
(ScreenRenderer.draw_selection_incrementally): New. The selection
drawing part of the removed draw_shape_layer as a generator
(ScreenRenderer.layer_shapes): Update because of the region
parameter change
(ExportRenderer.__init__): New. Extend the inherited constructor
with the destination region for the drawing
(ExportRenderer.RenderMap): Removed the map and region parameters
which are now in the constructor

* Thuban/UI/view.py (MapCanvas.PreviewBitmap): New. Return a
bitmap suitable for a preview in a tool
(CanvasPanTool.MouseMove): Use the PreviewBitmap method to get the
bitmap
(MapPrintout.draw_on_dc): Adapt to new renderer interface
(MapCanvas.OnPaint): Handle drawing the selection bitmap if it
exists
(MapCanvas.OnIdle): Update the logic to deal with incremental
rendering and the selection bitmap
(MapCanvas._do_redraw): Handle the instantiation of the render
iterator and the redraws during rendering
(MapCanvas._render_iterator): New. Generator to incrementally
redraw both bitmaps
(MapCanvas.Export): Adapt to new renderer interface.
(MapCanvas.full_redraw): Reset the selection bitmap and the
renderer iterator too
(MapCanvas.redraw_selection): New. Force a redraw of the selection
bitmap
(MapCanvas.shape_selected): Only redraw the selection bitmap

1 bh 1554 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2     # Authors:
3     # Bernhard Herzog <[email protected]>
4     # Jonathan Coles <[email protected]>
5     # Frank Koormann <[email protected]>
6     #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     """Basic rendering logic for Thuban maps
11    
12     The code in this module is completely independend of wx so that it can
13     be tested reasonably easily and it could make it easier to write non-wx
14     renderers.
15     """
16    
17 bh 1866 from __future__ import generators
18    
19 bh 1554 __version__ = "$Revision$"
20     # $Source$
21     # $Id$
22    
23     import traceback
24    
25     from Thuban.Model.layer import Layer, RasterLayer
26     from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POINT
27     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
28     ALIGN_LEFT, ALIGN_RIGHT
29    
30     import Thuban.Model.resource
31    
32     if Thuban.Model.resource.has_gdal_support():
33     from gdalwarp import ProjectRasterFile
34    
35    
36     class BaseRenderer:
37    
38     """Basic Renderer Infrastructure for Thuban Maps
39    
40     This class can't be used directly to render because it doesn't know
41     anything about real DCs such as how to create pens or brushes. That
42     functionality has to be provided by derived classes. The reason for
43     this is that it makes the BaseRenderer completely independend of wx
44     and thus it's quite easy to write test cases for it.
45     """
46     # If true the render honors the visibility flag of the layers
47     honor_visibility = 1
48    
49     # Transparent brushes and pens. Derived classes should define these
50     # as appropriate.
51     TRANSPARENT_PEN = None
52     TRANSPARENT_BRUSH = None
53    
54 bh 1866 def __init__(self, dc, map, scale, offset, region = None,
55     resolution = 72.0, honor_visibility = None):
56 bh 1554 """Inititalize the renderer.
57    
58     dc -- the device context to render on.
59    
60     scale, offset -- the scale factor and translation to convert
61     between projected coordinates and the DC coordinates
62    
63 bh 1866 region -- The region to draw as a (x, y, width, height) tuple in
64     the map's coordinate system. Default is None meaning
65     to draw everything.
66    
67 bh 1554 resolution -- the assumed resolution of the DC. Used to convert
68     absolute lengths like font sizes to DC coordinates. The
69 bh 1866 default is 72.0. If given, this parameter must be
70     provided as a keyword argument.
71 bh 1554
72     honor_visibility -- boolean. If true, honor the visibility flag
73     of the layers, otherwise draw all layers. If None (the
74 bh 1866 default), use the renderer's default. If given, this
75     parameter must be provided as a keyword argument.
76 bh 1554 """
77     # resolution in pixel/inch
78     self.dc = dc
79 bh 1866 self.map = map
80 bh 1554 self.scale = scale
81     self.offset = offset
82 bh 1866 self.region = region
83 bh 1554 if honor_visibility is not None:
84     self.honor_visibility = honor_visibility
85     # store the resolution in pixel/point because it's more useful
86     # later.
87     self.resolution = resolution / 72.0
88    
89     def tools_for_property(self, prop):
90     """Return a suitable pen and brush for the property
91    
92     This method must be implemented in derived classes. The return
93     value should be a tuple (pen, brush).
94     """
95     raise NotImplementedError
96    
97 bh 1866 def render_map(self):
98     """Render the map onto the DC.
99 bh 1554
100 bh 1866 Both map and DC are the ones the renderer was instantiated with.
101    
102     This method is just a front-end for render_map_incrementally
103     which does all rendering in one go. It also calls the dc's
104     BeginDrawing and EndDrawing methods before and after calling
105     render_map_incrementally.
106     """
107    
108     self.dc.BeginDrawing()
109     try:
110     for cont in self.render_map_incrementally():
111     pass
112     finally:
113     self.dc.EndDrawing()
114    
115     def render_map_incrementally(self):
116     """Render the map incrementally.
117    
118     Return an iterator whose next method should be called until it
119     returns False. After returning False it will raise StopIteration
120     so that you could also use it in a for loop (implementation
121     detail: this method is implemented as a generator).
122    
123 bh 1554 Iterate through all layers and draw them. Layers containing
124     vector data are drawn with the draw_shape_layer method, raster
125     layers are drawn with draw_raster_layer. The label layer is
126     drawn last with draw_label_layer.
127    
128     During execution of this method, the map is bound to self.map so
129     that methods especially in derived classes have access to the
130     map if necessary.
131     """
132     # Whether the raster layer has already been drawn. See below for
133     # the optimization this is used for
134     seenRaster = True
135    
136 bh 1866 #
137     # This is only a good optimization if there is only one
138     # raster layer and the image covers the entire window (as
139     # it currently does). We note if there is a raster layer
140     # and only begin drawing layers once we have drawn it.
141     # That way we avoid drawing layers that won't be seen.
142     #
143     if Thuban.Model.resource.has_gdal_support():
144     for layer in self.map.Layers():
145     if isinstance(layer, RasterLayer) and layer.Visible():
146     seenRaster = False
147     break
148 bh 1554
149 bh 1866 for layer in self.map.Layers():
150     # if honor_visibility is true, only draw visible layers,
151     # otherwise draw all layers
152     if not self.honor_visibility or layer.Visible():
153     if isinstance(layer, Layer) and seenRaster:
154     for i in self.draw_shape_layer_incrementally(layer):
155     yield True
156     elif isinstance(layer, RasterLayer) \
157     and Thuban.Model.resource.has_gdal_support():
158     self.draw_raster_layer(layer)
159     seenRaster = True
160     yield True
161 bh 1554
162 bh 1866 self.draw_label_layer(self.map.LabelLayer())
163     yield False
164 bh 1554
165 bh 1866 def draw_shape_layer_incrementally(self, layer):
166     """Draw the shape layer layer onto the map incrementally.
167 bh 1554
168 bh 1866 This method is a generator which yields True after every 500
169     shapes.
170 bh 1554 """
171     scale = self.scale
172     offx, offy = self.offset
173    
174     map_proj = self.map.projection
175     layer_proj = layer.projection
176    
177     brush = self.TRANSPARENT_BRUSH
178     pen = self.TRANSPARENT_PEN
179    
180     old_prop = None
181     old_group = None
182     lc = layer.GetClassification()
183     field = layer.GetClassificationColumn()
184     defaultGroup = lc.GetDefaultGroup()
185     table = layer.ShapeStore().Table()
186    
187     # Determine which render function to use.
188 bh 1591 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
189 bh 1554
190     # Iterate through all shapes that have to be drawn.
191 bh 1866 count = 0
192 bh 1593 for shape in self.layer_shapes(layer):
193 bh 1866 count += 1
194 bh 1554 if field is None:
195     group = defaultGroup
196     else:
197 bh 1593 record = table.ReadRowAsDict(shape.ShapeID())
198 bh 1554 assert record is not None
199     group = lc.FindGroup(record[field])
200    
201     if not group.IsVisible():
202     continue
203    
204     # don't recreate new objects if they are the same as before
205     if group is not old_group:
206     old_group = group
207    
208     prop = group.GetProperties()
209    
210     if prop != old_prop:
211     pen, brush = self.tools_for_property(prop)
212    
213 bh 1591 if useraw:
214     data = shape.RawData()
215     else:
216     data = shape.Points()
217     draw_func(draw_func_param, data, pen, brush)
218 bh 1866 if count % 500 == 0:
219     yield True
220 bh 1554
221 bh 1593 def layer_shapes(self, layer):
222     """Return an iterable over the shapes to be drawn from the given layer.
223 bh 1554
224     The default implementation simply returns all ids in the layer.
225     Override in derived classes to be more precise.
226     """
227 bh 1593 return layer.ShapeStore().AllShapes()
228 bh 1554
229     def low_level_renderer(self, layer):
230     """Return the low-level renderer for the layer for draw_shape_layer
231    
232     The low level renderer to be returned by this method is a tuple
233 bh 1591 (useraw, func, param) where useraw is a boolean indicating
234     whether the function uses the raw shape data, func is a callable
235     object and param is passed as the first parameter to func. The
236     draw_shape_layer method will call func like this:
237 bh 1554
238 bh 1591 func(param, shapedata, pen, brush)
239 bh 1554
240 bh 1591 where shapedata is the return value of the RawData method of the
241     shape object if useraw is true or the return value of the Points
242     method if it's false. pen and brush are the pen and brush to use
243     to draw the shape on the dc.
244 bh 1554
245     The default implementation returns one of
246     self.draw_polygon_shape, self.draw_arc_shape or
247 bh 1591 self.draw_point_shape as func and layer as param. None of the
248     method use the raw shape data. Derived classes can override this
249     method to return more efficient low level renderers.
250 bh 1554 """
251     shapetype = layer.ShapeType()
252     if shapetype == SHAPETYPE_POINT:
253     func = self.draw_point_shape
254     elif shapetype == SHAPETYPE_ARC:
255     func = self.draw_arc_shape
256     else:
257     func = self.draw_polygon_shape
258 bh 1591 return False, func, layer
259 bh 1554
260     def make_point(self, x, y):
261     """Convert (x, y) to a point object.
262    
263     Derived classes must override this method.
264     """
265     raise NotImplementedError
266    
267 bh 1591 def projected_points(self, layer, points):
268     """Return the projected coordinates of the points taken from layer.
269 bh 1554
270 bh 1591 Transform all the points in the list of lists of coordinate
271     pairs in points.
272 bh 1554
273     The transformation applies the inverse of the layer's projection
274     if any, then the map's projection if any and finally applies
275     self.scale and self.offset.
276    
277     The returned list has the same structure as the one returned the
278     shape's Points method.
279     """
280     proj = self.map.GetProjection()
281     if proj is not None:
282     forward = proj.Forward
283     else:
284     forward = None
285     proj = layer.GetProjection()
286     if proj is not None:
287     inverse = proj.Inverse
288     else:
289     inverse = None
290 bh 1591 result = []
291 bh 1554 scale = self.scale
292     offx, offy = self.offset
293     make_point = self.make_point
294 bh 1591 for part in points:
295     result.append([])
296 bh 1554 for x, y in part:
297     if inverse:
298     x, y = inverse(x, y)
299     if forward:
300     x, y = forward(x, y)
301 bh 1591 result[-1].append(make_point(x * scale + offx,
302 bh 1554 -y * scale + offy))
303 bh 1591 return result
304 bh 1554
305 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
306     """Draw a polygon shape from layer with the given brush and pen
307 bh 1554
308 bh 1591 The shape is given by points argument which is a the return
309     value of the shape's Points() method. The coordinates in the
310     DC's coordinate system are determined with
311 bh 1554 self.projected_points.
312     """
313 bh 1591 points = self.projected_points(layer, points)
314 bh 1554
315     if brush is not self.TRANSPARENT_BRUSH:
316     polygon = []
317     for part in points:
318     polygon.extend(part)
319    
320     insert_index = len(polygon)
321     for part in points[:-1]:
322     polygon.insert(insert_index, part[0])
323    
324     self.dc.SetBrush(brush)
325     self.dc.SetPen(self.TRANSPARENT_PEN)
326     self.dc.DrawPolygon(polygon)
327    
328     if pen is not self.TRANSPARENT_PEN:
329     # At last draw the boundarys of the simple polygons
330     self.dc.SetBrush(self.TRANSPARENT_BRUSH)
331     self.dc.SetPen(pen)
332     for part in points:
333     self.dc.DrawLines(part)
334    
335 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
336     """Draw an arc shape from layer with the given brush and pen
337 bh 1554
338 bh 1591 The shape is given by points argument which is a the return
339     value of the shape's Points() method. The coordinates in the
340     DC's coordinate system are determined with
341 bh 1554 self.projected_points.
342     """
343 bh 1591 points = self.projected_points(layer, points)
344 bh 1554 self.dc.SetBrush(brush)
345     self.dc.SetPen(pen)
346     for part in points:
347     self.dc.DrawLines(part)
348    
349 bh 1591 def draw_point_shape(self, layer, points, pen, brush):
350     """Draw a point shape from layer with the given brush and pen
351 bh 1554
352 bh 1591 The shape is given by points argument which is a the return
353     value of the shape's Points() method. The coordinates in the
354     DC's coordinate system are determined with
355 bh 1554 self.projected_points.
356    
357     The point is drawn as a circle centered on the point.
358     """
359 bh 1591 points = self.projected_points(layer, points)
360 bh 1554 if not points:
361     return
362    
363     radius = self.resolution * 5
364     self.dc.SetBrush(brush)
365     self.dc.SetPen(pen)
366     for part in points:
367     for p in part:
368     self.dc.DrawEllipse(p.x - radius, p.y - radius,
369     2 * radius, 2 * radius)
370    
371     def draw_raster_layer(self, layer):
372     """Draw the raster layer
373    
374     This implementation does the projection and scaling of the data
375     as required by the layer's and map's projections and the scale
376     and offset of the renderer and then hands the transformed data
377     to self.draw_raster_data() which has to be implemented in
378     derived classes.
379     """
380     offx, offy = self.offset
381     width, height = self.dc.GetSizeTuple()
382    
383     in_proj = ""
384     proj = layer.GetProjection()
385     if proj is not None:
386     for p in proj.GetAllParameters():
387     in_proj += "+" + p + " "
388    
389     out_proj = ""
390     proj = self.map.GetProjection()
391     if proj is not None:
392     for p in proj.GetAllParameters():
393     out_proj += "+" + p + " "
394    
395     xmin = (0 - offx) / self.scale
396     ymin = (offy - height) / self.scale
397     xmax = (width - offx) / self.scale
398     ymax = (offy - 0) / self.scale
399    
400     try:
401     data = ProjectRasterFile(layer.GetImageFilename(),
402     in_proj, out_proj,
403     (xmin, ymin, xmax, ymax), "",
404     (width, height))
405     except (IOError, AttributeError, ValueError):
406     # Why does this catch AttributeError and ValueError?
407     # FIXME: The exception should be communicated to the user
408     # better.
409     traceback.print_exc()
410     else:
411     self.draw_raster_data(data)
412    
413     def draw_raster_data(self, data):
414     """Draw the raster image in data onto the DC
415    
416     The raster image data is a string holding the data in BMP
417     format. The data is exactly the size of the dc and covers it
418     completely.
419    
420     This method has to be implemented by derived classes.
421     """
422     raise NotImplementedError
423    
424     def label_font(self):
425     """Return the font object for the label layer"""
426     raise NotImplementedError
427    
428     def draw_label_layer(self, layer):
429     """Draw the label layer
430    
431     All labels are draw in the font returned by self.label_font().
432     """
433     scale = self.scale
434     offx, offy = self.offset
435    
436     self.dc.SetFont(self.label_font())
437    
438     map_proj = self.map.projection
439     if map_proj is not None:
440     forward = map_proj.Forward
441     else:
442     forward = None
443    
444     for label in layer.Labels():
445     x = label.x
446     y = label.y
447     text = label.text
448     if forward:
449     x, y = forward(x, y)
450     x = x * scale + offx
451     y = -y * scale + offy
452     width, height = self.dc.GetTextExtent(text)
453     if label.halign == ALIGN_LEFT:
454     # nothing to be done
455     pass
456     elif label.halign == ALIGN_RIGHT:
457     x = x - width
458     elif label.halign == ALIGN_CENTER:
459     x = x - width/2
460     if label.valign == ALIGN_TOP:
461     # nothing to be done
462     pass
463     elif label.valign == ALIGN_BOTTOM:
464     y = y - height
465     elif label.valign == ALIGN_CENTER:
466     y = y - height/2
467     self.dc.DrawText(text, x, y)

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26