/[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 2571 - (hide annotations)
Fri Feb 18 14:54:17 2005 UTC (20 years ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/baserenderer.py
File MIME type: text/x-python
File size: 21844 byte(s)
Refactored baserenderer.py and renderer.py to remove baserenderer.py's
dependencies on wxPython. Added a new method projected_raster_layer()
that returns a raster layer image in projected space. This must be
implemented in classes derived from BaseRenderer. This also eliminates
the dependency on gdal in baserenderer.py.

1 bh 2224 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 bh 1554 # 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 bh 1866 from __future__ import generators
18    
19 bh 1554 __version__ = "$Revision$"
20     # $Source$
21     # $Id$
22    
23 bh 1926 import sys
24 bh 1554 import traceback
25    
26     from Thuban.Model.layer import Layer, RasterLayer
27     from Thuban.Model.data import SHAPETYPE_ARC, SHAPETYPE_POINT
28     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
29     ALIGN_LEFT, ALIGN_RIGHT
30    
31     import Thuban.Model.resource
32    
33    
34    
35 bh 1926 #
36     # Renderer Extensions
37     #
38     # The renderer extensions provide a way to render layer types defined in
39     # Thuban extensions. The renderer extensions are stored as a list with
40     # (layer_class, draw_function) pairs. If the renderer has to draw a
41     # non-builtin layer type, i.e. a layer that is not a subclass of Layer
42     # or RasterLayer, it iterates through that list, tests whether the layer
43 bh 2224 # to be drawn is an instance of layer_class and if so calls
44 bh 1926 # draw_function with the renderer and the layer as arguments. Since
45     # drawing is done incrementally, the draw_function should return an
46     # iterable. The easiest way is to simply implement the draw_function as
47     # a generator and to yield in suitable places, or to return the empty
48     # tuple.
49     #
50     # New renderer extensions should be added with add_renderer_extension().
51     # If necessary the extensions list can be reset with
52     # init_renderer_extensions().
53    
54     _renderer_extensions = []
55    
56     def add_renderer_extension(layer_class, function):
57     """Add a renderer extension for the layer class layer_class
58    
59     When an instance of layer_class is to be drawn by the renderer the
60     renderer will call function with the renderer and the layer_class
61     instance as arguments. Since drawing is done incrementally, the
62     function should return an iterable. The easiest way is to simply
63     implement the draw_function as a generator and to yield True in
64     suitable places, or to return the empty tuple if it's not possible
65 bh 2224 to do the rendering incrementally.
66 bh 1926 """
67     _renderer_extensions.append((layer_class, function))
68    
69     def init_renderer_extensions():
70     """(Re)initialize the list of renderer extensions
71    
72     Calling this function outside of the test suite is probably not
73     useful.
74     """
75     del _renderer_extensions[:]
76    
77 jonathan 2537 def proj_params_to_str(proj):
78     "Build a string suitable for GDAL describing the given projection"
79     str = ""
80     if proj is not None:
81     for p in proj.GetAllParameters():
82     str += "+" + p + " "
83     return str
84 bh 1926
85     #
86     # Base Renderer
87     #
88    
89 bh 1554 class BaseRenderer:
90    
91     """Basic Renderer Infrastructure for Thuban Maps
92    
93     This class can't be used directly to render because it doesn't know
94     anything about real DCs such as how to create pens or brushes. That
95     functionality has to be provided by derived classes. The reason for
96     this is that it makes the BaseRenderer completely independend of wx
97     and thus it's quite easy to write test cases for it.
98     """
99     # If true the render honors the visibility flag of the layers
100     honor_visibility = 1
101    
102     # Transparent brushes and pens. Derived classes should define these
103     # as appropriate.
104     TRANSPARENT_PEN = None
105     TRANSPARENT_BRUSH = None
106    
107 bh 1866 def __init__(self, dc, map, scale, offset, region = None,
108     resolution = 72.0, honor_visibility = None):
109 bh 1554 """Inititalize the renderer.
110    
111     dc -- the device context to render on.
112    
113     scale, offset -- the scale factor and translation to convert
114     between projected coordinates and the DC coordinates
115    
116 bh 1866 region -- The region to draw as a (x, y, width, height) tuple in
117     the map's coordinate system. Default is None meaning
118     to draw everything.
119    
120 bh 1554 resolution -- the assumed resolution of the DC. Used to convert
121     absolute lengths like font sizes to DC coordinates. The
122 bh 1866 default is 72.0. If given, this parameter must be
123     provided as a keyword argument.
124 bh 1554
125     honor_visibility -- boolean. If true, honor the visibility flag
126     of the layers, otherwise draw all layers. If None (the
127 bh 1866 default), use the renderer's default. If given, this
128     parameter must be provided as a keyword argument.
129 bh 1554 """
130     # resolution in pixel/inch
131     self.dc = dc
132 bh 1866 self.map = map
133 bh 1554 self.scale = scale
134     self.offset = offset
135 bh 1866 self.region = region
136 bh 1554 if honor_visibility is not None:
137     self.honor_visibility = honor_visibility
138     # store the resolution in pixel/point because it's more useful
139     # later.
140     self.resolution = resolution / 72.0
141    
142     def tools_for_property(self, prop):
143     """Return a suitable pen and brush for the property
144    
145     This method must be implemented in derived classes. The return
146     value should be a tuple (pen, brush).
147     """
148     raise NotImplementedError
149    
150 bh 1866 def render_map(self):
151     """Render the map onto the DC.
152 bh 1554
153 bh 1866 Both map and DC are the ones the renderer was instantiated with.
154    
155     This method is just a front-end for render_map_incrementally
156     which does all rendering in one go. It also calls the dc's
157     BeginDrawing and EndDrawing methods before and after calling
158     render_map_incrementally.
159     """
160    
161     self.dc.BeginDrawing()
162     try:
163     for cont in self.render_map_incrementally():
164     pass
165     finally:
166     self.dc.EndDrawing()
167    
168     def render_map_incrementally(self):
169     """Render the map incrementally.
170    
171     Return an iterator whose next method should be called until it
172     returns False. After returning False it will raise StopIteration
173     so that you could also use it in a for loop (implementation
174     detail: this method is implemented as a generator).
175    
176 bh 1554 Iterate through all layers and draw them. Layers containing
177     vector data are drawn with the draw_shape_layer method, raster
178     layers are drawn with draw_raster_layer. The label layer is
179     drawn last with draw_label_layer.
180    
181     During execution of this method, the map is bound to self.map so
182     that methods especially in derived classes have access to the
183     map if necessary.
184     """
185    
186 bh 1866 for layer in self.map.Layers():
187     # if honor_visibility is true, only draw visible layers,
188     # otherwise draw all layers
189     if not self.honor_visibility or layer.Visible():
190 bh 2278 if isinstance(layer, Layer):
191 jonathan 2537 for i in self.draw_shape_layer_incrementally(layer):
192     yield True
193 jonathan 2571 elif isinstance(layer, RasterLayer):
194 bh 1866 self.draw_raster_layer(layer)
195     yield True
196 bh 1926 else:
197     # look it up in the renderer extensions
198     for cls, func in _renderer_extensions:
199     if isinstance(layer, cls):
200     for i in func(self, layer):
201     yield True
202     break
203     else:
204     # No renderer found. Print a message about it
205     print >>sys.stderr, ("Drawing layer %r not supported"
206     % layer)
207     yield True
208 bh 1554
209 bh 1866 self.draw_label_layer(self.map.LabelLayer())
210     yield False
211 bh 1554
212 bh 1866 def draw_shape_layer_incrementally(self, layer):
213     """Draw the shape layer layer onto the map incrementally.
214 bh 1554
215 bh 1866 This method is a generator which yields True after every 500
216     shapes.
217 bh 1554 """
218     scale = self.scale
219     offx, offy = self.offset
220    
221     map_proj = self.map.projection
222     layer_proj = layer.projection
223    
224     brush = self.TRANSPARENT_BRUSH
225     pen = self.TRANSPARENT_PEN
226    
227     old_prop = None
228     old_group = None
229     lc = layer.GetClassification()
230     field = layer.GetClassificationColumn()
231     defaultGroup = lc.GetDefaultGroup()
232     table = layer.ShapeStore().Table()
233    
234 bh 1879 if lc.GetNumGroups() == 0:
235     # There's only the default group, so we can pretend that
236     # there is no field to classifiy on which makes things
237     # faster since we don't need the attribute information at
238     # all.
239     field = None
240    
241 bh 1554 # Determine which render function to use.
242 bh 1591 useraw, draw_func, draw_func_param = self.low_level_renderer(layer)
243 bh 1554
244 bh 1914 #
245 bh 1554 # Iterate through all shapes that have to be drawn.
246 bh 1914 #
247    
248     # Count the shapes drawn so that we can yield every few hundred
249     # shapes
250 bh 1866 count = 0
251 bh 1914
252     # Cache the tools (pens and brushes) for the classification
253     # groups. This is a mapping from the group's ids to the a tuple
254     # (pen, brush)
255     tool_cache = {}
256    
257 bh 1593 for shape in self.layer_shapes(layer):
258 bh 1866 count += 1
259 bh 1554 if field is None:
260     group = defaultGroup
261     else:
262 bh 1918 value = table.ReadValue(shape.ShapeID(), field)
263 bh 1914 group = lc.FindGroup(value)
264 bh 1554
265     if not group.IsVisible():
266     continue
267    
268 bh 1914 try:
269     pen, brush = tool_cache[id(group)]
270     except KeyError:
271     pen, brush = tool_cache[id(group)] \
272     = self.tools_for_property(group.GetProperties())
273 bh 1554
274 bh 1591 if useraw:
275     data = shape.RawData()
276     else:
277     data = shape.Points()
278 jan 2377 if draw_func == self.draw_point_shape:
279     draw_func(draw_func_param, data, pen, brush,
280     size = group.GetProperties().GetSize())
281     else:
282     draw_func(draw_func_param, data, pen, brush)
283 bh 1866 if count % 500 == 0:
284     yield True
285 bh 1554
286 bh 1593 def layer_shapes(self, layer):
287     """Return an iterable over the shapes to be drawn from the given layer.
288 bh 1554
289     The default implementation simply returns all ids in the layer.
290     Override in derived classes to be more precise.
291     """
292 bh 1593 return layer.ShapeStore().AllShapes()
293 bh 1554
294     def low_level_renderer(self, layer):
295     """Return the low-level renderer for the layer for draw_shape_layer
296    
297     The low level renderer to be returned by this method is a tuple
298 bh 1591 (useraw, func, param) where useraw is a boolean indicating
299     whether the function uses the raw shape data, func is a callable
300     object and param is passed as the first parameter to func. The
301     draw_shape_layer method will call func like this:
302 bh 1554
303 bh 1591 func(param, shapedata, pen, brush)
304 bh 1554
305 bh 1591 where shapedata is the return value of the RawData method of the
306     shape object if useraw is true or the return value of the Points
307     method if it's false. pen and brush are the pen and brush to use
308     to draw the shape on the dc.
309 bh 1554
310     The default implementation returns one of
311     self.draw_polygon_shape, self.draw_arc_shape or
312 bh 1591 self.draw_point_shape as func and layer as param. None of the
313     method use the raw shape data. Derived classes can override this
314     method to return more efficient low level renderers.
315 bh 1554 """
316     shapetype = layer.ShapeType()
317     if shapetype == SHAPETYPE_POINT:
318     func = self.draw_point_shape
319     elif shapetype == SHAPETYPE_ARC:
320     func = self.draw_arc_shape
321     else:
322     func = self.draw_polygon_shape
323 bh 1591 return False, func, layer
324 bh 1554
325     def make_point(self, x, y):
326     """Convert (x, y) to a point object.
327    
328     Derived classes must override this method.
329     """
330     raise NotImplementedError
331    
332 bh 1591 def projected_points(self, layer, points):
333     """Return the projected coordinates of the points taken from layer.
334 bh 1554
335 bh 1591 Transform all the points in the list of lists of coordinate
336     pairs in points.
337 bh 1554
338     The transformation applies the inverse of the layer's projection
339     if any, then the map's projection if any and finally applies
340     self.scale and self.offset.
341    
342     The returned list has the same structure as the one returned the
343     shape's Points method.
344     """
345     proj = self.map.GetProjection()
346     if proj is not None:
347     forward = proj.Forward
348     else:
349     forward = None
350     proj = layer.GetProjection()
351     if proj is not None:
352     inverse = proj.Inverse
353     else:
354     inverse = None
355 bh 1591 result = []
356 bh 1554 scale = self.scale
357     offx, offy = self.offset
358     make_point = self.make_point
359 jonathan 2537
360 bh 1591 for part in points:
361     result.append([])
362 bh 1554 for x, y in part:
363     if inverse:
364     x, y = inverse(x, y)
365     if forward:
366     x, y = forward(x, y)
367 bh 1591 result[-1].append(make_point(x * scale + offx,
368 bh 1554 -y * scale + offy))
369 bh 1591 return result
370 bh 1554
371 bh 1591 def draw_polygon_shape(self, layer, points, pen, brush):
372     """Draw a polygon shape from layer with the given brush and pen
373 bh 1554
374 bh 1591 The shape is given by points argument which is a the return
375     value of the shape's Points() method. The coordinates in the
376     DC's coordinate system are determined with
377 bh 1554 self.projected_points.
378 bernhard 2413
379     For a description of the algorithm look in wxproj.cpp.
380 bh 1554 """
381 bh 1591 points = self.projected_points(layer, points)
382 bh 1554
383     if brush is not self.TRANSPARENT_BRUSH:
384     polygon = []
385     for part in points:
386     polygon.extend(part)
387    
388 bernhard 2413 # missing back vertices for correct filling.
389 bh 1554 insert_index = len(polygon)
390     for part in points[:-1]:
391     polygon.insert(insert_index, part[0])
392    
393     self.dc.SetBrush(brush)
394     self.dc.SetPen(self.TRANSPARENT_PEN)
395     self.dc.DrawPolygon(polygon)
396    
397     if pen is not self.TRANSPARENT_PEN:
398     # At last draw the boundarys of the simple polygons
399     self.dc.SetBrush(self.TRANSPARENT_BRUSH)
400     self.dc.SetPen(pen)
401     for part in points:
402     self.dc.DrawLines(part)
403    
404 bh 1591 def draw_arc_shape(self, layer, points, pen, brush):
405     """Draw an arc shape from layer with the given brush and pen
406 bh 1554
407 bh 1591 The shape is given by points argument which is a the return
408     value of the shape's Points() method. The coordinates in the
409     DC's coordinate system are determined with
410 bh 1554 self.projected_points.
411     """
412 bh 1591 points = self.projected_points(layer, points)
413 bh 1554 self.dc.SetBrush(brush)
414     self.dc.SetPen(pen)
415     for part in points:
416     self.dc.DrawLines(part)
417    
418 jan 2377 def draw_point_shape(self, layer, points, pen, brush, size = 5):
419 bh 1591 """Draw a point shape from layer with the given brush and pen
420 bh 1554
421 bh 1591 The shape is given by points argument which is a the return
422     value of the shape's Points() method. The coordinates in the
423     DC's coordinate system are determined with
424 bh 1554 self.projected_points.
425    
426     The point is drawn as a circle centered on the point.
427     """
428 bh 1591 points = self.projected_points(layer, points)
429 bh 1554 if not points:
430     return
431    
432 jan 2377 radius = int(round(self.resolution * size))
433 bh 1554 self.dc.SetBrush(brush)
434     self.dc.SetPen(pen)
435     for part in points:
436     for p in part:
437     self.dc.DrawEllipse(p.x - radius, p.y - radius,
438     2 * radius, 2 * radius)
439    
440     def draw_raster_layer(self, layer):
441     """Draw the raster layer
442    
443 jonathan 2571 This implementation uses self.projected_raster_layer() to project
444     and scale the data as required by the layer's and map's projections
445     and the scale and offset of the renderer and then hands the transformed
446     data to self.draw_raster_data() which has to be implemented in
447 bh 1554 derived classes.
448     """
449     offx, offy = self.offset
450     width, height = self.dc.GetSizeTuple()
451    
452 jonathan 2537 in_proj = proj_params_to_str(layer.GetProjection())
453     out_proj = proj_params_to_str(self.map.GetProjection())
454 bh 1554
455 jonathan 2537 # True -- warp the image to the size of the whole screen
456     # False -- only use the bound box of the layer (currently inaccurate)
457     if True:
458 jonathan 2551 #if False:
459 jonathan 2537 pmin = [0,height]
460     pmax = [width, 0]
461     else:
462     bb = layer.LatLongBoundingBox()
463     bb = [[[bb[0], bb[1]], [bb[2], bb[3]],],]
464     pmin, pmax = self.projected_points(layer, bb)[0]
465 bh 1554
466 jonathan 2551 #print bb
467     #print pmin, pmax
468 bh 1554
469 jonathan 2551 fmin = [max(0, min(pmin[0], pmax[0])) - offx,
470     offy - min(height, max(pmin[1], pmax[1]))]
471    
472     fmax = [min(width, max(pmin[0], pmax[0])) - offx,
473     offy - max(0, min(pmin[1], pmax[1]))]
474    
475 jonathan 2537 xmin = fmin[0]/self.scale
476     ymin = fmin[1]/self.scale
477     xmax = fmax[0]/self.scale
478     ymax = fmax[1]/self.scale
479    
480     width = int(min(width, round(fmax[0] - fmin[0] + 1)))
481     height = int(min(height, round(fmax[1] - fmin[1] + 1)))
482    
483 jonathan 2571 options = 0
484     options = options | layer.MaskType()
485 jonathan 2552
486 jonathan 2571 img_data = self.projected_raster_layer(layer, in_proj, out_proj,
487     (xmin,ymin,xmax,ymax), [0,0], (width, height), options)
488 jonathan 2551
489 jonathan 2571 if img_data is not None:
490     data = (width, height, img_data)
491 jonathan 2551 self.draw_raster_data(fmin[0]+offx, offy-fmax[1], data, "RAW")
492 jonathan 2537 data = None
493 bh 1554
494 jonathan 2571 def projected_raster_layer(self, layer, srcProj, dstProj, extents,
495     resolution, dimensions, options):
496     """Return the projected raster image associated with the layer.
497    
498     The returned value will be a tuple of the form
499    
500     (image_data, mask_data, alpha_data)
501    
502     suitable for the data parameter to draw_raster_data.
503    
504     The return value may be None if raster projections are not supported.
505    
506     srcProj -- a string describing the source projection
507     dstProj -- a string describing the destination projection
508     extents -- a tuple of the region to project in map coordinates
509     resolution -- (currently not used, defaults to [0,0])
510     dimensions -- a tuple (width, height) for the output image
511     options -- bit-wise options to pass to the renderer
512    
513     the currently supported values for options are
514    
515     OPTS_MASK = 1 -- generate a mask
516     OPTS_ALPHA = 2 -- generate an alpha channel
517     OPTS_INVERT_MASK = 4 -- invert the values in the mask
518     (if generated)
519    
520     This method has to be implemented by derived classes.
521     """
522    
523     raise NotImplementedError
524    
525 jonathan 2551 def draw_raster_data(self, x, y, data, format="BMP"):
526 jonathan 2537 """Draw the raster image in data onto the DC with the top
527     left corner at (x,y)
528 bh 1554
529 jonathan 2551 The raster image data is a tuple of the form
530 jonathan 2562 (width, height, (image_data, mask_data, alpha_data))
531 jonathan 2551
532 jonathan 2562 holding the image width, height, image data, mask data, and alpha data.
533     mask_data may be None if a mask should not be used. alpha_data may
534     also be None. If both are not None mask overrides alpha. If
535 jonathan 2552 format is 'RAW' the data will be RGB values and the mask
536     will be in XMB format. Otherwise, both kinds
537 jonathan 2551 of data are assumed to be in the format specified in format.
538 bh 1554
539 bh 1926 The format parameter is a string with the name of the format.
540     The following format names should be used:
541    
542 jonathan 2537 'RAW' -- an array of RGB values (len=3*width*height)
543     'BMP' -- Windows Bitmap
544     'JPEG' -- JPEG Image
545 bh 1926
546 jonathan 2537 The default format is 'BMP'.
547 bh 1926
548     This method has to be implemented by derived classes. The
549     implementation in the derived class should try to support at
550     least the formats specified above and may support more.
551 bh 1554 """
552     raise NotImplementedError
553    
554     def label_font(self):
555     """Return the font object for the label layer"""
556     raise NotImplementedError
557    
558     def draw_label_layer(self, layer):
559     """Draw the label layer
560    
561     All labels are draw in the font returned by self.label_font().
562     """
563     scale = self.scale
564     offx, offy = self.offset
565    
566     self.dc.SetFont(self.label_font())
567    
568     map_proj = self.map.projection
569     if map_proj is not None:
570     forward = map_proj.Forward
571     else:
572     forward = None
573    
574     for label in layer.Labels():
575     x = label.x
576     y = label.y
577     text = label.text
578     if forward:
579     x, y = forward(x, y)
580 bh 1935 x = int(round(x * scale + offx))
581     y = int(round(-y * scale + offy))
582 bh 1554 width, height = self.dc.GetTextExtent(text)
583     if label.halign == ALIGN_LEFT:
584     # nothing to be done
585     pass
586     elif label.halign == ALIGN_RIGHT:
587     x = x - width
588     elif label.halign == ALIGN_CENTER:
589     x = x - width/2
590     if label.valign == ALIGN_TOP:
591     # nothing to be done
592     pass
593     elif label.valign == ALIGN_BOTTOM:
594     y = y - height
595     elif label.valign == ALIGN_CENTER:
596     y = y - height/2
597     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