/[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 1591 - (show 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 # 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 useraw, 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 shape = layer.Shape(i)
168
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 if useraw:
189 data = shape.RawData()
190 else:
191 data = shape.Points()
192 draw_func(draw_func_param, data, pen, brush)
193
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 (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
211 func(param, shapedata, pen, brush)
212
213 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
218 The default implementation returns one of
219 self.draw_polygon_shape, self.draw_arc_shape or
220 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 """
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 return False, func, layer
232
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 def projected_points(self, layer, points):
241 """Return the projected coordinates of the points taken from layer.
242
243 Transform all the points in the list of lists of coordinate
244 pairs in points.
245
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 result = []
264 scale = self.scale
265 offx, offy = self.offset
266 make_point = self.make_point
267 for part in points:
268 result.append([])
269 for x, y in part:
270 if inverse:
271 x, y = inverse(x, y)
272 if forward:
273 x, y = forward(x, y)
274 result[-1].append(make_point(x * scale + offx,
275 -y * scale + offy))
276 return result
277
278 def draw_polygon_shape(self, layer, points, pen, brush):
279 """Draw a polygon shape from layer with the given brush and pen
280
281 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 self.projected_points.
285 """
286 points = self.projected_points(layer, points)
287
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 def draw_arc_shape(self, layer, points, pen, brush):
309 """Draw an arc shape from layer with the given brush and pen
310
311 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 self.projected_points.
315 """
316 points = self.projected_points(layer, points)
317 self.dc.SetBrush(brush)
318 self.dc.SetPen(pen)
319 for part in points:
320 self.dc.DrawLines(part)
321
322 def draw_point_shape(self, layer, points, pen, brush):
323 """Draw a point shape from layer with the given brush and pen
324
325 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 self.projected_points.
329
330 The point is drawn as a circle centered on the point.
331 """
332 points = self.projected_points(layer, points)
333 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