/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/renderer.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/UI/renderer.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.76  
changed lines
  Added in v.2552

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26