/[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 1467 - (show annotations)
Tue Jul 22 14:02:57 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 19091 byte(s)
(MapRenderer.render_map): Wrap method
        with Begin/EndDrawing() calls to ensure we aren't doing to
        many updates to the dc during rendering.
(ScreenRenderer.draw_shape_layer): self.draw_point_shape takes
        a pen and brush argument so they need to be passed to the function.

1 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jonathan Coles <[email protected]>
5 # Frank Koormann <[email protected]>
6 #
7 # This program is free software under the GPL (>=v2)
8 # Read the file COPYING coming with Thuban for details.
9
10 __version__ = "$Revision$"
11
12 import cStringIO
13
14 from Thuban import _
15
16 from wxPython.wx import wxMemoryDC, wxEmptyBitmap, \
17 wxPoint, wxRect, wxPen, wxBrush, wxFont, \
18 wxTRANSPARENT_PEN, wxTRANSPARENT_BRUSH, \
19 wxBLACK_PEN, wxRED_PEN, wxBLACK, \
20 wxSOLID, wxCROSS_HATCH, wxSWISS, wxNORMAL, \
21 wxBitmap, wxImageFromBitmap, wxBitmapFromImage, \
22 wxImageFromStream, wxBITMAP_TYPE_BMP
23
24 from wxproj import draw_polygon_shape, draw_polygon_init
25
26 from Thuban.UI.common import Color2wxColour
27 from Thuban.UI.classifier import ClassDataPreviewer
28 from Thuban.UI.scalebar import ScaleBar
29
30 from Thuban.Model.layer import Layer, RasterLayer, \
31 SHAPETYPE_POLYGON, SHAPETYPE_ARC, SHAPETYPE_POINT
32 from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
33 ALIGN_LEFT, ALIGN_RIGHT, ALIGN_BASELINE
34
35 from Thuban.Model.classification import Classification
36 from Thuban.Model.color import Transparent
37 import Thuban.Model.resource
38
39 if Thuban.Model.resource.has_gdal_support():
40 from gdalwarp import ProjectRasterFile
41
42 class MapRenderer:
43
44 """Class to render a map onto a wxDC"""
45
46 honor_visibility = 1
47
48 def __init__(self, dc, scale, offset, resolution = 72.0,
49 honor_visibility = None):
50 """Inititalize the renderer.
51
52 dc -- the wxPython DC to render on
53 scale, offset -- the scale factor and translation to convert
54 between projected coordinates and the DC coordinates
55
56 resolution -- the assumed resolution of the DC. Used to convert
57 absolute lengths like font sizes to DC coordinates
58
59 honor_visibility -- boolean. If true, honor the visibility flag
60 of the layers, otherwise draw all layers. If None, use
61 the renderer's default.
62 """
63 # resolution in pixel/inch
64
65 self.dc = dc
66 self.scale = scale
67 self.offset = offset
68 if honor_visibility is not None:
69 self.honor_visibility = honor_visibility
70 # store the resolution in pixel/point because it's more useful
71 # later.
72 self.resolution = resolution / 72.0
73
74 def render_map(self, map):
75 self.map = map
76 seenRaster = True
77
78 self.dc.BeginDrawing()
79
80 #
81 # This is only a good optimization if there is only one
82 # raster layer and the image covers the entire window (as
83 # it currently does). We note if there is a raster layer
84 # and only begin drawing layers once we have drawn it.
85 # That way we avoid drawing layers that won't be seen.
86 #
87 if Thuban.Model.resource.has_gdal_support():
88 for layer in map.Layers():
89 if isinstance(layer, RasterLayer) and layer.Visible():
90 seenRaster = False
91 break
92
93 for layer in map.Layers():
94 # if honor_visibility is true, only draw visible layers,
95 # otherwise draw all layers
96 if not self.honor_visibility or layer.Visible():
97 if isinstance(layer, Layer) and seenRaster:
98 self.draw_shape_layer(layer)
99 elif isinstance(layer, RasterLayer) \
100 and Thuban.Model.resource.has_gdal_support():
101 self.draw_raster_layer(layer)
102 seenRaster = True
103
104 self.draw_label_layer(map.LabelLayer())
105
106 self.dc.EndDrawing()
107
108 def draw_shape_layer(self, layer):
109 scale = self.scale
110 offx, offy = self.offset
111
112 map_proj = self.map.projection
113 layer_proj = layer.projection
114
115 shapetype = layer.ShapeType()
116
117 brush = wxTRANSPARENT_BRUSH
118 pen = wxTRANSPARENT_PEN
119
120 old_prop = None
121 old_group = None
122 lc = layer.GetClassification()
123 field = layer.GetClassificationColumn()
124 defaultGroup = lc.GetDefaultGroup()
125
126
127
128 if shapetype == SHAPETYPE_POINT:
129 draw_func = self.draw_point_shape
130 draw_func_param = layer
131 else:
132 draw_func = draw_polygon_shape
133 draw_func_param = self.polygon_render_param(layer)
134
135 table = layer.ShapeStore().Table()
136 for i in self.layer_ids(layer):
137
138 if field is None:
139 group = defaultGroup
140 else:
141 record = table.ReadRowAsDict(i)
142 assert record is not None
143 group = lc.FindGroup(record[field])
144
145
146 if not group.IsVisible():
147 continue
148
149
150 # don't recreate new objects if they are the same as before
151 if group is not old_group:
152 old_group = group
153
154 prop = group.GetProperties()
155
156 if prop != old_prop:
157 old_prop = prop
158
159 if shapetype == SHAPETYPE_ARC:
160 fill = Transparent
161 else:
162 fill = prop.GetFill()
163
164
165 if fill is Transparent:
166 brush = wxTRANSPARENT_BRUSH
167 else:
168 color = Color2wxColour(fill)
169 brush = wxBrush(color, wxSOLID)
170
171 stroke = prop.GetLineColor()
172 stroke_width = prop.GetLineWidth()
173 if stroke is Transparent:
174 pen = wxTRANSPARENT_PEN
175 else:
176 color = Color2wxColour(stroke)
177 pen = wxPen(color, stroke_width, wxSOLID)
178
179 draw_func(draw_func_param, i, pen, brush)
180
181 def draw_raster_layer(self, layer):
182 data = None
183 offx, offy = self.offset
184 width, height = self.dc.GetSizeTuple()
185
186 inProj = ""
187 proj = layer.GetProjection()
188 if proj is not None:
189 for p in proj.GetAllParameters():
190 inProj += "+" + p + " "
191
192 outProj = ""
193 proj = self.map.GetProjection()
194 if proj is not None:
195 for p in proj.GetAllParameters():
196 outProj += "+" + p + " "
197
198 xmin = (0 - offx) / self.scale
199 ymin = (offy - height) / self.scale
200 xmax = (width - offx) / self.scale
201 ymax = (offy - 0) / self.scale
202
203 try:
204 data = ProjectRasterFile(
205 layer.GetImageFilename(),
206 inProj,
207 outProj,
208 (xmin, ymin, xmax, ymax),
209 "", (width, height))
210 except IOError, (strerr):
211 print strerr
212 except (AttributeError, ValueError):
213 pass
214 else:
215 if data is not None:
216 stream = cStringIO.StringIO(data)
217 image = wxImageFromStream(stream, wxBITMAP_TYPE_BMP)
218 bitmap = wxBitmapFromImage(image)
219 self.dc.DrawBitmap(bitmap, 0, 0)
220
221 def layer_ids(self, layer):
222 """Return the shape ids of the given layer that have to be drawn.
223
224 The default implementation simply returns all ids in the layer.
225 Override in derived classes to be more precise.
226 """
227 return range(layer.NumShapes())
228
229 def polygon_render_param(self, layer):
230 """Return the low-lever render parameter for the layer"""
231 offx, offy = self.offset
232 return draw_polygon_init(layer.ShapeStore().Shapefile(), self.dc,
233 self.map.projection,
234 layer.projection,
235 self.scale, -self.scale,
236 offx, offy)
237
238 def draw_polygon_shape(self, draw_polygon_info, index, pen, brush):
239 draw_polygon_shape(draw_polygon_info, index, pen, brush)
240
241 def projected_points(self, layer, index):
242 proj = self.map.GetProjection()
243 if proj is not None:
244 forward = proj.Forward
245 else:
246 forward = None
247 proj = layer.GetProjection()
248 if proj is not None:
249 inverse = proj.Inverse
250 else:
251 inverse = None
252 shape = layer.Shape(index)
253 points = []
254 scale = self.scale
255 offx, offy = self.offset
256 for x, y in shape.Points():
257 if inverse:
258 x, y = inverse(x, y)
259 if forward:
260 x, y = forward(x, y)
261 points.append(wxPoint(x * scale + offx,
262 -y * scale + offy))
263 return points
264
265 def draw_arc_shape(self, layer, index):
266 points = self.projected_points(layer, index)
267 self.dc.DrawLines(points)
268
269 def draw_point_shape(self, layer, index, pen, brush):
270 pp = self.projected_points(layer, index)
271
272 if len(pp) == 0: return # ignore Null Shapes which have no points
273
274 p = pp[0]
275 radius = self.resolution * 5
276 self.dc.SetBrush(brush)
277 self.dc.SetPen(pen)
278 self.dc.DrawEllipse(p.x - radius, p.y - radius, 2*radius, 2*radius)
279
280 def draw_label_layer(self, layer):
281 scale = self.scale
282 offx, offy = self.offset
283
284 font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
285 self.dc.SetFont(font)
286
287 map_proj = self.map.projection
288 if map_proj is not None:
289 forward = map_proj.Forward
290 else:
291 forward = None
292
293 for label in layer.Labels():
294 x = label.x
295 y = label.y
296 text = label.text
297 if forward:
298 x, y = forward(x, y)
299 x = x * scale + offx
300 y = -y * scale + offy
301 width, height = self.dc.GetTextExtent(text)
302 if label.halign == ALIGN_LEFT:
303 # nothing to be done
304 pass
305 elif label.halign == ALIGN_RIGHT:
306 x = x - width
307 elif label.halign == ALIGN_CENTER:
308 x = x - width/2
309 if label.valign == ALIGN_TOP:
310 # nothing to be done
311 pass
312 elif label.valign == ALIGN_BOTTOM:
313 y = y - height
314 elif label.valign == ALIGN_CENTER:
315 y = y - height/2
316 self.dc.DrawText(text, x, y)
317
318
319 class ScreenRenderer(MapRenderer):
320
321 # On the screen we want to see only visible layers by default
322 honor_visibility = 1
323
324 def RenderMap(self, map, region, selected_layer, selected_shapes):
325 """Render the map.
326
327 Only the given region (a tuple in window coordinates as returned
328 by a wxrect's asTuple method) needs to be redrawn. Highlight the
329 shapes given by the ids in selected_shapes in the
330 selected_layer.
331 """
332 self.update_region = region
333 self.selected_layer = selected_layer
334 self.selected_shapes = selected_shapes
335 self.render_map(map)
336
337 def draw_shape_layer(self, layer):
338 MapRenderer.draw_shape_layer(self, layer)
339 if layer is self.selected_layer and self.selected_shapes:
340 pen = wxPen(wxBLACK, 3, wxSOLID)
341 brush = wxBrush(wxBLACK, wxCROSS_HATCH)
342
343 shapetype = layer.ShapeType()
344 if shapetype == SHAPETYPE_POLYGON:
345 offx, offy = self.offset
346 renderparam = self.polygon_render_param(layer)
347 func = self.draw_polygon_shape
348 args = (pen, brush)
349 elif shapetype == SHAPETYPE_ARC:
350 renderparam = self.polygon_render_param(layer)
351 func = self.draw_polygon_shape
352 args = (pen, None)
353 elif shapetype == SHAPETYPE_POINT:
354 renderparam = layer
355 self.dc.SetBrush(brush)
356 self.dc.SetPen(pen)
357 func = self.draw_point_shape
358 args = (pen, brush)
359 else:
360 raise TypeError(_("Unhandled shape type %s") % shapetype)
361
362 for index in self.selected_shapes:
363 func(renderparam, index, *args)
364
365 def layer_ids(self, layer):
366 """Return the shapeids covered by the region that has to be redrawn
367
368 Call the layer's ShapesInRegion method to determine the ids so
369 that it can use the quadtree.
370 """
371 # FIXME: the quad-tree should be built from the projected
372 # coordinates not the lat-long ones because it's not trivial to
373 # determine an appropriate rectangle in lat-long for a given
374 # rectangle in projected coordinates which we have to start from
375 # here.
376 proj = self.map.projection
377 if proj is not None:
378 inverse = proj.Inverse
379 else:
380 inverse = None
381
382 scale = self.scale
383 offx, offy = self.offset
384 xs = []
385 ys = []
386 x, y, width, height = self.update_region
387 for winx, winy in ((x, y), (x + width, y),
388 (x + width, y + height), (x, y + height)):
389 px = (winx - offx) / scale
390 py = -(winy - offy) / scale
391 if inverse:
392 px, py = inverse(px, py)
393 xs.append(px)
394 ys.append(py)
395 left = min(xs)
396 right = max(xs)
397 top = max(ys)
398 bottom = min(ys)
399
400 return layer.ShapesInRegion((left, bottom, right, top))
401
402
403 class ExportRenderer(ScreenRenderer):
404
405 honor_visibility = 1
406
407 def RenderMap(self, map, region, mapregion,
408 selected_layer, selected_shapes ):
409 """Render the map.
410
411 The rendering device has been specified during initialisation.
412 The device border distance was set in Thuban.UI.view.OutputTranform().
413
414 RenderMap renders a frame set (one page frame, one around
415 legend/scalebar and one around the map), the map, the legend and the
416 scalebar on the given DC. The map is rendered with the region displayed
417 in the canvas view, centered on the area available for map display.
418 """
419
420 self.update_region = region
421 self.selected_layer = selected_layer
422 self.selected_shapes = selected_shapes
423
424 # Get some dimensions
425 llx, lly, urx, ury = region
426 self.mapregion = mapregion
427 mminx, mminy, mmaxx, mmaxy = self.mapregion
428
429 # Manipulate the offset to position the map
430 offx, offy = self.offset
431 # 1. Shift to corner of map drawing area
432 offx = offx + mminx
433 offy = offy + mminy
434
435 # 2. Center the map on the map drawing area:
436 # region identifies the region on the canvas view:
437 # center of map drawing area - half the size of region: rendering origin
438 self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
439 self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5
440
441 self.offset = (offx+self.shiftx, offy+self.shifty)
442
443 # Draw the map
444 self.dc.BeginDrawing()
445 self.dc.DestroyClippingRegion()
446 self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
447 urx, ury)
448 self.render_map(map)
449 self.dc.EndDrawing()
450
451 # Draw the rest (frames, legend, scalebar)
452 self.dc.BeginDrawing()
453 self.dc.DestroyClippingRegion()
454
455 # Force the font for Legend drawing
456 font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
457 self.dc.SetFont(font)
458
459 self.render_frame(region)
460 self.render_legend(map)
461 self.render_scalebar(map)
462 self.dc.EndDrawing()
463
464 def render_frame(self, region):
465 """Render the frames for map and legend/scalebar."""
466
467 dc = self.dc
468 dc.SetPen(wxBLACK_PEN)
469 dc.SetBrush(wxTRANSPARENT_BRUSH)
470
471 # Dimension stuff
472 width, height = dc.GetSizeTuple()
473 mminx, mminy, mmaxx, mmaxy = self.mapregion
474
475 # Page Frame
476 dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)
477
478 # Map Frame
479 llx, lly, urx, ury = region
480 dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)
481
482 # Legend Frame
483 dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)
484
485 dc.DestroyClippingRegion()
486 dc.SetClippingRegion(mmaxx+10,mminy,
487 (width-20) - (mmaxx+10), mmaxy-mminy)
488
489 def render_legend(self, map):
490 """Render the legend on the Map."""
491
492 previewer = ClassDataPreviewer()
493 dc = self.dc
494 dc.SetPen(wxBLACK_PEN)
495 dc.SetBrush(wxTRANSPARENT_BRUSH)
496
497 # Dimension stuff
498 width, height = dc.GetSizeTuple()
499 mminx, mminy, mmaxx, mmaxy = self.mapregion
500 textwidth, textheight = dc.GetTextExtent("0")
501 iconwidth = textheight
502 iconheight = textheight
503 stepy = textheight+3
504 dx = 10
505 posx = mmaxx + 10 + 5 # 10 pix distance mapframe/legend frame,
506 # 5 pix inside legend frame
507 posy = mminy + 5 # 5 pix inside legend frame
508
509 # Render the legend
510 dc.SetTextForeground(wxBLACK)
511 if map.HasLayers():
512 layers = map.Layers()
513 layers.reverse()
514 for l in layers:
515 if l.Visible():
516 # Render title
517 dc.DrawText(l.Title(), posx, posy)
518 posy+=stepy
519 if l.HasClassification():
520 # Render classification
521 clazz = l.GetClassification()
522 shapeType = l.ShapeType()
523 for g in clazz:
524 if g.IsVisible():
525 previewer.Draw(dc,
526 wxRect(posx+dx, posy,
527 iconwidth, iconheight),
528 g.GetProperties(), shapeType)
529 dc.DrawText(g.GetDisplayText(),
530 posx+2*dx+iconwidth, posy)
531 posy+=stepy
532
533 def render_scalebar(self, map):
534 """Render the scalebar."""
535
536 scalebar = ScaleBar(map)
537
538 # Dimension stuff
539 width, height = self.dc.GetSizeTuple()
540 mminx, mminy, mmaxx, mmaxy = self.mapregion
541
542 # Render the scalebar
543 scalebar.DrawScaleBar(self.scale, self.dc,
544 (mmaxx+10+5, mmaxy-25),
545 ((width-15-5) - (mmaxx+10+5),20)
546 )
547 # 10 pix between map and legend frame, 5 pix inside legend frame
548 # 25 pix from the legend frame bottom line
549 # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
550 # Height: 20
551
552 class PrinterRenderer(ExportRenderer):
553
554 # Printing as well as Export / Screen display only the visible layer.
555 honor_visibility = 1
556

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26