/[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 2700 - (show annotations)
Mon Sep 18 14:27:02 2006 UTC (18 years, 5 months ago) by dpinte
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 16780 byte(s)
2006-09-18 Didrik Pinte <dpinte@itae.be>
    
        * wxPython 2.6 update : wx 2.4 syntax has been updated to 2.6


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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26