/[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 2639 - (show annotations)
Thu Jun 30 16:15:03 2005 UTC (19 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 17077 byte(s)
(ScreenRenderer.draw_selection_incrementally):
untabify.

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 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:
268 if field is not None:
269 value = table.ReadValue(shape.ShapeID(), field)
270 group = lc.FindGroup(value)
271 size = group.GetProperties().GetSize()
272 else:
273 size = lc.GetDefaultSize()
274 args = (pen, brush, size)
275
276 if useraw:
277 data = shape.RawData()
278 else:
279 data = shape.Points()
280 func(param, data, *args)
281 if count % 500 == 0:
282 yield True
283
284 def layer_shapes(self, layer):
285 """Return the shapeids covered by the region that has to be redrawn
286
287 Call the layer's ShapesInRegion method to determine the ids so
288 that it can use the quadtree.
289 """
290 # FIXME: the quad-tree should be built from the projected
291 # coordinates not the lat-long ones because it's not trivial to
292 # determine an appropriate rectangle in lat-long for a given
293 # rectangle in projected coordinates which we have to start from
294 # here.
295 proj = self.map.projection
296 if proj is not None:
297 inverse = proj.Inverse
298 else:
299 inverse = None
300
301 scale = self.scale
302 offx, offy = self.offset
303 xs = []
304 ys = []
305 x, y, width, height = self.region
306 for winx, winy in ((x, y), (x + width, y),
307 (x + width, y + height), (x, y + height)):
308 px = (winx - offx) / scale
309 py = -(winy - offy) / scale
310 if inverse:
311 px, py = inverse(px, py)
312 xs.append(px)
313 ys.append(py)
314 left = min(xs)
315 right = max(xs)
316 top = max(ys)
317 bottom = min(ys)
318
319 return layer.ShapesInRegion((left, bottom, right, top))
320
321
322 class ExportRenderer(ScreenRenderer):
323
324 honor_visibility = 1
325
326 def __init__(self, *args, **kw):
327 """Initialize the ExportRenderer.
328
329 In addition to all parameters of the the ScreenRender
330 constructor, this class requires and additional keyword argument
331 destination_region with a tuple (minx, miny, maxx, maxy) giving
332 the region in dc coordinates which is to contain the map.
333 """
334 self.destination_region = kw["destination_region"]
335 del kw["destination_region"]
336 ScreenRenderer.__init__(self, *args, **kw)
337
338 def RenderMap(self, selected_layer, selected_shapes):
339 """Render the map.
340
341 The rendering device has been specified during initialisation.
342 The device border distance was set in
343 Thuban.UI.viewport.output_transform().
344
345 RenderMap renders a frame set (one page frame, one around
346 legend/scalebar and one around the map), the map, the legend and
347 the scalebar on the given DC. The map is rendered with the
348 region displayed in the canvas view, centered on the area
349 available for map display.
350 """
351
352 self.selected_layer = selected_layer
353 self.selected_shapes = selected_shapes
354
355 # Get some dimensions
356 llx, lly, urx, ury = self.region
357 mminx, mminy, mmaxx, mmaxy = self.destination_region
358
359 # Manipulate the offset to position the map
360 offx, offy = self.offset
361 # 1. Shift to corner of map drawing area
362 offx = offx + mminx
363 offy = offy + mminy
364
365 # 2. Center the map on the map drawing area:
366 # region identifies the region on the canvas view:
367 # center of map drawing area - half the size of region: rendering origin
368 self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
369 self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5
370
371 self.offset = (offx+self.shiftx, offy+self.shifty)
372 self.region = (llx + self.shiftx, lly + self.shifty, urx, ury)
373
374 # Draw the map
375 self.dc.BeginDrawing()
376 self.dc.DestroyClippingRegion()
377 self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
378 urx, ury)
379 self.render_map()
380 self.dc.EndDrawing()
381
382 # Draw the rest (frames, legend, scalebar)
383 self.dc.BeginDrawing()
384 self.dc.DestroyClippingRegion()
385
386 # Force the font for Legend drawing
387 font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
388 self.dc.SetFont(font)
389
390 self.render_frame()
391 self.render_legend()
392 self.render_scalebar()
393 self.dc.EndDrawing()
394
395 def render_frame(self):
396 """Render the frames for map and legend/scalebar."""
397
398 dc = self.dc
399 dc.SetPen(wxBLACK_PEN)
400 dc.SetBrush(wxTRANSPARENT_BRUSH)
401
402 # Dimension stuff
403 width, height = dc.GetSizeTuple()
404 mminx, mminy, mmaxx, mmaxy = self.destination_region
405
406 # Page Frame
407 dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)
408
409 # Map Frame
410 llx, lly, urx, ury = self.region
411 dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)
412
413 # Legend Frame
414 dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)
415
416 dc.DestroyClippingRegion()
417 dc.SetClippingRegion(mmaxx+10,mminy,
418 (width-20) - (mmaxx+10), mmaxy-mminy)
419
420 def render_legend(self):
421 """Render the legend on the Map."""
422
423 previewer = ClassDataPreviewer()
424 dc = self.dc
425 dc.SetPen(wxBLACK_PEN)
426 dc.SetBrush(wxTRANSPARENT_BRUSH)
427
428 # Dimension stuff
429 width, height = dc.GetSizeTuple()
430 mminx, mminy, mmaxx, mmaxy = self.destination_region
431 textwidth, textheight = dc.GetTextExtent("0")
432 iconwidth = textheight
433 iconheight = textheight
434 stepy = textheight+3
435 dx = 10
436 posx = mmaxx + 10 + 5 # 10 pix distance mapframe/legend frame,
437 # 5 pix inside legend frame
438 posy = mminy + 5 # 5 pix inside legend frame
439
440 # Render the legend
441 dc.SetTextForeground(wxBLACK)
442 if self.map.HasLayers():
443 layers = self.map.Layers()[:]
444 layers.reverse()
445 for l in layers:
446 if l.Visible():
447 # Render title
448 dc.DrawText(l.Title(), posx, posy)
449 posy+=stepy
450 if l.HasClassification():
451 # Render classification
452 clazz = l.GetClassification()
453 shapeType = l.ShapeType()
454 for g in clazz:
455 if g.IsVisible():
456 previewer.Draw(dc,
457 wxRect(posx+dx, posy,
458 iconwidth, iconheight),
459 g.GetProperties(), shapeType)
460 dc.DrawText(g.GetDisplayText(),
461 posx+2*dx+iconwidth, posy)
462 posy+=stepy
463
464 def render_scalebar(self):
465 """Render the scalebar."""
466
467 scalebar = ScaleBar(self.map)
468
469 # Dimension stuff
470 width, height = self.dc.GetSizeTuple()
471 mminx, mminy, mmaxx, mmaxy = self.destination_region
472
473 # Render the scalebar
474 scalebar.DrawScaleBar(self.scale, self.dc,
475 (mmaxx+10+5, mmaxy-25),
476 ((width-15-5) - (mmaxx+10+5),20)
477 )
478 # 10 pix between map and legend frame, 5 pix inside legend frame
479 # 25 pix from the legend frame bottom line
480 # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
481 # Height: 20
482
483 class PrinterRenderer(ExportRenderer):
484
485 # Printing as well as Export / Screen display only the visible layer.
486 honor_visibility = 1
487

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26