/[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 1343 - (show annotations)
Tue Jul 1 16:11:08 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: 19295 byte(s)
Fixes RTbug #1971.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26