/[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 2618 - (show annotations)
Fri May 6 14:18:31 2005 UTC (19 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 16981 byte(s)
(MapRenderer.draw_raster_data): Fixed signature to match that in BaseRenderer.
Use the new opacity argument in place of calling layer.Opacity(). In the case
where the format is not 'RAW', alpha_data is None and the loaded image has
alpha information, use the file's alpha information. This is still subject to
the layer's opacity setting.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26