/[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 1593 - (show annotations)
Fri Aug 15 14:10:27 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: 15636 byte(s)
Change the way shapes are returned by a shape store. The
ShapesInRegion method returns an iterator over actual shape
objects instead of a list of shape ids.

* Thuban/Model/data.py (ShapefileShape.ShapeID): New. Return shape
id.
(ShapefileStore.ShapesInRegion): Return an iterator over the
shapes which yields shape objects instead of returning a list of
shape ids
(ShapefileStore.AllShapes): New. Return an iterator over all
shapes in the shape store
(DerivedShapeStore.AllShapes): New. Like in ShapefileStore

* Thuban/Model/layer.py (Layer.ShapesInRegion): Update
doc-string.

* Thuban/UI/baserenderer.py
(BaseRenderer.layer_ids, BaseRenderer.layer_shapes): Rename to
layer_shapes and make it return an iterator containg shapes
instead of a list of ids.
(BaseRenderer.draw_shape_layer): Update doc-string; Adapt to
layer_shapes() change

* Thuban/UI/renderer.py (ScreenRenderer.layer_ids)
(ScreenRenderer.layer_shapes): Rename as in BaseRenderer

* Thuban/UI/viewport.py (ViewPort._find_shape_in_layer): Adapt to
changes in the ShapesInRegion return value.
(ViewPort._get_hit_tester): Remove commented out code

* test/mockgeo.py (SimpleShapeStore.ShapesInRegion): Adapt to the
new return value.
(SimpleShapeStore.AllShapes): New. Implement this method too.

* test/test_layer.py (TestLayer.test_arc_layer)
(TestLayer.test_polygon_layer, TestLayer.test_point_layer)
(TestLayer.test_point_layer_with_projection)
(TestLayer.test_derived_store): Adapt to changes in the
ShapesInRegion return value.

* test/test_shapefilestore.py
(TestShapefileStoreArc.test_shapes_in_region)
(TestShapefileStorePolygon.test_shapes_in_region)
(TestShapefileStorePoint.test_shapes_in_region): Adapt to changes
in the ShapesInRegion return value.
(TestShapefileStorePoint.test_all_shapes)
(TestShapefileStoreArc.test_shape_shapeid): New tests for the new
methods

* test/test_derivedshapestore.py
(TestDerivedShapeStore.test_shapes_in_region): Adapt to changes in
the ShapesInRegion return value.
(TestDerivedShapeStore.test_all_shapes)
(TestDerivedShapeStore.test_shape_shapeid): New tests for the new
methods

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