/[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 2377 - (hide annotations)
Sun Oct 3 21:45:48 2004 UTC (20 years, 5 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 20305 byte(s)
(BaseRenderer.draw_shape_layer_incrementally): If the draw function
is for points, call it with the size as additional parameter.
BaseRenderer.draw_point_shape): Added additional, optional parameter for
the size. Compute the radius using the size.

1 bh 2224 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 bh 1554 # 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 bh 1926 import sys
24 bh 1554 import traceback
25    
26     from Thuban.Model.layer import Layer, RasterLayer
27     from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POINT
28     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
29     ALIGN_LEFT, ALIGN_RIGHT
30    
31     import Thuban.Model.resource
32    
33     if Thuban.Model.resource.has_gdal_support():
34     from gdalwarp import ProjectRasterFile
35    
36    
37 bh 1926 #
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 bh 2224 # to be drawn is an instance of layer_class and if so calls
46 bh 1926 # 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 bh 2224 to do the rendering incrementally.
68 bh 1926 """
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 bh 1554 class BaseRenderer:
85    
86     """Basic Renderer Infrastructure for Thuban Maps
87    
88     This class can't be used directly to render because it doesn't know
89     anything about real DCs such as how to create pens or brushes. That
90     functionality has to be provided by derived classes. The reason for
91     this is that it makes the BaseRenderer completely independend of wx
92     and thus it's quite easy to write test cases for it.
93     """
94     # If true the render honors the visibility flag of the layers
95     honor_visibility = 1
96    
97     # Transparent brushes and pens. Derived classes should define these
98     # as appropriate.
99     TRANSPARENT_PEN = None
100     TRANSPARENT_BRUSH = None
101    
102 bh 1866 def __init__(self, dc, map, scale, offset, region = None,
103     resolution = 72.0, honor_visibility = None):
104 bh 1554 """Inititalize the renderer.
105    
106     dc -- the device context to render on.
107    
108     scale, offset -- the scale factor and translation to convert
109     between projected coordinates and the DC coordinates
110    
111 bh 1866 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 bh 1554 resolution -- the assumed resolution of the DC. Used to convert
116     absolute lengths like font sizes to DC coordinates. The
117 bh 1866 default is 72.0. If given, this parameter must be
118     provided as a keyword argument.
119 bh 1554
120     honor_visibility -- boolean. If true, honor the visibility flag
121     of the layers, otherwise draw all layers. If None (the
122 bh 1866 default), use the renderer's default. If given, this
123     parameter must be provided as a keyword argument.
124 bh 1554 """
125     # resolution in pixel/inch
126     self.dc = dc
127 bh 1866 self.map = map
128 bh 1554 self.scale = scale
129     self.offset = offset
130 bh 1866 self.region = region
131 bh 1554 if honor_visibility is not None:
132     self.honor_visibility = honor_visibility
133     # store the resolution in pixel/point because it's more useful
134     # later.
135     self.resolution = resolution / 72.0
136    
137     def tools_for_property(self, prop):
138     """Return a suitable pen and brush for the property
139    
140     This method must be implemented in derived classes. The return
141     value should be a tuple (pen, brush).
142     """
143     raise NotImplementedError
144    
145 bh 1866 def render_map(self):
146     """Render the map onto the DC.
147 bh 1554
148 bh 1866 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 bh 1554 Iterate through all layers and draw them. Layers containing
172     vector data are drawn with the draw_shape_layer method, raster
173     layers are drawn with draw_raster_layer. The label layer is
174     drawn last with draw_label_layer.
175    
176     During execution of this method, the map is bound to self.map so
177     that methods especially in derived classes have access to the
178     map if necessary.
179     """
180     # Whether the raster layer has already been drawn. See below for
181     # the optimization this is used for
182     seenRaster = True
183    
184 bh 1866 #
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 bh 1554
197 bh 1866 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 bh 2278 if isinstance(layer, Layer):
202     if seenRaster:
203     for i in self.draw_shape_layer_incrementally(layer):
204     yield True
205 bh 1866 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 bh 1926 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 bh 1554
223 bh 1866 self.draw_label_layer(self.map.LabelLayer())
224     yield False
225 bh 1554
226 bh 1866 def draw_shape_layer_incrementally(self, layer):
227     """Draw the shape layer layer onto the map incrementally.
228 bh 1554
229 bh 1866 This method is a generator which yields True after every 500
230     shapes.
231 bh 1554 """
232     scale = self.scale
233     offx, offy = self.offset
234    
235     map_proj = self.map.projection
236     layer_proj = layer.projection
237    
238     brush = self.TRANSPARENT_BRUSH
239     pen = self.TRANSPARENT_PEN
240    
241     old_prop = None
242     old_group = None
243     lc = layer.GetClassification()
244     field = layer.GetClassificationColumn()
245     defaultGroup = lc.GetDefaultGroup()
246     table = layer.ShapeStore().Table()
247    
248 bh 1879 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 bh 1554 # Determine which render function to use.
256 bh 1591 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
257 bh 1554
258 bh 1914 #
259 bh 1554 # Iterate through all shapes that have to be drawn.
260 bh 1914 #
261    
262     # Count the shapes drawn so that we can yield every few hundred
263     # shapes
264 bh 1866 count = 0
265 bh 1914
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 bh 1593 for shape in self.layer_shapes(layer):
272 bh 1866 count += 1
273 bh 1554 if field is None:
274     group = defaultGroup
275     else:
276 bh 1918 value = table.ReadValue(shape.ShapeID(), field)
277 bh 1914 group = lc.FindGroup(value)
278 bh 1554
279     if not group.IsVisible():
280     continue
281    
282 bh 1914 try:
283     pen, brush = tool_cache[id(group)]
284     except KeyError:
285     pen, brush = tool_cache[id(group)] \
286     = self.tools_for_property(group.GetProperties())
287 bh 1554
288 bh 1591 if useraw:
289     data = shape.RawData()
290     else:
291     data = shape.Points()
292 jan 2377 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 bh 1866 if count % 500 == 0:
298     yield True
299 bh 1554
300 bh 1593 def layer_shapes(self, layer):
301     """Return an iterable over the shapes to be drawn from the given layer.
302 bh 1554
303     The default implementation simply returns all ids in the layer.
304     Override in derived classes to be more precise.
305     """
306 bh 1593 return layer.ShapeStore().AllShapes()
307 bh 1554
308     def low_level_renderer(self, layer):
309     """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
312 bh 1591 (useraw, func, param) where useraw is a boolean indicating
313     whether the function uses the raw shape data, func is a callable
314     object and param is passed as the first parameter to func. The
315     draw_shape_layer method will call func like this:
316 bh 1554
317 bh 1591 func(param, shapedata, pen, brush)
318 bh 1554
319 bh 1591 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 bh 1554
324     The default implementation returns one of
325     self.draw_polygon_shape, self.draw_arc_shape or
326 bh 1591 self.draw_point_shape as func and layer as param. None of the
327     method use the raw shape data. Derived classes can override this
328     method to return more efficient low level renderers.
329 bh 1554 """
330     shapetype = layer.ShapeType()
331     if shapetype == SHAPETYPE_POINT:
332     func = self.draw_point_shape
333     elif shapetype == SHAPETYPE_ARC:
334     func = self.draw_arc_shape
335     else:
336     func = self.draw_polygon_shape
337 bh 1591 return False, func, layer
338 bh 1554
339     def make_point(self, x, y):
340     """Convert (x, y) to a point object.
341    
342     Derived classes must override this method.
343     """
344     raise NotImplementedError
345    
346 bh 1591 def projected_points(self, layer, points):
347     """Return the projected coordinates of the points taken from layer.
348 bh 1554
349 bh 1591 Transform all the points in the list of lists of coordinate
350     pairs in points.
351 bh 1554
352     The transformation applies the inverse of the layer's projection
353     if any, then the map's projection if any and finally applies
354     self.scale and self.offset.
355    
356     The returned list has the same structure as the one returned the
357     shape's Points method.
358     """
359     proj = self.map.GetProjection()
360     if proj is not None:
361     forward = proj.Forward
362     else:
363     forward = None
364     proj = layer.GetProjection()
365     if proj is not None:
366     inverse = proj.Inverse
367     else:
368     inverse = None
369 bh 1591 result = []
370 bh 1554 scale = self.scale
371     offx, offy = self.offset
372     make_point = self.make_point
373 bh 1591 for part in points:
374     result.append([])
375 bh 1554 for x, y in part:
376     if inverse:
377     x, y = inverse(x, y)
378     if forward:
379     x, y = forward(x, y)
380 bh 1591 result[-1].append(make_point(x * scale + offx,
381 bh 1554 -y * scale + offy))
382 bh 1591 return result
383 bh 1554
384 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
385     """Draw a polygon shape from layer with the given brush and pen
386 bh 1554
387 bh 1591 The shape is given by points argument which is a the return
388     value of the shape's Points() method. The coordinates in the
389     DC's coordinate system are determined with
390 bh 1554 self.projected_points.
391     """
392 bh 1591 points = self.projected_points(layer, points)
393 bh 1554
394     if brush is not self.TRANSPARENT_BRUSH:
395     polygon = []
396     for part in points:
397     polygon.extend(part)
398    
399     insert_index = len(polygon)
400     for part in points[:-1]:
401     polygon.insert(insert_index, part[0])
402    
403     self.dc.SetBrush(brush)
404     self.dc.SetPen(self.TRANSPARENT_PEN)
405     self.dc.DrawPolygon(polygon)
406    
407     if pen is not self.TRANSPARENT_PEN:
408     # At last draw the boundarys of the simple polygons
409     self.dc.SetBrush(self.TRANSPARENT_BRUSH)
410     self.dc.SetPen(pen)
411     for part in points:
412     self.dc.DrawLines(part)
413    
414 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
415     """Draw an arc shape from layer with the given brush and pen
416 bh 1554
417 bh 1591 The shape is given by points argument which is a the return
418     value of the shape's Points() method. The coordinates in the
419     DC's coordinate system are determined with
420 bh 1554 self.projected_points.
421     """
422 bh 1591 points = self.projected_points(layer, points)
423 bh 1554 self.dc.SetBrush(brush)
424     self.dc.SetPen(pen)
425     for part in points:
426     self.dc.DrawLines(part)
427    
428 jan 2377 def draw_point_shape(self, layer, points, pen, brush, size = 5):
429 bh 1591 """Draw a point shape from layer with the given brush and pen
430 bh 1554
431 bh 1591 The shape is given by points argument which is a the return
432     value of the shape's Points() method. The coordinates in the
433     DC's coordinate system are determined with
434 bh 1554 self.projected_points.
435    
436     The point is drawn as a circle centered on the point.
437     """
438 bh 1591 points = self.projected_points(layer, points)
439 bh 1554 if not points:
440     return
441    
442 jan 2377 radius = int(round(self.resolution * size))
443 bh 1554 self.dc.SetBrush(brush)
444     self.dc.SetPen(pen)
445     for part in points:
446     for p in part:
447     self.dc.DrawEllipse(p.x - radius, p.y - radius,
448     2 * radius, 2 * radius)
449    
450     def draw_raster_layer(self, layer):
451     """Draw the raster layer
452    
453     This implementation does the projection and scaling of the data
454     as required by the layer's and map's projections and the scale
455     and offset of the renderer and then hands the transformed data
456     to self.draw_raster_data() which has to be implemented in
457     derived classes.
458     """
459     offx, offy = self.offset
460     width, height = self.dc.GetSizeTuple()
461    
462     in_proj = ""
463     proj = layer.GetProjection()
464     if proj is not None:
465     for p in proj.GetAllParameters():
466     in_proj += "+" + p + " "
467    
468     out_proj = ""
469     proj = self.map.GetProjection()
470     if proj is not None:
471     for p in proj.GetAllParameters():
472     out_proj += "+" + p + " "
473    
474     xmin = (0 - offx) / self.scale
475     ymin = (offy - height) / self.scale
476     xmax = (width - offx) / self.scale
477     ymax = (offy - 0) / self.scale
478    
479     try:
480     data = ProjectRasterFile(layer.GetImageFilename(),
481     in_proj, out_proj,
482     (xmin, ymin, xmax, ymax), "",
483     (width, height))
484     except (IOError, AttributeError, ValueError):
485     # Why does this catch AttributeError and ValueError?
486     # FIXME: The exception should be communicated to the user
487     # better.
488     traceback.print_exc()
489     else:
490 bh 1926 self.draw_raster_data(data, "BMP")
491 bh 1554
492 bh 1926 def draw_raster_data(self, data, format="BMP"):
493 bh 1554 """Draw the raster image in data onto the DC
494    
495 bh 1926 The raster image data is a string holding the data in the format
496     indicated by the format parameter. The image is assumed to be
497     exactly the size of the dc and to cover it completely.
498 bh 1554
499 bh 1926 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. The
508     implementation in the derived class should try to support at
509     least the formats specified above and may support more.
510 bh 1554 """
511     raise NotImplementedError
512    
513     def label_font(self):
514     """Return the font object for the label layer"""
515     raise NotImplementedError
516    
517     def draw_label_layer(self, layer):
518     """Draw the label layer
519    
520     All labels are draw in the font returned by self.label_font().
521     """
522     scale = self.scale
523     offx, offy = self.offset
524    
525     self.dc.SetFont(self.label_font())
526    
527     map_proj = self.map.projection
528     if map_proj is not None:
529     forward = map_proj.Forward
530     else:
531     forward = None
532    
533     for label in layer.Labels():
534     x = label.x
535     y = label.y
536     text = label.text
537     if forward:
538     x, y = forward(x, y)
539 bh 1935 x = int(round(x * scale + offx))
540     y = int(round(-y * scale + offy))
541 bh 1554 width, height = self.dc.GetTextExtent(text)
542     if label.halign == ALIGN_LEFT:
543     # nothing to be done
544     pass
545     elif label.halign == ALIGN_RIGHT:
546     x = x - width
547     elif label.halign == ALIGN_CENTER:
548     x = x - width/2
549     if label.valign == ALIGN_TOP:
550     # nothing to be done
551     pass
552     elif label.valign == ALIGN_BOTTOM:
553     y = y - height
554     elif label.valign == ALIGN_CENTER:
555     y = y - height/2
556     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