/[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 1593 - (hide 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 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 bh 1593 as indicated by self.layer_shapes() and draw them, using
144     low-level renderers returned by self.low_level_renderer().
145 bh 1554 """
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 bh 1593 for shape in self.layer_shapes(layer):
167 bh 1554
168     if field is None:
169     group = defaultGroup
170     else:
171 bh 1593 record = table.ReadRowAsDict(shape.ShapeID())
172 bh 1554 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 bh 1591 if useraw:
188     data = shape.RawData()
189     else:
190     data = shape.Points()
191     draw_func(draw_func_param, data, pen, brush)
192 bh 1554
193 bh 1593 def layer_shapes(self, layer):
194     """Return an iterable over the shapes to be drawn from the given layer.
195 bh 1554
196     The default implementation simply returns all ids in the layer.
197     Override in derived classes to be more precise.
198     """
199 bh 1593 return layer.ShapeStore().AllShapes()
200 bh 1554
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 bh 1591 (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 bh 1554
210 bh 1591 func(param, shapedata, pen, brush)
211 bh 1554
212 bh 1591 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 bh 1554
217     The default implementation returns one of
218     self.draw_polygon_shape, self.draw_arc_shape or
219 bh 1591 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 bh 1554 """
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 bh 1591 return False, func, layer
231 bh 1554
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 bh 1591 def projected_points(self, layer, points):
240     """Return the projected coordinates of the points taken from layer.
241 bh 1554
242 bh 1591 Transform all the points in the list of lists of coordinate
243     pairs in points.
244 bh 1554
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 bh 1591 result = []
263 bh 1554 scale = self.scale
264     offx, offy = self.offset
265     make_point = self.make_point
266 bh 1591 for part in points:
267     result.append([])
268 bh 1554 for x, y in part:
269     if inverse:
270     x, y = inverse(x, y)
271     if forward:
272     x, y = forward(x, y)
273 bh 1591 result[-1].append(make_point(x * scale + offx,
274 bh 1554 -y * scale + offy))
275 bh 1591 return result
276 bh 1554
277 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
278     """Draw a polygon shape from layer with the given brush and pen
279 bh 1554
280 bh 1591 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 bh 1554 self.projected_points.
284     """
285 bh 1591 points = self.projected_points(layer, points)
286 bh 1554
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 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
308     """Draw an arc shape from layer with the given brush and pen
309 bh 1554
310 bh 1591 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 bh 1554 self.projected_points.
314     """
315 bh 1591 points = self.projected_points(layer, points)
316 bh 1554 self.dc.SetBrush(brush)
317     self.dc.SetPen(pen)
318     for part in points:
319     self.dc.DrawLines(part)
320    
321 bh 1591 def draw_point_shape(self, layer, points, pen, brush):
322     """Draw a point shape from layer with the given brush and pen
323 bh 1554
324 bh 1591 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 bh 1554 self.projected_points.
328    
329     The point is drawn as a circle centered on the point.
330     """
331 bh 1591 points = self.projected_points(layer, points)
332 bh 1554 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