/[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 2224 - (hide annotations)
Fri May 28 17:29:30 2004 UTC (20 years, 9 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 20061 byte(s)
Fix some typos.

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     if isinstance(layer, Layer) and seenRaster:
202     for i in self.draw_shape_layer_incrementally(layer):
203     yield True
204     elif isinstance(layer, RasterLayer) \
205     and Thuban.Model.resource.has_gdal_support():
206     self.draw_raster_layer(layer)
207     seenRaster = True
208     yield True
209 bh 1926 else:
210     # look it up in the renderer extensions
211     for cls, func in _renderer_extensions:
212     if isinstance(layer, cls):
213     for i in func(self, layer):
214     yield True
215     break
216     else:
217     # No renderer found. Print a message about it
218     print >>sys.stderr, ("Drawing layer %r not supported"
219     % layer)
220     yield True
221 bh 1554
222 bh 1866 self.draw_label_layer(self.map.LabelLayer())
223     yield False
224 bh 1554
225 bh 1866 def draw_shape_layer_incrementally(self, layer):
226     """Draw the shape layer layer onto the map incrementally.
227 bh 1554
228 bh 1866 This method is a generator which yields True after every 500
229     shapes.
230 bh 1554 """
231     scale = self.scale
232     offx, offy = self.offset
233    
234     map_proj = self.map.projection
235     layer_proj = layer.projection
236    
237     brush = self.TRANSPARENT_BRUSH
238     pen = self.TRANSPARENT_PEN
239    
240     old_prop = None
241     old_group = None
242     lc = layer.GetClassification()
243     field = layer.GetClassificationColumn()
244     defaultGroup = lc.GetDefaultGroup()
245     table = layer.ShapeStore().Table()
246    
247 bh 1879 if lc.GetNumGroups() == 0:
248     # There's only the default group, so we can pretend that
249     # there is no field to classifiy on which makes things
250     # faster since we don't need the attribute information at
251     # all.
252     field = None
253    
254 bh 1554 # Determine which render function to use.
255 bh 1591 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
256 bh 1554
257 bh 1914 #
258 bh 1554 # Iterate through all shapes that have to be drawn.
259 bh 1914 #
260    
261     # Count the shapes drawn so that we can yield every few hundred
262     # shapes
263 bh 1866 count = 0
264 bh 1914
265     # Cache the tools (pens and brushes) for the classification
266     # groups. This is a mapping from the group's ids to the a tuple
267     # (pen, brush)
268     tool_cache = {}
269    
270 bh 1593 for shape in self.layer_shapes(layer):
271 bh 1866 count += 1
272 bh 1554 if field is None:
273     group = defaultGroup
274     else:
275 bh 1918 value = table.ReadValue(shape.ShapeID(), field)
276 bh 1914 group = lc.FindGroup(value)
277 bh 1554
278     if not group.IsVisible():
279     continue
280    
281 bh 1914 try:
282     pen, brush = tool_cache[id(group)]
283     except KeyError:
284     pen, brush = tool_cache[id(group)] \
285     = self.tools_for_property(group.GetProperties())
286 bh 1554
287 bh 1591 if useraw:
288     data = shape.RawData()
289     else:
290     data = shape.Points()
291     draw_func(draw_func_param, data, pen, brush)
292 bh 1866 if count % 500 == 0:
293     yield True
294 bh 1554
295 bh 1593 def layer_shapes(self, layer):
296     """Return an iterable over the shapes to be drawn from the given layer.
297 bh 1554
298     The default implementation simply returns all ids in the layer.
299     Override in derived classes to be more precise.
300     """
301 bh 1593 return layer.ShapeStore().AllShapes()
302 bh 1554
303     def low_level_renderer(self, layer):
304     """Return the low-level renderer for the layer for draw_shape_layer
305    
306     The low level renderer to be returned by this method is a tuple
307 bh 1591 (useraw, func, param) where useraw is a boolean indicating
308     whether the function uses the raw shape data, func is a callable
309     object and param is passed as the first parameter to func. The
310     draw_shape_layer method will call func like this:
311 bh 1554
312 bh 1591 func(param, shapedata, pen, brush)
313 bh 1554
314 bh 1591 where shapedata is the return value of the RawData method of the
315     shape object if useraw is true or the return value of the Points
316     method if it's false. pen and brush are the pen and brush to use
317     to draw the shape on the dc.
318 bh 1554
319     The default implementation returns one of
320     self.draw_polygon_shape, self.draw_arc_shape or
321 bh 1591 self.draw_point_shape as func and layer as param. None of the
322     method use the raw shape data. Derived classes can override this
323     method to return more efficient low level renderers.
324 bh 1554 """
325     shapetype = layer.ShapeType()
326     if shapetype == SHAPETYPE_POINT:
327     func = self.draw_point_shape
328     elif shapetype == SHAPETYPE_ARC:
329     func = self.draw_arc_shape
330     else:
331     func = self.draw_polygon_shape
332 bh 1591 return False, func, layer
333 bh 1554
334     def make_point(self, x, y):
335     """Convert (x, y) to a point object.
336    
337     Derived classes must override this method.
338     """
339     raise NotImplementedError
340    
341 bh 1591 def projected_points(self, layer, points):
342     """Return the projected coordinates of the points taken from layer.
343 bh 1554
344 bh 1591 Transform all the points in the list of lists of coordinate
345     pairs in points.
346 bh 1554
347     The transformation applies the inverse of the layer's projection
348     if any, then the map's projection if any and finally applies
349     self.scale and self.offset.
350    
351     The returned list has the same structure as the one returned the
352     shape's Points method.
353     """
354     proj = self.map.GetProjection()
355     if proj is not None:
356     forward = proj.Forward
357     else:
358     forward = None
359     proj = layer.GetProjection()
360     if proj is not None:
361     inverse = proj.Inverse
362     else:
363     inverse = None
364 bh 1591 result = []
365 bh 1554 scale = self.scale
366     offx, offy = self.offset
367     make_point = self.make_point
368 bh 1591 for part in points:
369     result.append([])
370 bh 1554 for x, y in part:
371     if inverse:
372     x, y = inverse(x, y)
373     if forward:
374     x, y = forward(x, y)
375 bh 1591 result[-1].append(make_point(x * scale + offx,
376 bh 1554 -y * scale + offy))
377 bh 1591 return result
378 bh 1554
379 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
380     """Draw a polygon shape from layer with the given brush and pen
381 bh 1554
382 bh 1591 The shape is given by points argument which is a the return
383     value of the shape's Points() method. The coordinates in the
384     DC's coordinate system are determined with
385 bh 1554 self.projected_points.
386     """
387 bh 1591 points = self.projected_points(layer, points)
388 bh 1554
389     if brush is not self.TRANSPARENT_BRUSH:
390     polygon = []
391     for part in points:
392     polygon.extend(part)
393    
394     insert_index = len(polygon)
395     for part in points[:-1]:
396     polygon.insert(insert_index, part[0])
397    
398     self.dc.SetBrush(brush)
399     self.dc.SetPen(self.TRANSPARENT_PEN)
400     self.dc.DrawPolygon(polygon)
401    
402     if pen is not self.TRANSPARENT_PEN:
403     # At last draw the boundarys of the simple polygons
404     self.dc.SetBrush(self.TRANSPARENT_BRUSH)
405     self.dc.SetPen(pen)
406     for part in points:
407     self.dc.DrawLines(part)
408    
409 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
410     """Draw an arc shape from layer with the given brush and pen
411 bh 1554
412 bh 1591 The shape is given by points argument which is a the return
413     value of the shape's Points() method. The coordinates in the
414     DC's coordinate system are determined with
415 bh 1554 self.projected_points.
416     """
417 bh 1591 points = self.projected_points(layer, points)
418 bh 1554 self.dc.SetBrush(brush)
419     self.dc.SetPen(pen)
420     for part in points:
421     self.dc.DrawLines(part)
422    
423 bh 1591 def draw_point_shape(self, layer, points, pen, brush):
424     """Draw a point shape from layer with the given brush and pen
425 bh 1554
426 bh 1591 The shape is given by points argument which is a the return
427     value of the shape's Points() method. The coordinates in the
428     DC's coordinate system are determined with
429 bh 1554 self.projected_points.
430    
431     The point is drawn as a circle centered on the point.
432     """
433 bh 1591 points = self.projected_points(layer, points)
434 bh 1554 if not points:
435     return
436    
437 bh 1935 radius = int(round(self.resolution * 5))
438 bh 1554 self.dc.SetBrush(brush)
439     self.dc.SetPen(pen)
440     for part in points:
441     for p in part:
442     self.dc.DrawEllipse(p.x - radius, p.y - radius,
443     2 * radius, 2 * radius)
444    
445     def draw_raster_layer(self, layer):
446     """Draw the raster layer
447    
448     This implementation does the projection and scaling of the data
449     as required by the layer's and map's projections and the scale
450     and offset of the renderer and then hands the transformed data
451     to self.draw_raster_data() which has to be implemented in
452     derived classes.
453     """
454     offx, offy = self.offset
455     width, height = self.dc.GetSizeTuple()
456    
457     in_proj = ""
458     proj = layer.GetProjection()
459     if proj is not None:
460     for p in proj.GetAllParameters():
461     in_proj += "+" + p + " "
462    
463     out_proj = ""
464     proj = self.map.GetProjection()
465     if proj is not None:
466     for p in proj.GetAllParameters():
467     out_proj += "+" + p + " "
468    
469     xmin = (0 - offx) / self.scale
470     ymin = (offy - height) / self.scale
471     xmax = (width - offx) / self.scale
472     ymax = (offy - 0) / self.scale
473    
474     try:
475     data = ProjectRasterFile(layer.GetImageFilename(),
476     in_proj, out_proj,
477     (xmin, ymin, xmax, ymax), "",
478     (width, height))
479     except (IOError, AttributeError, ValueError):
480     # Why does this catch AttributeError and ValueError?
481     # FIXME: The exception should be communicated to the user
482     # better.
483     traceback.print_exc()
484     else:
485 bh 1926 self.draw_raster_data(data, "BMP")
486 bh 1554
487 bh 1926 def draw_raster_data(self, data, format="BMP"):
488 bh 1554 """Draw the raster image in data onto the DC
489    
490 bh 1926 The raster image data is a string holding the data in the format
491     indicated by the format parameter. The image is assumed to be
492     exactly the size of the dc and to cover it completely.
493 bh 1554
494 bh 1926 The format parameter is a string with the name of the format.
495     The following format names should be used:
496    
497     'BMP' -- Windows Bitmap
498     'JPEG' -- Jpeg
499    
500     The default format is 'bmp'.
501    
502     This method has to be implemented by derived classes. The
503     implementation in the derived class should try to support at
504     least the formats specified above and may support more.
505 bh 1554 """
506     raise NotImplementedError
507    
508     def label_font(self):
509     """Return the font object for the label layer"""
510     raise NotImplementedError
511    
512     def draw_label_layer(self, layer):
513     """Draw the label layer
514    
515     All labels are draw in the font returned by self.label_font().
516     """
517     scale = self.scale
518     offx, offy = self.offset
519    
520     self.dc.SetFont(self.label_font())
521    
522     map_proj = self.map.projection
523     if map_proj is not None:
524     forward = map_proj.Forward
525     else:
526     forward = None
527    
528     for label in layer.Labels():
529     x = label.x
530     y = label.y
531     text = label.text
532     if forward:
533     x, y = forward(x, y)
534 bh 1935 x = int(round(x * scale + offx))
535     y = int(round(-y * scale + offy))
536 bh 1554 width, height = self.dc.GetTextExtent(text)
537     if label.halign == ALIGN_LEFT:
538     # nothing to be done
539     pass
540     elif label.halign == ALIGN_RIGHT:
541     x = x - width
542     elif label.halign == ALIGN_CENTER:
543     x = x - width/2
544     if label.valign == ALIGN_TOP:
545     # nothing to be done
546     pass
547     elif label.valign == ALIGN_BOTTOM:
548     y = y - height
549     elif label.valign == ALIGN_CENTER:
550     y = y - height/2
551     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