/[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 1591 - (hide annotations)
Fri Aug 15 14:00:53 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 15632 byte(s)
Make the renderers deal correctly with raw vs. python level
representation of shape geometries

* Thuban/UI/baserenderer.py (BaseRenderer.low_level_renderer):
Return a flag useraw in addition to the callable and the parameter
to indicate whether the callable can deal with the raw shape data
or uses the higher level python lists of coordinates. The callable
now should accept either the raw data or the return value of the
shape's Points() method.
(BaseRenderer.draw_shape_layer): Adapt to the low_level_renderer
change
(BaseRenderer.projected_points): Instead of the shape id use the
points list as parameter.
(BaseRenderer.draw_polygon_shape, BaseRenderer.draw_arc_shape)
(BaseRenderer.draw_point_shape): Adapt to projected_points()
change and accept the points list as parameter instead of the
shape id.

* Thuban/UI/renderer.py (MapRenderer.low_level_renderer): Return
the useraw flag as required by the BaseRenderer
(ScreenRenderer.draw_shape_layer): Adapt to low-level renderer
changes.

* test/test_baserenderer.py
(TestBaseRenderer.test_point_with_classification): New test for
rendering a map with classifications.

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 bh 1591 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
164 bh 1554
165     # Iterate through all shapes that have to be drawn.
166     for i in self.layer_ids(layer):
167 bh 1591 shape = layer.Shape(i)
168 bh 1554
169     if field is None:
170     group = defaultGroup
171     else:
172     record = table.ReadRowAsDict(i)
173     assert record is not None
174     group = lc.FindGroup(record[field])
175    
176     if not group.IsVisible():
177     continue
178    
179     # don't recreate new objects if they are the same as before
180     if group is not old_group:
181     old_group = group
182    
183     prop = group.GetProperties()
184    
185     if prop != old_prop:
186     pen, brush = self.tools_for_property(prop)
187    
188 bh 1591 if useraw:
189     data = shape.RawData()
190     else:
191     data = shape.Points()
192     draw_func(draw_func_param, data, pen, brush)
193 bh 1554
194     def layer_ids(self, layer):
195     """Return the shape ids of the given layer that have to be drawn.
196    
197     The default implementation simply returns all ids in the layer.
198     Override in derived classes to be more precise.
199     """
200     return range(layer.NumShapes())
201    
202     def low_level_renderer(self, layer):
203     """Return the low-level renderer for the layer for draw_shape_layer
204    
205     The low level renderer to be returned by this method is a tuple
206 bh 1591 (useraw, func, param) where useraw is a boolean indicating
207     whether the function uses the raw shape data, func is a callable
208     object and param is passed as the first parameter to func. The
209     draw_shape_layer method will call func like this:
210 bh 1554
211 bh 1591 func(param, shapedata, pen, brush)
212 bh 1554
213 bh 1591 where shapedata is the return value of the RawData method of the
214     shape object if useraw is true or the return value of the Points
215     method if it's false. pen and brush are the pen and brush to use
216     to draw the shape on the dc.
217 bh 1554
218     The default implementation returns one of
219     self.draw_polygon_shape, self.draw_arc_shape or
220 bh 1591 self.draw_point_shape as func and layer as param. None of the
221     method use the raw shape data. Derived classes can override this
222     method to return more efficient low level renderers.
223 bh 1554 """
224     shapetype = layer.ShapeType()
225     if shapetype == SHAPETYPE_POINT:
226     func = self.draw_point_shape
227     elif shapetype == SHAPETYPE_ARC:
228     func = self.draw_arc_shape
229     else:
230     func = self.draw_polygon_shape
231 bh 1591 return False, func, layer
232 bh 1554
233     def make_point(self, x, y):
234     """Convert (x, y) to a point object.
235    
236     Derived classes must override this method.
237     """
238     raise NotImplementedError
239    
240 bh 1591 def projected_points(self, layer, points):
241     """Return the projected coordinates of the points taken from layer.
242 bh 1554
243 bh 1591 Transform all the points in the list of lists of coordinate
244     pairs in points.
245 bh 1554
246     The transformation applies the inverse of the layer's projection
247     if any, then the map's projection if any and finally applies
248     self.scale and self.offset.
249    
250     The returned list has the same structure as the one returned the
251     shape's Points method.
252     """
253     proj = self.map.GetProjection()
254     if proj is not None:
255     forward = proj.Forward
256     else:
257     forward = None
258     proj = layer.GetProjection()
259     if proj is not None:
260     inverse = proj.Inverse
261     else:
262     inverse = None
263 bh 1591 result = []
264 bh 1554 scale = self.scale
265     offx, offy = self.offset
266     make_point = self.make_point
267 bh 1591 for part in points:
268     result.append([])
269 bh 1554 for x, y in part:
270     if inverse:
271     x, y = inverse(x, y)
272     if forward:
273     x, y = forward(x, y)
274 bh 1591 result[-1].append(make_point(x * scale + offx,
275 bh 1554 -y * scale + offy))
276 bh 1591 return result
277 bh 1554
278 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
279     """Draw a polygon shape from layer with the given brush and pen
280 bh 1554
281 bh 1591 The shape is given by points argument which is a the return
282     value of the shape's Points() method. The coordinates in the
283     DC's coordinate system are determined with
284 bh 1554 self.projected_points.
285     """
286 bh 1591 points = self.projected_points(layer, points)
287 bh 1554
288     if brush is not self.TRANSPARENT_BRUSH:
289     polygon = []
290     for part in points:
291     polygon.extend(part)
292    
293     insert_index = len(polygon)
294     for part in points[:-1]:
295     polygon.insert(insert_index, part[0])
296    
297     self.dc.SetBrush(brush)
298     self.dc.SetPen(self.TRANSPARENT_PEN)
299     self.dc.DrawPolygon(polygon)
300    
301     if pen is not self.TRANSPARENT_PEN:
302     # At last draw the boundarys of the simple polygons
303     self.dc.SetBrush(self.TRANSPARENT_BRUSH)
304     self.dc.SetPen(pen)
305     for part in points:
306     self.dc.DrawLines(part)
307    
308 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
309     """Draw an arc shape from layer with the given brush and pen
310 bh 1554
311 bh 1591 The shape is given by points argument which is a the return
312     value of the shape's Points() method. The coordinates in the
313     DC's coordinate system are determined with
314 bh 1554 self.projected_points.
315     """
316 bh 1591 points = self.projected_points(layer, points)
317 bh 1554 self.dc.SetBrush(brush)
318     self.dc.SetPen(pen)
319     for part in points:
320     self.dc.DrawLines(part)
321    
322 bh 1591 def draw_point_shape(self, layer, points, pen, brush):
323     """Draw a point shape from layer with the given brush and pen
324 bh 1554
325 bh 1591 The shape is given by points argument which is a the return
326     value of the shape's Points() method. The coordinates in the
327     DC's coordinate system are determined with
328 bh 1554 self.projected_points.
329    
330     The point is drawn as a circle centered on the point.
331     """
332 bh 1591 points = self.projected_points(layer, points)
333 bh 1554 if not points:
334     return
335    
336     radius = self.resolution * 5
337     self.dc.SetBrush(brush)
338     self.dc.SetPen(pen)
339     for part in points:
340     for p in part:
341     self.dc.DrawEllipse(p.x - radius, p.y - radius,
342     2 * radius, 2 * radius)
343    
344     def draw_raster_layer(self, layer):
345     """Draw the raster layer
346    
347     This implementation does the projection and scaling of the data
348     as required by the layer's and map's projections and the scale
349     and offset of the renderer and then hands the transformed data
350     to self.draw_raster_data() which has to be implemented in
351     derived classes.
352     """
353     offx, offy = self.offset
354     width, height = self.dc.GetSizeTuple()
355    
356     in_proj = ""
357     proj = layer.GetProjection()
358     if proj is not None:
359     for p in proj.GetAllParameters():
360     in_proj += "+" + p + " "
361    
362     out_proj = ""
363     proj = self.map.GetProjection()
364     if proj is not None:
365     for p in proj.GetAllParameters():
366     out_proj += "+" + p + " "
367    
368     xmin = (0 - offx) / self.scale
369     ymin = (offy - height) / self.scale
370     xmax = (width - offx) / self.scale
371     ymax = (offy - 0) / self.scale
372    
373     try:
374     data = ProjectRasterFile(layer.GetImageFilename(),
375     in_proj, out_proj,
376     (xmin, ymin, xmax, ymax), "",
377     (width, height))
378     except (IOError, AttributeError, ValueError):
379     # Why does this catch AttributeError and ValueError?
380     # FIXME: The exception should be communicated to the user
381     # better.
382     traceback.print_exc()
383     else:
384     self.draw_raster_data(data)
385    
386     def draw_raster_data(self, data):
387     """Draw the raster image in data onto the DC
388    
389     The raster image data is a string holding the data in BMP
390     format. The data is exactly the size of the dc and covers it
391     completely.
392    
393     This method has to be implemented by derived classes.
394     """
395     raise NotImplementedError
396    
397     def label_font(self):
398     """Return the font object for the label layer"""
399     raise NotImplementedError
400    
401     def draw_label_layer(self, layer):
402     """Draw the label layer
403    
404     All labels are draw in the font returned by self.label_font().
405     """
406     scale = self.scale
407     offx, offy = self.offset
408    
409     self.dc.SetFont(self.label_font())
410    
411     map_proj = self.map.projection
412     if map_proj is not None:
413     forward = map_proj.Forward
414     else:
415     forward = None
416    
417     for label in layer.Labels():
418     x = label.x
419     y = label.y
420     text = label.text
421     if forward:
422     x, y = forward(x, y)
423     x = x * scale + offx
424     y = -y * scale + offy
425     width, height = self.dc.GetTextExtent(text)
426     if label.halign == ALIGN_LEFT:
427     # nothing to be done
428     pass
429     elif label.halign == ALIGN_RIGHT:
430     x = x - width
431     elif label.halign == ALIGN_CENTER:
432     x = x - width/2
433     if label.valign == ALIGN_TOP:
434     # nothing to be done
435     pass
436     elif label.valign == ALIGN_BOTTOM:
437     y = y - height
438     elif label.valign == ALIGN_CENTER:
439     y = y - height/2
440     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