/[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 2601 - (show annotations)
Sat Apr 23 16:40:33 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: 16748 byte(s)
Added a missing import

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
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, layer, x,y, data, format = 'BMP'):
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 = wxEmptyImage(width, height)
155 image.SetData(image_data)
156 if mask_data is not None:
157 mask = wxBitmapFromBits(mask_data, width, height, 1)
158 mask = wxMask(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 = wxImageFromStream(stream, raster_format_map[format])
166 if mask_data is not None:
167 stream = cStringIO.StringIO(mask_data)
168 mask = wxImageFromStream(stream, raster_format_map[format])
169 mask = wxMask(wxBitmapFromImage(mask, 1))
170 elif alpha_data is not None:
171 stream = cStringIO.StringIO(alpha_data)
172 alpha = wxImageFromStream(stream, raster_format_map[format])
173 alpha = alpha.GetData()[:] # XXX: do we need to copy this?
174
175 #
176 # if we are using the alpha_data then scale down the alpha values
177 # by the layer's opacity using a string translation table
178 #
179 if alpha is not None:
180 lo = layer.Opacity()
181 if lo == 0:
182 return
183 elif lo == 1:
184 a = alpha
185 else:
186 tr = [int(i*lo) for i in range(256)]
187 table = array.array('B', tr).tostring()
188 a = alpha.translate(table)
189
190 image.SetAlphaData(a)
191
192 bitmap = wxBitmapFromImage(image)
193
194 if mask is not None:
195 bitmap.SetMask(mask)
196
197 self.dc.DrawBitmap(bitmap, int(round(x)), int(round(y)), True)
198
199
200 class ScreenRenderer(MapRenderer):
201
202 # On the screen we want to see only visible layers by default
203 honor_visibility = 1
204
205 def RenderMap(self, selected_layer, selected_shapes):
206 """Render the map.
207
208 Only the given region (a tuple in window coordinates as returned
209 by a wxrect's asTuple method) needs to be redrawn. Highlight the
210 shapes given by the ids in selected_shapes in the
211 selected_layer.
212 """
213 self.selected_layer = selected_layer
214 self.selected_shapes = selected_shapes
215 self.render_map()
216
217 def RenderMapIncrementally(self):
218 """Render the map.
219
220 Only the given region (a tuple in window coordinates as returned
221 by a wxrect's asTuple method) needs to be redrawn. Highlight the
222 shapes given by the ids in selected_shapes in the
223 selected_layer.
224 """
225 return self.render_map_incrementally()
226
227 def draw_selection_incrementally(self, layer, selected_shapes):
228 """Draw the selected shapes in a emphasized way (i.e.
229 with a special pen and brush.
230 The drawing is performed incrementally, that means every
231 n shapes, the user can have interactions with the map.
232 n is currently fixed to 500.
233
234 layer -- the layer where the shapes belong to.
235 selected_shapes -- a list of the shape-ids representing the
236 selected shapes for the given layer.
237 """
238 pen = wxPen(wxBLACK, 3, wxSOLID)
239 brush = wxBrush(wxBLACK, wxCROSS_HATCH)
240
241 shapetype = layer.ShapeType()
242 useraw, func, param = self.low_level_renderer(layer)
243 args = (pen, brush)
244
245 # for point shapes we need to find out the properties
246 # to determine the size. Based on table and field,
247 # we can find out the properties for object - see below.
248 if shapetype == SHAPETYPE_POINT:
249 lc = layer.GetClassification()
250 field = layer.GetClassificationColumn()
251 table = layer.ShapeStore().Table()
252
253 count = 0
254 for index in selected_shapes:
255 count += 1
256 shape = layer.Shape(index)
257
258 # Get the size of the specific property for this
259 # point
260 if shapetype == SHAPETYPE_POINT and field is not None:
261 value = table.ReadValue(shape.ShapeID(), field)
262 group = lc.FindGroup(value)
263 size = group.GetProperties().GetSize()
264 args = (pen, brush, size)
265
266 if useraw:
267 data = shape.RawData()
268 else:
269 data = shape.Points()
270 func(param, data, *args)
271 if count % 500 == 0:
272 yield True
273
274 def layer_shapes(self, layer):
275 """Return the shapeids covered by the region that has to be redrawn
276
277 Call the layer's ShapesInRegion method to determine the ids so
278 that it can use the quadtree.
279 """
280 # FIXME: the quad-tree should be built from the projected
281 # coordinates not the lat-long ones because it's not trivial to
282 # determine an appropriate rectangle in lat-long for a given
283 # rectangle in projected coordinates which we have to start from
284 # here.
285 proj = self.map.projection
286 if proj is not None:
287 inverse = proj.Inverse
288 else:
289 inverse = None
290
291 scale = self.scale
292 offx, offy = self.offset
293 xs = []
294 ys = []
295 x, y, width, height = self.region
296 for winx, winy in ((x, y), (x + width, y),
297 (x + width, y + height), (x, y + height)):
298 px = (winx - offx) / scale
299 py = -(winy - offy) / scale
300 if inverse:
301 px, py = inverse(px, py)
302 xs.append(px)
303 ys.append(py)
304 left = min(xs)
305 right = max(xs)
306 top = max(ys)
307 bottom = min(ys)
308
309 return layer.ShapesInRegion((left, bottom, right, top))
310
311
312 class ExportRenderer(ScreenRenderer):
313
314 honor_visibility = 1
315
316 def __init__(self, *args, **kw):
317 """Initialize the ExportRenderer.
318
319 In addition to all parameters of the the ScreenRender
320 constructor, this class requires and additional keyword argument
321 destination_region with a tuple (minx, miny, maxx, maxy) giving
322 the region in dc coordinates which is to contain the map.
323 """
324 self.destination_region = kw["destination_region"]
325 del kw["destination_region"]
326 ScreenRenderer.__init__(self, *args, **kw)
327
328 def RenderMap(self, selected_layer, selected_shapes):
329 """Render the map.
330
331 The rendering device has been specified during initialisation.
332 The device border distance was set in
333 Thuban.UI.viewport.output_transform().
334
335 RenderMap renders a frame set (one page frame, one around
336 legend/scalebar and one around the map), the map, the legend and
337 the scalebar on the given DC. The map is rendered with the
338 region displayed in the canvas view, centered on the area
339 available for map display.
340 """
341
342 self.selected_layer = selected_layer
343 self.selected_shapes = selected_shapes
344
345 # Get some dimensions
346 llx, lly, urx, ury = self.region
347 mminx, mminy, mmaxx, mmaxy = self.destination_region
348
349 # Manipulate the offset to position the map
350 offx, offy = self.offset
351 # 1. Shift to corner of map drawing area
352 offx = offx + mminx
353 offy = offy + mminy
354
355 # 2. Center the map on the map drawing area:
356 # region identifies the region on the canvas view:
357 # center of map drawing area - half the size of region: rendering origin
358 self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
359 self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5
360
361 self.offset = (offx+self.shiftx, offy+self.shifty)
362 self.region = (llx + self.shiftx, lly + self.shifty, urx, ury)
363
364 # Draw the map
365 self.dc.BeginDrawing()
366 self.dc.DestroyClippingRegion()
367 self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
368 urx, ury)
369 self.render_map()
370 self.dc.EndDrawing()
371
372 # Draw the rest (frames, legend, scalebar)
373 self.dc.BeginDrawing()
374 self.dc.DestroyClippingRegion()
375
376 # Force the font for Legend drawing
377 font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
378 self.dc.SetFont(font)
379
380 self.render_frame()
381 self.render_legend()
382 self.render_scalebar()
383 self.dc.EndDrawing()
384
385 def render_frame(self):
386 """Render the frames for map and legend/scalebar."""
387
388 dc = self.dc
389 dc.SetPen(wxBLACK_PEN)
390 dc.SetBrush(wxTRANSPARENT_BRUSH)
391
392 # Dimension stuff
393 width, height = dc.GetSizeTuple()
394 mminx, mminy, mmaxx, mmaxy = self.destination_region
395
396 # Page Frame
397 dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)
398
399 # Map Frame
400 llx, lly, urx, ury = self.region
401 dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)
402
403 # Legend Frame
404 dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)
405
406 dc.DestroyClippingRegion()
407 dc.SetClippingRegion(mmaxx+10,mminy,
408 (width-20) - (mmaxx+10), mmaxy-mminy)
409
410 def render_legend(self):
411 """Render the legend on the Map."""
412
413 previewer = ClassDataPreviewer()
414 dc = self.dc
415 dc.SetPen(wxBLACK_PEN)
416 dc.SetBrush(wxTRANSPARENT_BRUSH)
417
418 # Dimension stuff
419 width, height = dc.GetSizeTuple()
420 mminx, mminy, mmaxx, mmaxy = self.destination_region
421 textwidth, textheight = dc.GetTextExtent("0")
422 iconwidth = textheight
423 iconheight = textheight
424 stepy = textheight+3
425 dx = 10
426 posx = mmaxx + 10 + 5 # 10 pix distance mapframe/legend frame,
427 # 5 pix inside legend frame
428 posy = mminy + 5 # 5 pix inside legend frame
429
430 # Render the legend
431 dc.SetTextForeground(wxBLACK)
432 if self.map.HasLayers():
433 layers = self.map.Layers()[:]
434 layers.reverse()
435 for l in layers:
436 if l.Visible():
437 # Render title
438 dc.DrawText(l.Title(), posx, posy)
439 posy+=stepy
440 if l.HasClassification():
441 # Render classification
442 clazz = l.GetClassification()
443 shapeType = l.ShapeType()
444 for g in clazz:
445 if g.IsVisible():
446 previewer.Draw(dc,
447 wxRect(posx+dx, posy,
448 iconwidth, iconheight),
449 g.GetProperties(), shapeType)
450 dc.DrawText(g.GetDisplayText(),
451 posx+2*dx+iconwidth, posy)
452 posy+=stepy
453
454 def render_scalebar(self):
455 """Render the scalebar."""
456
457 scalebar = ScaleBar(self.map)
458
459 # Dimension stuff
460 width, height = self.dc.GetSizeTuple()
461 mminx, mminy, mmaxx, mmaxy = self.destination_region
462
463 # Render the scalebar
464 scalebar.DrawScaleBar(self.scale, self.dc,
465 (mmaxx+10+5, mmaxy-25),
466 ((width-15-5) - (mmaxx+10+5),20)
467 )
468 # 10 pix between map and legend frame, 5 pix inside legend frame
469 # 25 pix from the legend frame bottom line
470 # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
471 # Height: 20
472
473 class PrinterRenderer(ExportRenderer):
474
475 # Printing as well as Export / Screen display only the visible layer.
476 honor_visibility = 1
477

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26