/[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 1419 - (show annotations)
Tue Jul 15 09:29:18 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/renderer.py
File MIME type: text/x-python
File size: 19125 byte(s)
(MapRenderer.draw_shape_layer): Rework the
draw_func handling a bit to remove one layer of indirection. This
makes the renderer about 10% faster in the non-classifying case
and the code a bit cleaner
(MapRenderer.draw_point_shape): Add the pen and brush parameters
and set them in the dc. Now the draw_point_shape method and
wxproj's draw_polygon_shape function have basically the same
signature so that both can be directly used as draw_func

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 if self.scale == 0:
79 return
80
81 #
82 # This is only a good optimization if there is only one
83 # raster layer and the image covers the entire window (as
84 # it currently does). We note if there is a raster layer
85 # and only begin drawing layers once we have drawn it.
86 # That way we avoid drawing layers that won't be seen.
87 #
88 if Thuban.Model.resource.has_gdal_support():
89 for layer in map.Layers():
90 if isinstance(layer, RasterLayer) and layer.Visible():
91 seenRaster = False
92 break
93
94 for layer in map.Layers():
95 # if honor_visibility is true, only draw visible layers,
96 # otherwise draw all layers
97 if not self.honor_visibility or layer.Visible():
98 if isinstance(layer, Layer) and seenRaster:
99 self.draw_shape_layer(layer)
100 elif isinstance(layer, RasterLayer) \
101 and Thuban.Model.resource.has_gdal_support():
102 self.draw_raster_layer(layer)
103 seenRaster = True
104
105 self.draw_label_layer(map.LabelLayer())
106
107 def draw_shape_layer(self, layer):
108 scale = self.scale
109 offx, offy = self.offset
110
111 map_proj = self.map.projection
112 layer_proj = layer.projection
113
114 shapetype = layer.ShapeType()
115
116 brush = wxTRANSPARENT_BRUSH
117 pen = wxTRANSPARENT_PEN
118
119 old_prop = None
120 old_group = None
121 lc = layer.GetClassification()
122 field = lc.GetField()
123 defaultGroup = lc.GetDefaultGroup()
124
125
126 if shapetype == SHAPETYPE_POINT:
127 draw_func = self.draw_point_shape
128 draw_func_param = layer
129 else:
130 draw_func = draw_polygon_shape
131 draw_func_param = self.polygon_render_param(layer)
132
133 table = layer.ShapeStore().Table()
134 for i in self.layer_ids(layer):
135
136 if field is None:
137 group = defaultGroup
138 else:
139 record = table.ReadRowAsDict(i)
140 assert record is not None
141 group = lc.FindGroup(record[field])
142
143
144 if not group.IsVisible():
145 continue
146
147
148 # don't recreate new objects if they are the same as before
149 if group is not old_group:
150 old_group = group
151
152 prop = group.GetProperties()
153
154 if prop != old_prop:
155 old_prop = prop
156
157 if shapetype == SHAPETYPE_ARC:
158 fill = Transparent
159 else:
160 fill = prop.GetFill()
161
162
163 if fill is Transparent:
164 brush = wxTRANSPARENT_BRUSH
165 else:
166 color = Color2wxColour(fill)
167 brush = wxBrush(color, wxSOLID)
168
169 stroke = prop.GetLineColor()
170 stroke_width = prop.GetLineWidth()
171 if stroke is Transparent:
172 pen = wxTRANSPARENT_PEN
173 else:
174 color = Color2wxColour(stroke)
175 pen = wxPen(color, stroke_width, wxSOLID)
176
177 draw_func(draw_func_param, i, pen, brush)
178
179 def draw_raster_layer(self, layer):
180 data = None
181 offx, offy = self.offset
182 width, height = self.dc.GetSizeTuple()
183
184 inProj = ""
185 proj = layer.GetProjection()
186 if proj is not None:
187 for p in proj.GetAllParameters():
188 inProj += "+" + p + " "
189
190 outProj = ""
191 proj = self.map.GetProjection()
192 if proj is not None:
193 for p in proj.GetAllParameters():
194 outProj += "+" + p + " "
195
196 xmin = (0 - offx) / self.scale
197 ymin = (offy - height) / self.scale
198 xmax = (width - offx) / self.scale
199 ymax = (offy - 0) / self.scale
200
201 try:
202 data = ProjectRasterFile(
203 layer.GetImageFilename(),
204 inProj,
205 outProj,
206 (xmin, ymin, xmax, ymax),
207 "", (width, height))
208 except IOError, (strerr):
209 print strerr
210 except (AttributeError, ValueError):
211 pass
212 else:
213 if data is not None:
214 stream = cStringIO.StringIO(data)
215 image = wxImageFromStream(stream, wxBITMAP_TYPE_BMP)
216 bitmap = wxBitmapFromImage(image)
217 self.dc.BeginDrawing()
218 self.dc.DrawBitmap(bitmap, 0, 0)
219 self.dc.EndDrawing()
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 = ()
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
366 def layer_ids(self, layer):
367 """Return the shapeids covered by the region that has to be redrawn
368
369 Call the layer's ShapesInRegion method to determine the ids so
370 that it can use the quadtree.
371 """
372 # FIXME: the quad-tree should be built from the projected
373 # coordinates not the lat-long ones because it's not trivial to
374 # determine an appropriate rectangle in lat-long for a given
375 # rectangle in projected coordinates which we have to start from
376 # here.
377 proj = self.map.projection
378 if proj is not None:
379 inverse = proj.Inverse
380 else:
381 inverse = None
382
383 scale = self.scale
384 offx, offy = self.offset
385 xs = []
386 ys = []
387 x, y, width, height = self.update_region
388 for winx, winy in ((x, y), (x + width, y),
389 (x + width, y + height), (x, y + height)):
390 px = (winx - offx) / scale
391 py = -(winy - offy) / scale
392 if inverse:
393 px, py = inverse(px, py)
394 xs.append(px)
395 ys.append(py)
396 left = min(xs)
397 right = max(xs)
398 top = max(ys)
399 bottom = min(ys)
400
401 return layer.ShapesInRegion((left, bottom, right, top))
402
403
404 class ExportRenderer(ScreenRenderer):
405
406 honor_visibility = 1
407
408 def RenderMap(self, map, region, mapregion,
409 selected_layer, selected_shapes ):
410 """Render the map.
411
412 The rendering device has been specified during initialisation.
413 The device border distance was set in Thuban.UI.view.OutputTranform().
414
415 RenderMap renders a frame set (one page frame, one around
416 legend/scalebar and one around the map), the map, the legend and the
417 scalebar on the given DC. The map is rendered with the region displayed
418 in the canvas view, centered on the area available for map display.
419 """
420
421 self.update_region = region
422 self.selected_layer = selected_layer
423 self.selected_shapes = selected_shapes
424
425 # Get some dimensions
426 llx, lly, urx, ury = region
427 self.mapregion = mapregion
428 mminx, mminy, mmaxx, mmaxy = self.mapregion
429
430 # Manipulate the offset to position the map
431 offx, offy = self.offset
432 # 1. Shift to corner of map drawing area
433 offx = offx + mminx
434 offy = offy + mminy
435
436 # 2. Center the map on the map drawing area:
437 # region identifies the region on the canvas view:
438 # center of map drawing area - half the size of region: rendering origin
439 self.shiftx = (mmaxx - mminx)*0.5 - (urx - llx)*0.5
440 self.shifty = (mmaxy - mminy)*0.5 - (ury - lly)*0.5
441
442 self.offset = (offx+self.shiftx, offy+self.shifty)
443
444 # Draw the map
445 self.dc.BeginDrawing()
446 self.dc.DestroyClippingRegion()
447 self.dc.SetClippingRegion(mminx+self.shiftx, mminy+self.shifty,
448 urx, ury)
449 self.render_map(map)
450 self.dc.EndDrawing()
451
452 # Draw the rest (frames, legend, scalebar)
453 self.dc.BeginDrawing()
454 self.dc.DestroyClippingRegion()
455
456 # Force the font for Legend drawing
457 font = wxFont(self.resolution * 10, wxSWISS, wxNORMAL, wxNORMAL)
458 self.dc.SetFont(font)
459
460 self.render_frame(region)
461 self.render_legend(map)
462 self.render_scalebar(map)
463 self.dc.EndDrawing()
464
465 def render_frame(self, region):
466 """Render the frames for map and legend/scalebar."""
467
468 dc = self.dc
469 dc.SetPen(wxBLACK_PEN)
470 dc.SetBrush(wxTRANSPARENT_BRUSH)
471
472 # Dimension stuff
473 width, height = dc.GetSizeTuple()
474 mminx, mminy, mmaxx, mmaxy = self.mapregion
475
476 # Page Frame
477 dc.DrawRectangle(15,15,width-30, (mmaxy-mminy)+10)
478
479 # Map Frame
480 llx, lly, urx, ury = region
481 dc.DrawRectangle(mminx + self.shiftx, mminy + self.shifty, urx, ury)
482
483 # Legend Frame
484 dc.DrawRectangle(mmaxx+10,mminy,(width-20) - (mmaxx+10), mmaxy-mminy)
485
486 dc.DestroyClippingRegion()
487 dc.SetClippingRegion(mmaxx+10,mminy,
488 (width-20) - (mmaxx+10), mmaxy-mminy)
489
490 def render_legend(self, map):
491 """Render the legend on the Map."""
492
493 previewer = ClassDataPreviewer()
494 dc = self.dc
495 dc.SetPen(wxBLACK_PEN)
496 dc.SetBrush(wxTRANSPARENT_BRUSH)
497
498 # Dimension stuff
499 width, height = dc.GetSizeTuple()
500 mminx, mminy, mmaxx, mmaxy = self.mapregion
501 textwidth, textheight = dc.GetTextExtent("0")
502 iconwidth = textheight
503 iconheight = textheight
504 stepy = textheight+3
505 dx = 10
506 posx = mmaxx + 10 + 5 # 10 pix distance mapframe/legend frame,
507 # 5 pix inside legend frame
508 posy = mminy + 5 # 5 pix inside legend frame
509
510 # Render the legend
511 dc.SetTextForeground(wxBLACK)
512 if map.HasLayers():
513 layers = map.Layers()
514 layers.reverse()
515 for l in layers:
516 if l.Visible():
517 # Render title
518 dc.DrawText(l.Title(), posx, posy)
519 posy+=stepy
520 if l.HasClassification():
521 # Render classification
522 clazz = l.GetClassification()
523 shapeType = l.ShapeType()
524 for g in clazz:
525 if g.IsVisible():
526 previewer.Draw(dc,
527 wxRect(posx+dx, posy,
528 iconwidth, iconheight),
529 g.GetProperties(), shapeType)
530 dc.DrawText(g.GetDisplayText(),
531 posx+2*dx+iconwidth, posy)
532 posy+=stepy
533
534 def render_scalebar(self, map):
535 """Render the scalebar."""
536
537 scalebar = ScaleBar(map)
538
539 # Dimension stuff
540 width, height = self.dc.GetSizeTuple()
541 mminx, mminy, mmaxx, mmaxy = self.mapregion
542
543 # Render the scalebar
544 scalebar.DrawScaleBar(self.scale, self.dc,
545 (mmaxx+10+5, mmaxy-25),
546 ((width-15-5) - (mmaxx+10+5),20)
547 )
548 # 10 pix between map and legend frame, 5 pix inside legend frame
549 # 25 pix from the legend frame bottom line
550 # Width: 15 pix from DC border, 5 pix inside frame, 10, 5 as above
551 # Height: 20
552
553 class PrinterRenderer(ExportRenderer):
554
555 # Printing as well as Export / Screen display only the visible layer.
556 honor_visibility = 1
557

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26