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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1866 - (show 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 # 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 from __future__ import generators
18
19 __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 def __init__(self, dc, map, scale, offset, region = None,
55 resolution = 72.0, honor_visibility = None):
56 """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 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 resolution -- the assumed resolution of the DC. Used to convert
68 absolute lengths like font sizes to DC coordinates. The
69 default is 72.0. If given, this parameter must be
70 provided as a keyword argument.
71
72 honor_visibility -- boolean. If true, honor the visibility flag
73 of the layers, otherwise draw all layers. If None (the
74 default), use the renderer's default. If given, this
75 parameter must be provided as a keyword argument.
76 """
77 # resolution in pixel/inch
78 self.dc = dc
79 self.map = map
80 self.scale = scale
81 self.offset = offset
82 self.region = region
83 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 def render_map(self):
98 """Render the map onto the DC.
99
100 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 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 #
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
149 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
162 self.draw_label_layer(self.map.LabelLayer())
163 yield False
164
165 def draw_shape_layer_incrementally(self, layer):
166 """Draw the shape layer layer onto the map incrementally.
167
168 This method is a generator which yields True after every 500
169 shapes.
170 """
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 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
189
190 # Iterate through all shapes that have to be drawn.
191 count = 0
192 for shape in self.layer_shapes(layer):
193 count += 1
194 if field is None:
195 group = defaultGroup
196 else:
197 record = table.ReadRowAsDict(shape.ShapeID())
198 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 if useraw:
214 data = shape.RawData()
215 else:
216 data = shape.Points()
217 draw_func(draw_func_param, data, pen, brush)
218 if count % 500 == 0:
219 yield True
220
221 def layer_shapes(self, layer):
222 """Return an iterable over the shapes to be drawn from the given layer.
223
224 The default implementation simply returns all ids in the layer.
225 Override in derived classes to be more precise.
226 """
227 return layer.ShapeStore().AllShapes()
228
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 (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
238 func(param, shapedata, pen, brush)
239
240 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
245 The default implementation returns one of
246 self.draw_polygon_shape, self.draw_arc_shape or
247 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 """
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 return False, func, layer
259
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 def projected_points(self, layer, points):
268 """Return the projected coordinates of the points taken from layer.
269
270 Transform all the points in the list of lists of coordinate
271 pairs in points.
272
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 result = []
291 scale = self.scale
292 offx, offy = self.offset
293 make_point = self.make_point
294 for part in points:
295 result.append([])
296 for x, y in part:
297 if inverse:
298 x, y = inverse(x, y)
299 if forward:
300 x, y = forward(x, y)
301 result[-1].append(make_point(x * scale + offx,
302 -y * scale + offy))
303 return result
304
305 def draw_polygon_shape(self, layer, points, pen, brush):
306 """Draw a polygon shape from layer with the given brush and pen
307
308 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 self.projected_points.
312 """
313 points = self.projected_points(layer, points)
314
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 def draw_arc_shape(self, layer, points, pen, brush):
336 """Draw an arc shape from layer with the given brush and pen
337
338 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 self.projected_points.
342 """
343 points = self.projected_points(layer, points)
344 self.dc.SetBrush(brush)
345 self.dc.SetPen(pen)
346 for part in points:
347 self.dc.DrawLines(part)
348
349 def draw_point_shape(self, layer, points, pen, brush):
350 """Draw a point shape from layer with the given brush and pen
351
352 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 self.projected_points.
356
357 The point is drawn as a circle centered on the point.
358 """
359 points = self.projected_points(layer, points)
360 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