/[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 1554 - (hide annotations)
Wed Aug 6 17:24:30 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 15144 byte(s)
New file with the basic rendering
logic. The code in this file is completely independend of wx.
(BaseRenderer): Class with the basic rendering logic

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