/[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 1554 - (show 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 # 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