/[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 2602 - (show annotations)
Mon Apr 25 15:01:48 2005 UTC (19 years, 10 months ago) by joey
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 16934 byte(s)
Added a description according to the CVS log message

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, layer, x,y, data, format = 'BMP'):
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 if mask_data is not None:
172 stream = cStringIO.StringIO(mask_data)
173 mask = wxImageFromStream(stream, raster_format_map[format])
174 mask = wxMask(wxBitmapFromImage(mask, 1))
175 elif alpha_data is not None:
176 stream = cStringIO.StringIO(alpha_data)
177 alpha = wxImageFromStream(stream, raster_format_map[format])
178 alpha = alpha.GetData()[:] # XXX: do we need to copy this?
179
180 #
181 # if we are using the alpha_data then scale down the alpha values
182 # by the layer's opacity using a string translation table
183 #
184 if alpha is not None:
185 lo = layer.Opacity()
186 if lo == 0:
187 return
188 elif lo == 1:
189 a = alpha
190 else:
191 tr = [int(i*lo) for i in range(256)]
192 table = array.array('B', tr).tostring()
193 a = alpha.translate(table)
194
195 image.SetAlphaData(a)
196
197 bitmap = wxBitmapFromImage(image)
198
199 if mask is not None:
200 bitmap.SetMask(mask)
201
202 self.dc.DrawBitmap(bitmap, int(round(x)), int(round(y)), True)
203
204
205 class ScreenRenderer(MapRenderer):
206
207 # On the screen we want to see only visible layers by default
208 honor_visibility = 1
209
210 def RenderMap(self, selected_layer, selected_shapes):
211 """Render the map.
212
213 Only the given region (a tuple in window coordinates as returned
214 by a wxrect's asTuple method) needs to be redrawn. Highlight the
215 shapes given by the ids in selected_shapes in the
216 selected_layer.
217 """
218 self.selected_layer = selected_layer
219 self.selected_shapes = selected_shapes
220 self.render_map()
221
222 def RenderMapIncrementally(self):
223 """Render the map.
224
225 Only the given region (a tuple in window coordinates as returned
226 by a wxrect's asTuple method) needs to be redrawn. Highlight the
227 shapes given by the ids in selected_shapes in the
228 selected_layer.
229 """
230 return self.render_map_incrementally()
231
232 def draw_selection_incrementally(self, layer, selected_shapes):
233 """Draw the selected shapes in a emphasized way (i.e.
234 with a special pen and brush.
235 The drawing is performed incrementally, that means every
236 n shapes, the user can have interactions with the map.
237 n is currently fixed to 500.
238
239 layer -- the layer where the shapes belong to.
240 selected_shapes -- a list of the shape-ids representing the
241 selected shapes for the given layer.
242 """
243 pen = wxPen(wxBLACK, 3, wxSOLID)
244 brush = wxBrush(wxBLACK, wxCROSS_HATCH)
245
246 shapetype = layer.ShapeType()
247 useraw, func, param = self.low_level_renderer(layer)
248 args = (pen, brush)
249
250 # for point shapes we need to find out the properties
251 # to determine the size. Based on table and field,
252 # we can find out the properties for object - see below.
253 if shapetype == SHAPETYPE_POINT:
254 lc = layer.GetClassification()
255 field = layer.GetClassificationColumn()
256 table = layer.ShapeStore().Table()
257
258 count = 0
259 for index in selected_shapes:
260 count += 1
261 shape = layer.Shape(index)
262
263 # Get the size of the specific property for this
264 # point
265 if shapetype == SHAPETYPE_POINT and field is not None:
266 value = table.ReadValue(shape.ShapeID(), field)
267 group = lc.FindGroup(value)
268 size = group.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 = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
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(wxBLACK_PEN)
395 dc.SetBrush(wxTRANSPARENT_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(wxBLACK_PEN)
421 dc.SetBrush(wxTRANSPARENT_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(wxBLACK)
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 wxRect(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