/[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 2587 - (show annotations)
Wed Mar 23 15:30:27 2005 UTC (19 years, 11 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 16730 byte(s)
Add support for adjusting the opacity of a raster layer.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26