/[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 1165 - (show annotations)
Thu Jun 12 12:42:10 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 18957 byte(s)
(MapRenderer.render_map): Only try to
        optimize if a raster layer is visible. Fixes RTbug #1931.
        Only draw the raster layer if the gdal library is available.
        Addresses RTbug #1877.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26