/[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 2562 - (show annotations)
Wed Feb 16 21:14:47 2005 UTC (20 years ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 15011 byte(s)
Further wxPython 2.5 changes using patches from Daniel Calvelo Aros
so that that wxproj doesn't crash. Added GUI support for selecting
alpha channel (opacity can't be selected yet).

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26