/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/renderer.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/renderer.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2734 - (show annotations)
Thu Mar 1 12:42:59 2007 UTC (18 years ago) by bramz
File MIME type: text/x-python
File size: 17123 byte(s)
made a copy
1 # Copyright (c) 2001-2006 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]> (2001-2003)
4 # Jonathan Coles <[email protected]> (2003)
5 # Frank Koormann <[email protected]> (2003)
6 # Jan-Oliver Wagner <[email protected]> (2003-2005)
7 #
8 # This program is free software under the GPL (>=v2)
9 # Read the file COPYING coming with Thuban for details.
10
11 from __future__ import generators
12
13 __version__ = "$Revision$"
14 # $Source$
15 # $Id$
16
17 import cStringIO
18
19 import array
20
21 import traceback
22
23 from Thuban import _
24
25 import wx
26
27 from wxproj import draw_polygon_shape, draw_polygon_init
28
29 from Thuban.UI.common import Color2wxColour
30 from Thuban.UI.classifier import ClassDataPreviewer
31 from Thuban.UI.scalebar import ScaleBar
32
33 from Thuban.Model.data import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
34 SHAPETYPE_POINT, RAW_SHAPEFILE
35
36 from Thuban.Model.color import Transparent
37 import Thuban.Model.resource
38
39 from baserenderer import BaseRenderer
40
41 from math import floor
42
43 from types import StringType
44
45 from Thuban.version import versions
46
47 if Thuban.Model.resource.has_gdal_support():
48 from gdalwarp import ProjectRasterFile
49
50 verbose = 0 # whether to talk more on stdout
51
52 # Map the strings used for the format parameter of the draw_raster_data
53 # method to the appropriate wxWindows constants
54 raster_format_map = {
55 "BMP": wx.BITMAP_TYPE_BMP,
56 "JPEG": wx.BITMAP_TYPE_JPEG,
57 "PNG": wx.BITMAP_TYPE_PNG,
58 "TIFF": wx.BITMAP_TYPE_TIF,
59 "GIF": wx.BITMAP_TYPE_GIF,
60 }
61
62 class MapRenderer(BaseRenderer):
63
64 """Class to render a map onto a wxDC"""
65
66 TRANSPARENT_PEN = wx.TRANSPARENT_PEN
67 TRANSPARENT_BRUSH = wx.TRANSPARENT_BRUSH
68
69 def make_point(self, x, y):
70 return wx.Point(int(round(x)), int(round(y)))
71
72 def tools_for_property(self, prop):
73 fill = prop.GetFill()
74 if fill is Transparent:
75 brush = self.TRANSPARENT_BRUSH
76 else:
77 brush = wx.Brush(Color2wxColour(fill), wx.SOLID)
78
79 stroke = prop.GetLineColor()
80 if stroke is Transparent:
81 pen = self.TRANSPARENT_PEN
82 else:
83 pen = wx.Pen(Color2wxColour(stroke), prop.GetLineWidth(), wx.SOLID)
84 return pen, brush
85
86 def low_level_renderer(self, layer):
87 """Override inherited method to provide more efficient renderers
88
89 If the underlying data format is not a shapefile or the layer
90 contains points shapes, simply use what the inherited method
91 returns.
92
93 Otherwise, i.e. for arc and polygon use the more efficient
94 wxproj.draw_polygon_shape and its corresponding parameter
95 created with wxproj.draw_polygon_init.
96 """
97 if (layer.ShapeStore().RawShapeFormat() == RAW_SHAPEFILE
98 and layer.ShapeType() in (SHAPETYPE_ARC, SHAPETYPE_POLYGON)):
99 offx, offy = self.offset
100 x = lambda a, b, c, d: None
101 #return (True, x, None)
102 return (True, draw_polygon_shape,
103 draw_polygon_init(layer.ShapeStore().Shapefile(),
104 self.dc, self.map.projection,
105 layer.projection,
106 self.scale, -self.scale, offx, offy))
107 else:
108 return BaseRenderer.low_level_renderer(self, layer)
109
110 def label_font(self):
111 return wx.Font(int(round(self.resolution * 10)), wx.SWISS, wx.NORMAL,
112 wx.NORMAL)
113
114 def projected_raster_layer(self, layer, srcProj, dstProj, extents,
115 resolution, dimensions, options):
116 """Returns a raster layer image in projected space
117
118 Based on a given filename. This method must be implemented in
119 classes derived from BaseRenderer.
120 """
121
122 ret = None
123
124 if Thuban.Model.resource.has_gdal_support():
125
126 if versions['wxPython-tuple'] < (2,5,3):
127 options = options | 4 # INVERT_MASK_BITS
128 options = options & ~2 # ALPHA_MASK not supported
129
130 try:
131 if verbose > 0:
132 print "doing ProjectRasterFile '%s' -> '%s'" % \
133 (srcProj, dstProj)
134 print "extents:", extents, "resolution:", resolution
135 print "dimensions:", dimensions, "options:", options
136 ret = ProjectRasterFile(layer.GetImageFilename(),
137 srcProj, dstProj,
138 extents, resolution, dimensions,
139 options)
140 except (MemoryError, IOError, AttributeError, ValueError):
141 # Why does this catch AttributeError and ValueError?
142 # FIXME: The exception should be communicated to the user
143 # better.
144 traceback.print_exc()
145
146 return ret
147
148 def draw_raster_data(self, x,y, data, format = 'BMP', opacity=1.0):
149
150 mask = None
151 alpha = None
152 width = data[0]
153 height = data[1]
154 image_data, mask_data, alpha_data = data[2]
155
156 if versions['wxPython-tuple'] < (2,5,3):
157 alpha_data = None
158
159 if format == 'RAW':
160 image = wx.EmptyImage(width, height)
161 image.SetData(image_data)
162 if mask_data is not None:
163 mask = wx.BitmapFromBits(mask_data, width, height, 1)
164 mask = wx.Mask(mask)
165 elif alpha_data is not None:
166 # alpha_data is already in the right format
167 alpha = alpha_data
168
169 else:
170 stream = cStringIO.StringIO(image_data)
171 image = wx.ImageFromStream(stream, raster_format_map[format])
172
173 if mask_data is not None:
174 stream = cStringIO.StringIO(mask_data)
175 mask = wx.ImageFromStream(stream, raster_format_map[format])
176 mask = wx.Mask(wx.BitmapFromImage(mask, 1))
177 elif alpha_data is not None:
178 stream = cStringIO.StringIO(alpha_data)
179 alpha = wx.ImageFromStream(stream, raster_format_map[format])
180 alpha = alpha.GetData() #[:] # XXX: do we need to copy this?
181 elif image.HasAlpha():
182 alpha = image.GetAlphaData()
183
184 #
185 # scale down the alpha values the opacity level using a string
186 # translation table for efficiency.
187 #
188 if alpha is not None:
189 if opacity == 0:
190 return
191 elif opacity == 1:
192 a = alpha
193 else:
194 tr = [int(i*opacity) for i in range(256)]
195 table = array.array('B', tr).tostring()
196 a = alpha.translate(table)
197
198 image.SetAlphaData(a)
199
200 bitmap = wx.BitmapFromImage(image)
201
202 if mask is not None:
203 bitmap.SetMask(mask)
204
205 self.dc.DrawBitmap(bitmap, int(round(x)), int(round(y)), True)
206
207
208 class ScreenRenderer(MapRenderer):
209
210 # On the screen we want to see only visible layers by default
211 honor_visibility = 1
212
213 def RenderMap(self, selected_layer, selected_shapes):
214 """Render the map.
215
216 Only the given region (a tuple in window coordinates as returned
217 by a wxrect's asTuple method) needs to be redrawn. Highlight the
218 shapes given by the ids in selected_shapes in the
219 selected_layer.
220 """
221 self.selected_layer = selected_layer
222 self.selected_shapes = selected_shapes
223 self.render_map()
224
225 def RenderMapIncrementally(self):
226 """Render the map.
227
228 Only the given region (a tuple in window coordinates as returned
229 by a wxrect's asTuple method) needs to be redrawn. Highlight the
230 shapes given by the ids in selected_shapes in the
231 selected_layer.
232 """
233 return self.render_map_incrementally()
234
235 def draw_selection_incrementally(self, layer, selected_shapes):
236 """Draw the selected shapes in a emphasized way (i.e.
237 with a special pen and brush.
238 The drawing is performed incrementally, that means every
239 n shapes, the user can have interactions with the map.
240 n is currently fixed to 500.
241
242 layer -- the layer where the shapes belong to.
243 selected_shapes -- a list of the shape-ids representing the
244 selected shapes for the given layer.
245 """
246 pen = wx.Pen(wx.BLACK, 3, wx.SOLID)
247 brush = wx.Brush(wx.BLACK, wx.CROSS_HATCH)
248
249 shapetype = layer.ShapeType()
250 useraw, func, param = self.low_level_renderer(layer)
251 args = (pen, brush)
252
253 # for point shapes we need to find out the properties
254 # to determine the size. Based on table and field,
255 # we can find out the properties for object - see below.
256 if shapetype == SHAPETYPE_POINT:
257 lc = layer.GetClassification()
258 field = layer.GetClassificationColumn()
259 table = layer.ShapeStore().Table()
260
261 count = 0
262 for index in selected_shapes:
263 count += 1
264 shape = layer.Shape(index)
265
266 # Get the size of the specific property for this
267 # point
268 if shapetype == SHAPETYPE_POINT:
269 if field is not None:
270 value = table.ReadValue(shape.ShapeID(), field)
271 group = lc.FindGroup(value)
272 size = group.GetProperties().GetSize()
273 else:
274 size = lc.GetDefaultGroup().GetProperties().GetSize()
275 args = (pen, brush, size)
276
277 if useraw:
278 data = shape.RawData()
279 else:
280 data = shape.Points()
281 func(param, data, *args)
282 if count % 500 == 0:
283 yield True
284
285 def layer_shapes(self, layer):
286 """Return the shapeids covered by the region that has to be redrawn
287
288 Call the layer's ShapesInRegion method to determine the ids so
289 that it can use the quadtree.
290 """
291 # FIXME: the quad-tree should be built from the projected
292 # coordinates not the lat-long ones because it's not trivial to
293 # determine an appropriate rectangle in lat-long for a given
294 # rectangle in projected coordinates which we have to start from
295 # here.
296 proj = self.map.projection
297 if proj is not None:
298 inverse = proj.Inverse
299 else:
300 inverse = None
301
302 scale = self.scale
303 offx, offy = self.offset
304 xs = []
305 ys = []
306 x, y, width, height = self.region
307 for winx, winy in ((x, y), (x + width, y),
308 (x + width, y + height), (x, y + height)):
309 px = (winx - offx) / scale
310 py = -(winy - offy) / scale
311 if inverse:
312 px, py = inverse(px, py)
313 xs.append(px)
314 ys.append(py)
315 left = min(xs)
316 right = max(xs)
317 top = max(ys)
318 bottom = min(ys)
319
320 return layer.ShapesInRegion((left, bottom, right, top))
321
322
323 class ExportRenderer(ScreenRenderer):
324
325 honor_visibility = 1
326
327 def __init__(self, *args, **kw):
328 """Initialize the ExportRenderer.
329
330 In addition to all parameters of the the ScreenRender
331 constructor, this class requires and additional keyword argument
332 destination_region with a tuple (minx, miny, maxx, maxy) giving
333 the region in dc coordinates which is to contain the map.
334 """
335 self.destination_region = kw["destination_region"]
336 del kw["destination_region"]
337 ScreenRenderer.__init__(self, *args, **kw)
338
339 def RenderMap(self, selected_layer, selected_shapes):
340 """Render the map.
341
342 The rendering device has been specified during initialisation.
343 The device border distance was set in
344 Thuban.UI.viewport.output_transform().
345
346 RenderMap renders a frame set (one page frame, one around
347 legend/scalebar and one around the map), the map, the legend and
348 the scalebar on the given DC. The map is rendered with the
349 region displayed in the canvas view, centered on the area
350 available for map display.
351 """
352
353 self.selected_layer = selected_layer
354 self.selected_shapes = selected_shapes
355
356 # Get some dimensions
357 llx, lly, urx, ury = self.region
358 mminx, mminy, mmaxx, mmaxy = self.destination_region
359
360 # Manipulate the offset to position the map
361 offx, offy = self.offset
362 # 1. Shift to corner of map drawing area
363 offx = offx + mminx
364 offy = offy + mminy
365
366 # 2. Center the map on the map drawing area:
367 # region identifies the region on the canvas view:
368 # center of map drawing area - half the size of region: rendering origin
369 self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
370 self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5
371
372 self.offset = (offx+self.shiftx, offy+self.shifty)
373 self.region = (llx + self.shiftx, lly + self.shifty, urx, ury)
374
375 # Draw the map
376 self.dc.BeginDrawing()
377 self.dc.DestroyClippingRegion()
378 self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
379 urx, ury)
380 self.render_map()
381 self.dc.EndDrawing()
382
383 # Draw the rest (frames, legend, scalebar)
384 self.dc.BeginDrawing()
385 self.dc.DestroyClippingRegion()
386
387 # Force the font for Legend drawing
388 font = wx.Font(self.resolution * 10, wx.SWISS, wx.NORMAL, wx.NORMAL)
389 self.dc.SetFont(font)
390
391 self.render_frame()
392 self.render_legend()
393 self.render_scalebar()
394 self.dc.EndDrawing()
395
396 def render_frame(self):
397 """Render the frames for map and legend/scalebar."""
398
399 dc = self.dc
400 dc.SetPen(wx.BLACK_PEN)
401 dc.SetBrush(wx.TRANSPARENT_BRUSH)
402
403 # Dimension stuff
404 width, height = dc.GetSizeTuple()
405 mminx, mminy, mmaxx, mmaxy = self.destination_region
406
407 # Page Frame
408 dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)
409
410 # Map Frame
411 llx, lly, urx, ury = self.region
412 dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)
413
414 # Legend Frame
415 dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)
416
417 dc.DestroyClippingRegion()
418 dc.SetClippingRegion(mmaxx+10,mminy,
419 (width-20) - (mmaxx+10), mmaxy-mminy)
420
421 def render_legend(self):
422 """Render the legend on the Map."""
423
424 previewer = ClassDataPreviewer()
425 dc = self.dc
426 dc.SetPen(wx.BLACK_PEN)
427 dc.SetBrush(wx.TRANSPARENT_BRUSH)
428
429 # Dimension stuff
430 width, height = dc.GetSizeTuple()
431 mminx, mminy, mmaxx, mmaxy = self.destination_region
432 textwidth, textheight = dc.GetTextExtent("0")
433 iconwidth = textheight
434 iconheight = textheight
435 stepy = textheight+3
436 dx = 10
437 posx = mmaxx + 10 + 5 # 10 pix distance mapframe/legend frame,
438 # 5 pix inside legend frame
439 posy = mminy + 5 # 5 pix inside legend frame
440
441 # Render the legend
442 dc.SetTextForeground(wx.BLACK)
443 if self.map.HasLayers():
444 layers = self.map.Layers()[:]
445 layers.reverse()
446 for l in layers:
447 if l.Visible():
448 # Render title
449 dc.DrawText(l.Title(), posx, posy)
450 posy+=stepy
451 if l.HasClassification():
452 # Render classification
453 clazz = l.GetClassification()
454 shapeType = l.ShapeType()
455 for g in clazz:
456 if g.IsVisible():
457 previewer.Draw(dc,
458 wx.Rect(posx+dx, posy,
459 iconwidth, iconheight),
460 g.GetProperties(), shapeType)
461 dc.DrawText(g.GetDisplayText(),
462 posx+2*dx+iconwidth, posy)
463 posy+=stepy
464
465 def render_scalebar(self):
466 """Render the scalebar."""
467
468 scalebar = ScaleBar(self.map)
469
470 # Dimension stuff
471 width, height = self.dc.GetSizeTuple()
472 mminx, mminy, mmaxx, mmaxy = self.destination_region
473
474 # Render the scalebar
475 scalebar.DrawScaleBar(self.scale, self.dc,
476 (mmaxx+10+5, mmaxy-25),
477 ((width-15-5) - (mmaxx+10+5),20)
478 )
479 # 10 pix between map and legend frame, 5 pix inside legend frame
480 # 25 pix from the legend frame bottom line
481 # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
482 # Height: 20
483
484 class PrinterRenderer(ExportRenderer):
485
486 # Printing as well as Export / Screen display only the visible layer.
487 honor_visibility = 1
488

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26