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

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

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

revision 43 by bh, Fri Sep 7 11:55:51 2001 UTC revision 1271 by jonathan, Fri Jun 20 16:43:04 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4    # Frank Koormann <[email protected]>
5  #  #
6  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
7  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
# Line 11  Classes for display of a map and interac Line 12  Classes for display of a map and interac
12    
13  __version__ = "$Revision$"  __version__ = "$Revision$"
14    
15    from Thuban import _
16    
17    import sys
18    import os.path
19    
20  from math import hypot  from math import hypot
21    
22  from wxPython.wx import wxWindow,\  from wxPython.wx import wxWindow, wxYield,\
23       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
24       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
25         wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \
26         wxImageFromBitmap, wxPlatform
27    
28    # Export related stuff
29    if wxPlatform == '__WXMSW__':
30        from wxPython.wx import wxMetaFileDC
31    from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK
32    
33  from wxPython import wx  from wxPython import wx
34    
35  from wxproj import point_in_polygon_shape, shape_centroid  from wxproj import point_in_polygon_shape, shape_centroid
36    
   
37  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
38       LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED       LAYER_PROJECTION_CHANGED, \
39         MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
40  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
41       SHAPETYPE_POINT       SHAPETYPE_POINT
42  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
43       ALIGN_LEFT, ALIGN_RIGHT       ALIGN_LEFT, ALIGN_RIGHT
44    from Thuban.Lib.connector import Publisher
45    from Thuban.Model.color import Color
46    
47    import resource
48    
49  from renderer import ScreenRenderer, PrinterRender  from selection import Selection
50    from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
51    
52  import labeldialog  import labeldialog
53    
54  from messages import SELECTED_SHAPE  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
55                         SCALE_CHANGED
56    
57    
58  #  #
# Line 127  class ZoomInTool(RectTool): Line 144  class ZoomInTool(RectTool):
144      def MouseUp(self, event):      def MouseUp(self, event):
145          if self.dragging:          if self.dragging:
146              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
147              self.view.FitRectToWindow(self.proj_rect())              sx, sy = self.start
148                cx, cy = self.current
149                if sx == cx or sy == cy:
150                    # Just a mouse click or a degenerate rectangle. Simply
151                    # zoom in by a factor of two
152                    # FIXME: For a click this is the desired behavior but should we
153                    # really do this for degenrate rectagles as well or
154                    # should we ignore them?
155                    self.view.ZoomFactor(2, center = (cx, cy))
156                else:
157                    # A drag. Zoom in to the rectangle
158                    self.view.FitRectToWindow(self.proj_rect())
159    
160    
161  class ZoomOutTool(RectTool):  class ZoomOutTool(RectTool):
162    
163      """The Zoom-Out Tool"""      """The Zoom-Out Tool"""
164        
165      def Name(self):      def Name(self):
166          return "ZoomOutTool"          return "ZoomOutTool"
167    
# Line 142  class ZoomOutTool(RectTool): Line 170  class ZoomOutTool(RectTool):
170              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
171              sx, sy = self.start              sx, sy = self.start
172              cx, cy = self.current              cx, cy = self.current
173              self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),              if sx == cx or sy == cy:
174                                       max(sx, cx), max(sy, cy)))                  # Just a mouse click or a degenerate rectangle. Simply
175                    # zoom out by a factor of two.
176                    # FIXME: For a click this is the desired behavior but should we
177                    # really do this for degenrate rectagles as well or
178                    # should we ignore them?
179                    self.view.ZoomFactor(0.5, center = (cx, cy))
180                else:
181                    # A drag. Zoom out to the rectangle
182                    self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
183                                             max(sx, cx), max(sy, cy)))
184    
185    
186  class PanTool(Tool):  class PanTool(Tool):
# Line 155  class PanTool(Tool): Line 192  class PanTool(Tool):
192    
193      def MouseMove(self, event):      def MouseMove(self, event):
194          if self.dragging:          if self.dragging:
             x0, y0 = self.current  
195              Tool.MouseMove(self, event)              Tool.MouseMove(self, event)
196                sx, sy = self.start
197              x, y = self.current              x, y = self.current
198              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
199    
200                bitmapdc = wx.wxMemoryDC()
201                bitmapdc.SelectObject(self.view.bitmap)
202    
203              dc = self.view.drag_dc              dc = self.view.drag_dc
204              dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
205    
206      def MouseUp(self, event):      def MouseUp(self, event):
207          if self.dragging:          if self.dragging:
# Line 168  class PanTool(Tool): Line 209  class PanTool(Tool):
209              sx, sy = self.start              sx, sy = self.start
210              cx, cy = self.current              cx, cy = self.current
211              self.view.Translate(cx - sx, cy - sy)              self.view.Translate(cx - sx, cy - sy)
212            
213  class IdentifyTool(Tool):  class IdentifyTool(Tool):
214    
215      """The "Identify" Tool"""      """The "Identify" Tool"""
216        
217      def Name(self):      def Name(self):
218          return "IdentifyTool"          return "IdentifyTool"
219    
# Line 191  class LabelTool(Tool): Line 232  class LabelTool(Tool):
232          self.view.LabelShapeAt(event.m_x, event.m_y)          self.view.LabelShapeAt(event.m_x, event.m_y)
233    
234    
   
   
235  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
236    
237      """      """
238      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
239      """      """
240    
241      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
242          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
243            self.canvas = canvas
244          self.map = map          self.map = map
245            self.region = region
246            self.selected_layer = selected_layer
247            self.selected_shapes = selected_shapes
248    
249      def GetPageInfo(self):      def GetPageInfo(self):
250          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 215  class MapPrintout(wx.wxPrintout): Line 258  class MapPrintout(wx.wxPrintout):
258    
259      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
260          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
261          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = OutputTransform(self.canvas.scale,
262          scalex = width / (urx - llx)                                                     self.canvas.offset,
263          scaley = height / (ury - lly)                                                     self.canvas.GetSizeTuple(),
264          scale = min(scalex, scaley)                                                     self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
265          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
266          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          renderer = PrinterRenderer(dc, scale, offset, resolution = resy)
267          renderer.RenderMap(self.map)          x, y, width, height = self.region
268          return wx.true          canvas_scale = self.canvas.scale
269                    renderer.RenderMap(self.map,
270                               (0,0,
271                                    (width/canvas_scale)*scale,
272                                    (height/canvas_scale)*scale),
273                                    mapregion,
274                               self.selected_layer, self.selected_shapes)
275            return True
276    
277  class MapCanvas(wxWindow):  class MapCanvas(wxWindow, Publisher):
278    
279      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
280    
281      def __init__(self, parent, winid, interactor):      # Some messages that can be subscribed/unsubscribed directly through
282        # the MapCanvas come in fact from other objects. This is a dict
283        # mapping those messages to the names of the instance variables they
284        # actually come from. The delegation is implemented in the Subscribe
285        # and Unsubscribe methods
286        delegated_messages = {LAYER_SELECTED: "selection",
287                              SHAPES_SELECTED: "selection"}
288    
289        # Methods delegated to some instance variables. The delegation is
290        # implemented in the __getattr__ method.
291        delegated_methods = {"SelectLayer": "selection",
292                             "SelectShapes": "selection",
293                             "SelectedLayer": "selection",
294                             "HasSelectedLayer": "selection",
295                             "HasSelectedShapes": "selection",
296                             "SelectedShapes": "selection"}
297    
298        def __init__(self, parent, winid):
299          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
300          self.SetBackgroundColour(wxColour(255, 255, 255))          self.SetBackgroundColour(wxColour(255, 255, 255))
301    
302            # the map displayed in this canvas. Set with SetMap()
303          self.map = None          self.map = None
304    
305            # current map projection. should only differ from map.projection
306            # when the map's projection is changing and we need access to the
307            # old projection.
308            self.current_map_proj = None
309    
310            # scale and offset describe the transformation from projected
311            # coordinates to window coordinates.
312          self.scale = 1.0          self.scale = 1.0
313          self.offset = (0, 0)          self.offset = (0, 0)
314    
315            # whether the user is currently dragging the mouse, i.e. moving
316            # the mouse while pressing a mouse button
317          self.dragging = 0          self.dragging = 0
318    
319            # the currently active tool
320          self.tool = None          self.tool = None
321          self.redraw_on_idle = 0  
322            # The current mouse position of the last OnMotion event or None
323            # if the mouse is outside the window.
324            self.current_position = None
325    
326            # the bitmap serving as backing store
327            self.bitmap = None
328    
329            # the selection
330            self.selection = Selection()
331            self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
332    
333            # keep track of which layers/shapes are selected to make sure we
334            # only redraw when necessary
335            self.last_selected_layer = None
336            self.last_selected_shape = None
337    
338            # subscribe the WX events we're interested in
339          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
340          EVT_LEFT_DOWN(self, self.OnLeftDown)          EVT_LEFT_DOWN(self, self.OnLeftDown)
341          EVT_LEFT_UP(self, self.OnLeftUp)          EVT_LEFT_UP(self, self.OnLeftUp)
342          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
343          wx.EVT_IDLE(self, self.OnIdle)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
344          self.interactor = interactor          wx.EVT_SIZE(self, self.OnSize)
345          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)  
346        def __del__(self):
347            wxWindow.__del__(self)
348            Publisher.__del__(self)
349    
350        def Subscribe(self, channel, *args):
351            """Extend the inherited method to handle delegated messages.
352    
353            If channel is one of the delegated messages call the appropriate
354            object's Subscribe method. Otherwise just call the inherited
355            method.
356            """
357            if channel in self.delegated_messages:
358                object = getattr(self, self.delegated_messages[channel])
359                object.Subscribe(channel, *args)
360            else:
361                Publisher.Subscribe(self, channel, *args)
362    
363        def Unsubscribe(self, channel, *args):
364            """Extend the inherited method to handle delegated messages.
365    
366            If channel is one of the delegated messages call the appropriate
367            object's Unsubscribe method. Otherwise just call the inherited
368            method.
369            """
370            if channel in self.delegated_messages:
371                object = getattr(self, self.delegated_messages[channel])
372                object.Unsubscribe(channel, *args)
373            else:
374                Publisher.Unsubscribe(self, channel, *args)
375    
376        def __getattr__(self, attr):
377            if attr in self.delegated_methods:
378                return getattr(getattr(self, self.delegated_methods[attr]), attr)
379            raise AttributeError(attr)
380    
381      def OnPaint(self, event):      def OnPaint(self, event):
382          dc = wxPaintDC(self)          dc = wxPaintDC(self)
383          if self.map is None or not self.map.HasLayers():          clear = self.map is None or not self.map.HasLayers()
384              return  
385          self.redraw_on_idle = 1          wxBeginBusyCursor()
386            wxYield()
387    
388            try:
389                if not clear:
390                    self.do_redraw()
391                    try:
392                        pass
393                    except:
394                        print "Error during drawing:", sys.exc_info()[0]
395                        clear = True
396    
397                if clear:
398                    # If we've got no map or if the map is empty, simply clear
399                    # the screen.
400    
401                    # XXX it's probably possible to get rid of this. The
402                    # background color of the window is already white and the
403                    # only thing we may have to do is to call self.Refresh()
404                    # with a true argument in the right places.
405                    dc.BeginDrawing()
406                    dc.Clear()
407                    dc.EndDrawing()
408            finally:
409                wxEndBusyCursor()
410    
411      def do_redraw(self):      def do_redraw(self):
412            # This should only be called if we have a non-empty map.
413    
414            # Get the window size.
415          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
         bitmap = wx.wxEmptyBitmap(width, height)  
416    
417            # If self.bitmap's still there, reuse it. Otherwise redraw it
418            if self.bitmap is not None:
419                bitmap = self.bitmap
420            else:
421                bitmap = wx.wxEmptyBitmap(width, height)
422                dc = wx.wxMemoryDC()
423                dc.SelectObject(bitmap)
424                dc.BeginDrawing()
425    
426                # clear the background
427                #dc.SetBrush(wx.wxWHITE_BRUSH)
428                #dc.SetPen(wx.wxTRANSPARENT_PEN)
429                #dc.DrawRectangle(0, 0, width, height)
430                dc.SetBackground(wx.wxWHITE_BRUSH)
431                dc.Clear()
432    
433                selected_layer = self.selection.SelectedLayer()
434                selected_shapes = self.selection.SelectedShapes()
435    
436                # draw the map into the bitmap
437                renderer = ScreenRenderer(dc, self.scale, self.offset)
438    
439                # Pass the entire bitmap as update region to the renderer.
440                # We're redrawing the whole bitmap, after all.
441                renderer.RenderMap(self.map, (0, 0, width, height),
442                                   selected_layer, selected_shapes)
443    
444                dc.EndDrawing()
445                dc.SelectObject(wx.wxNullBitmap)
446                self.bitmap = bitmap
447    
448            # blit the bitmap to the screen
449          dc = wx.wxMemoryDC()          dc = wx.wxMemoryDC()
450          dc.SelectObject(bitmap)          dc.SelectObject(bitmap)
   
         dc.BeginDrawing()  
   
         dc.SetBrush(wx.wxWHITE_BRUSH)  
         dc.SetPen(wx.wxTRANSPARENT_PEN)  
         dc.DrawRectangle(0, 0, width, height)  
   
         if 1: #self.interactor.selected_map is self.map:  
             selected_layer = self.interactor.selected_layer  
             selected_shape = self.interactor.selected_shape  
         else:  
             selected_layer = None  
             selected_shape = None  
               
         renderer = ScreenRenderer(dc, self.scale, self.offset)  
         renderer.RenderMap(self.map, selected_layer, selected_shape)  
   
451          clientdc = wxClientDC(self)          clientdc = wxClientDC(self)
452          clientdc.BeginDrawing()          clientdc.BeginDrawing()
453          clientdc.Blit(0, 0, width, height, dc, 0, 0)          clientdc.Blit(0, 0, width, height, dc, 0, 0)
454          clientdc.EndDrawing()          clientdc.EndDrawing()
455    
456        def Export(self):
457            if self.scale == 0:
458                return
459    
460            if hasattr(self, "export_path"):
461                export_path = self.export_path
462            else:
463                export_path="."
464            dlg = wxFileDialog(self, _("Export Map"), export_path, "",
465                               "Enhanced Metafile (*.wmf)|*.wmf",
466                               wxSAVE|wxOVERWRITE_PROMPT)
467            if dlg.ShowModal() == wxID_OK:
468                self.export_path = os.path.dirname(dlg.GetPath())
469                dc = wxMetaFileDC(dlg.GetPath())
470        
471                scale, offset, mapregion = OutputTransform(self.scale,
472                                                           self.offset,
473                                                           self.GetSizeTuple(),
474                                                           dc.GetSizeTuple())
475    
476                selected_layer = self.selection.SelectedLayer()
477                selected_shapes = self.selection.SelectedShapes()
478    
479                renderer = ExportRenderer(dc, scale, offset)
480    
481                # Pass the entire bitmap as update region to the renderer.
482                # We're redrawing the whole bitmap, after all.
483                width, height = self.GetSizeTuple()
484                renderer.RenderMap(self.map,
485                                    (0,0,
486                                        (width/self.scale)*scale,
487                                        (height/self.scale)*scale),
488                                    mapregion,
489                                    selected_layer, selected_shapes)
490                dc.EndDrawing()
491                dc.Close()
492            dlg.Destroy()
493            
494      def Print(self):      def Print(self):
495          printer = wx.wxPrinter()          printer = wx.wxPrinter()
496          printout = MapPrintout(self.map)          width, height = self.GetSizeTuple()
497          printer.Print(self, printout, wx.true)          selected_layer = self.selection.SelectedLayer()
498          printout.Destroy()          selected_shapes = self.selection.SelectedShapes()
499                    
500            printout = MapPrintout(self, self.map, (0, 0, width, height),
501                                   selected_layer, selected_shapes)
502            printer.Print(self, printout, True)
503            printout.Destroy()
504    
505      def SetMap(self, map):      def SetMap(self, map):
506          redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
507                             LAYER_VISIBILITY_CHANGED)                             LAYER_VISIBILITY_CHANGED)
508          if self.map is not None:          if self.map is not None:
509              for channel in redraw_channels:              for channel in redraw_channels:
510                  self.map.Unsubscribe(channel, self.redraw)                  self.map.Unsubscribe(channel, self.full_redraw)
511              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
512                                   self.projection_changed)                                   self.map_projection_changed)
513                self.map.Unsubscribe(LAYER_PROJECTION_CHANGED,
514                                     self.layer_projection_changed)
515          self.map = map          self.map = map
516            self.current_map_proj = self.map.GetProjection()
517            self.selection.ClearSelection()
518          if self.map is not None:          if self.map is not None:
519              for channel in redraw_channels:              for channel in redraw_channels:
520                  self.map.Subscribe(channel, self.redraw)                  self.map.Subscribe(channel, self.full_redraw)
521              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.map_projection_changed)
522                self.map.Subscribe(LAYER_PROJECTION_CHANGED, self.layer_projection_changed)
523          self.FitMapToWindow()          self.FitMapToWindow()
524            # force a redraw. If map is not empty, it's already been called
525            # by FitMapToWindow but if map is empty it hasn't been called
526            # yet so we have to explicitly call it.
527            self.full_redraw()
528    
529      def Map(self):      def Map(self):
530            """Return the map displayed by this canvas"""
531          return self.map          return self.map
532    
533      def redraw(self, *args):      def redraw(self, *args):
534          self.Refresh(0)          self.Refresh(0)
535    
536      def projection_changed(self, *args):      def full_redraw(self, *args):
537          self.FitMapToWindow()          self.bitmap = None
538          self.redraw()          self.redraw()
539    
540        def map_projection_changed(self, *args):
541    
542            proj = self.current_map_proj
543            self.current_map_proj = self.map.GetProjection()
544    
545            bbox = None
546    
547            if proj is not None and self.current_map_proj is not None:
548                width, height = self.GetSizeTuple()
549                llx, lly = self.win_to_proj(0, height)
550                urx, ury = self.win_to_proj(width, 0)
551                bbox = proj.Inverse(llx, lly) + proj.Inverse(urx, ury)
552                bbox = self.current_map_proj.ForwardBBox(bbox)
553    
554            if bbox is not None:
555                self.FitRectToWindow(bbox)
556            else:
557                self.FitMapToWindow()
558    
559            self.full_redraw()
560    
561        def layer_projection_changed(self, *args):
562            self.full_redraw()
563    
564      def set_view_transform(self, scale, offset):      def set_view_transform(self, scale, offset):
565            # width/height of the projected bbox
566            llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()
567            pwidth = float(urx - llx)
568            pheight = float(ury - lly)
569    
570            # width/height of the window
571            wwidth, wheight = self.GetSizeTuple()
572    
573            # The window's center in projected coordinates assuming the new
574            # scale/offset
575            pcenterx = (wwidth/2 - offset[0]) / scale
576            pcentery = (offset[1] - wheight/2) / scale
577    
578            # The window coordinates used when drawing the shapes must fit
579            # into 16bit signed integers.
580            max_len = max(pwidth, pheight)
581            if max_len:
582                max_scale = 32000.0 / max_len
583            else:
584                # FIXME: What to do in this case? The bbox is effectively
585                # empty so any scale should work.
586                max_scale = scale
587    
588            # The minimal scale is somewhat arbitrarily set to half that of
589            # the bbox fit into the window
590            scales = []
591            if pwidth:
592                scales.append(wwidth / pwidth)
593            if pheight:
594                scales.append(wheight / pheight)
595            if scales:
596                min_scale = 0.5 * min(scales)
597            else:
598                min_scale = scale
599    
600            if scale > max_scale:
601                scale = max_scale
602            elif scale < min_scale:
603                scale = min_scale
604    
605          self.scale = scale          self.scale = scale
606          self.offset = offset  
607          self.redraw()          # determine new offset to preserve the center
608            self.offset = (wwidth/2 - scale * pcenterx,
609                           wheight/2 + scale * pcentery)
610            self.full_redraw()
611            self.issue(SCALE_CHANGED, scale)
612    
613      def proj_to_win(self, x, y):      def proj_to_win(self, x, y):
614          """\          """\
615          Return the point in  window coords given by projected coordinates x y          Return the point in  window coords given by projected coordinates x y
616          """          """
617            if self.scale == 0:
618                return (0, 0)
619    
620          offx, offy = self.offset          offx, offy = self.offset
621          return (self.scale * x + offx, -self.scale * y + offy)          return (self.scale * x + offx, -self.scale * y + offy)
622    
# Line 331  class MapCanvas(wxWindow): Line 624  class MapCanvas(wxWindow):
624          """\          """\
625          Return the point in projected coordinates given by window coords x y          Return the point in projected coordinates given by window coords x y
626          """          """
627            if self.scale == 0:
628                return (0, 0)
629    
630          offx, offy = self.offset          offx, offy = self.offset
631          return ((x - offx) / self.scale, (offy - y) / self.scale)          return ((x - offx) / self.scale, (offy - y) / self.scale)
632    
633      def FitRectToWindow(self, rect):      def FitRectToWindow(self, rect):
634            """Fit the rectangular region given by rect into the window.
635    
636            Set scale so that rect (in projected coordinates) just fits into
637            the window and center it.
638            """
639          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
640          llx, lly, urx, ury = rect          llx, lly, urx, ury = rect
641            if llx == urx or lly == ury:
642                # zero width or zero height. Do Nothing
643                return
644          scalex = width / (urx - llx)          scalex = width / (urx - llx)
645          scaley = height / (ury - lly)          scaley = height / (ury - lly)
646          scale = min(scalex, scaley)          scale = min(scalex, scaley)
# Line 345  class MapCanvas(wxWindow): Line 649  class MapCanvas(wxWindow):
649          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
650    
651      def FitMapToWindow(self):      def FitMapToWindow(self):
652          """\          """Fit the map to the window
653          Set the scale and offset so that the map is centered in the  
654          window          Set the scale so that the map fits exactly into the window and
655            center it in the window.
656            """
657            if self.map is not None:
658                bbox = self.map.ProjectedBoundingBox()
659                if bbox is not None:
660                    self.FitRectToWindow(bbox)
661    
662        def FitLayerToWindow(self, layer):
663            """Fit the given layer to the window.
664    
665            Set the scale so that the layer fits exactly into the window and
666            center it in the window.
667          """          """
668          bbox = self.map.ProjectedBoundingBox()          
669            bbox = layer.LatLongBoundingBox()
670          if bbox is not None:          if bbox is not None:
671              self.FitRectToWindow(bbox)              proj = self.map.GetProjection()
672                if proj is not None:
673                    bbox = proj.ForwardBBox(bbox)
674    
675                if bbox is not None:
676                    self.FitRectToWindow(bbox)
677    
678        def FitSelectedToWindow(self):
679            layer = self.selection.SelectedLayer()
680            shapes = self.selection.SelectedShapes()
681    
682      def ZoomFactor(self, factor):          bbox = layer.ShapesBoundingBox(shapes)
683          width, height = self.GetSizeTuple()          if bbox is not None:
684          scale = self.scale * factor              proj = self.map.GetProjection()
685          offx, offy = self.offset              if proj is not None:
686          offset = (factor * (offx - width / 2) + width / 2,                  bbox = proj.ForwardBBox(bbox)
687                    factor * (offy - height / 2) + height / 2)  
688          self.set_view_transform(scale, offset)              if bbox is not None:
689                    if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:
690                        self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))
691                    else:
692                        self.FitRectToWindow(bbox)
693    
694        def ZoomFactor(self, factor, center = None):
695            """Multiply the zoom by factor and center on center.
696    
697            The optional parameter center is a point in window coordinates
698            that should be centered. If it is omitted, it defaults to the
699            center of the window
700            """
701            if self.scale > 0:
702                width, height = self.GetSizeTuple()
703                scale = self.scale * factor
704                offx, offy = self.offset
705                if center is not None:
706                    cx, cy = center
707                else:
708                    cx = width / 2
709                    cy = height / 2
710                offset = (factor * (offx - cx) + width / 2,
711                        factor * (offy - cy) + height / 2)
712                self.set_view_transform(scale, offset)
713    
714      def ZoomOutToRect(self, rect):      def ZoomOutToRect(self, rect):
715          # rect is given in window coordinates          """Zoom out to fit the currently visible region into rect.
716    
717            The rect parameter is given in window coordinates
718            """
719          # determine the bbox of the displayed region in projected          # determine the bbox of the displayed region in projected
720          # coordinates          # coordinates
721          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
# Line 380  class MapCanvas(wxWindow): Line 732  class MapCanvas(wxWindow):
732          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
733    
734      def Translate(self, dx, dy):      def Translate(self, dx, dy):
735            """Move the map by dx, dy pixels"""
736          offx, offy = self.offset          offx, offy = self.offset
737          self.set_view_transform(self.scale, (offx + dx, offy + dy))          self.set_view_transform(self.scale, (offx + dx, offy + dy))
738    
739        def SelectTool(self, tool):
740            """Make tool the active tool.
741    
742            The parameter should be an instance of Tool or None to indicate
743            that no tool is active.
744            """
745            self.tool = tool
746    
747      def ZoomInTool(self):      def ZoomInTool(self):
748          self.tool = ZoomInTool(self)          """Start the zoom in tool"""
749            self.SelectTool(ZoomInTool(self))
750    
751      def ZoomOutTool(self):      def ZoomOutTool(self):
752          self.tool = ZoomOutTool(self)          """Start the zoom out tool"""
753            self.SelectTool(ZoomOutTool(self))
754    
755      def PanTool(self):      def PanTool(self):
756          self.tool = PanTool(self)          """Start the pan tool"""
757            self.SelectTool(PanTool(self))
758            #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
759            #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
760            #print bmp
761            #img = wxImageFromBitmap(bmp)
762            #print img
763            #cur = wxCursor(img)
764            #print cur
765            #self.SetCursor(cur)
766    
767      def IdentifyTool(self):      def IdentifyTool(self):
768          self.tool = IdentifyTool(self)          """Start the identify tool"""
769            self.SelectTool(IdentifyTool(self))
770    
771      def LabelTool(self):      def LabelTool(self):
772          self.tool = LabelTool(self)          """Start the label tool"""
773            self.SelectTool(LabelTool(self))
774    
775      def CurrentTool(self):      def CurrentTool(self):
776            """Return the name of the current tool or None if no tool is active"""
777          return self.tool and self.tool.Name() or None          return self.tool and self.tool.Name() or None
778    
779        def CurrentPosition(self):
780            """Return current position of the mouse in projected coordinates.
781    
782            The result is a 2-tuple of floats with the coordinates. If the
783            mouse is not in the window, the result is None.
784            """
785            if self.current_position is not None:
786                x, y = self.current_position
787                return self.win_to_proj(x, y)
788            else:
789                return None
790    
791        def set_current_position(self, event):
792            """Set the current position from event
793    
794            Should be called by all events that contain mouse positions
795            especially EVT_MOTION. The event paramete may be None to
796            indicate the the pointer left the window.
797            """
798            if event is not None:
799                self.current_position = (event.m_x, event.m_y)
800            else:
801                self.current_position = None
802            self.issue(VIEW_POSITION)
803    
804      def OnLeftDown(self, event):      def OnLeftDown(self, event):
805            self.set_current_position(event)
806          if self.tool is not None:          if self.tool is not None:
807              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
808              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
# Line 410  class MapCanvas(wxWindow): Line 811  class MapCanvas(wxWindow):
811              self.tool.MouseDown(event)              self.tool.MouseDown(event)
812              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
813              self.dragging = 1              self.dragging = 1
814            
815      def OnLeftUp(self, event):      def OnLeftUp(self, event):
816          self.ReleaseMouse()          self.set_current_position(event)
817          if self.dragging:          if self.dragging:
818              self.tool.Hide(self.drag_dc)              self.ReleaseMouse()
819              self.tool.MouseUp(event)              try:
820              self.drag_dc = None                  self.tool.Hide(self.drag_dc)
821          self.dragging = 0                  self.tool.MouseUp(event)
822                finally:
823                    self.drag_dc = None
824                    self.dragging = 0
825    
826      def OnMotion(self, event):      def OnMotion(self, event):
827            self.set_current_position(event)
828          if self.dragging:          if self.dragging:
829              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
830              self.tool.MouseMove(event)              self.tool.MouseMove(event)
831              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
832    
833      def OnIdle(self, event):      def OnLeaveWindow(self, event):
834          if self.redraw_on_idle:          self.set_current_position(None)
835              self.do_redraw()  
836          self.redraw_on_idle = 0      def OnSize(self, event):
837            # the window's size has changed. We have to get a new bitmap. If
838            # we want to be clever we could try to get by without throwing
839            # everything away. E.g. when the window gets smaller, we could
840            # either keep the bitmap or create the new one from the old one.
841            # Even when the window becomes larger some parts of the bitmap
842            # could be reused.
843            self.full_redraw()
844            pass
845    
846      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
847          self.redraw()          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
848            # The selection object takes care that it only issues
849            # SHAPES_SELECTED messages when the set of selected shapes has
850            # actually changed, so we can do a full redraw unconditionally.
851            # FIXME: We should perhaps try to limit the redraw to the are
852            # actually covered by the shapes before and after the selection
853            # change.
854            self.full_redraw()
855    
856        def unprojected_rect_around_point(self, x, y, dist):
857            """return a rect dist pixels around (x, y) in unprojected corrdinates
858    
859            The return value is a tuple (minx, miny, maxx, maxy) suitable a
860            parameter to a layer's ShapesInRegion method.
861            """
862            map_proj = self.map.projection
863            if map_proj is not None:
864                inverse = map_proj.Inverse
865            else:
866                inverse = None
867    
868            xs = []
869            ys = []
870            for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
871                px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
872                if inverse:
873                    px, py = inverse(px, py)
874                xs.append(px)
875                ys.append(py)
876            return (min(xs), min(ys), max(xs), max(ys))
877    
878      def find_shape_at(self, px, py, select_labels = 0, selected_layer = 1):      def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
879          """Determine the shape at point px, py in window coords          """Determine the shape at point px, py in window coords
880    
881          Return the shape and the corresponding layer as a tuple (layer,          Return the shape and the corresponding layer as a tuple (layer,
# Line 443  class MapCanvas(wxWindow): Line 885  class MapCanvas(wxWindow):
885          search through the labels. If a label is found return it's index          search through the labels. If a label is found return it's index
886          as the shape and None as the layer.          as the shape and None as the layer.
887    
888          If the optional parameter selected_layer is true (default), only          If the optional parameter searched_layer is given (or not None
889          search in the currently selected layer.          which it defaults to), only search in that layer.
890          """          """
891          map_proj = self.map.projection          map_proj = self.map.projection
892          if map_proj is not None:          if map_proj is not None:
# Line 453  class MapCanvas(wxWindow): Line 895  class MapCanvas(wxWindow):
895              forward = None              forward = None
896    
897          scale = self.scale          scale = self.scale
898    
899            if scale == 0:
900                return None, None
901    
902          offx, offy = self.offset          offx, offy = self.offset
903    
904          if select_labels:          if select_labels:
905              labels = self.map.LabelLayer().Labels()              labels = self.map.LabelLayer().Labels()
906                
907              if labels:              if labels:
908                  dc = wxClientDC(self)                  dc = wxClientDC(self)
909                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
910                  dc.SetFont(font)                  dc.SetFont(font)
911                  for i in range(len(labels)):                  for i in range(len(labels) - 1, -1, -1):
912                      label = labels[i]                      label = labels[i]
913                      x = label.x                      x = label.x
914                      y = label.y                      y = label.y
# Line 489  class MapCanvas(wxWindow): Line 935  class MapCanvas(wxWindow):
935                      if x <= px < x + width and y <= py <= y + height:                      if x <= px < x + width and y <= py <= y + height:
936                          return None, i                          return None, i
937    
938          if selected_layer:          if searched_layer:
939              layer = self.interactor.SelectedLayer()              layers = [searched_layer]
             if layer is not None:  
                 layers = [layer]  
             else:  
                 # no layer selected. Use an empty list to effectively  
                 # ignore all layers.  
                 layers = []  
940          else:          else:
941              layers = self.map.Layers()              layers = self.map.Layers()
942    
# Line 507  class MapCanvas(wxWindow): Line 947  class MapCanvas(wxWindow):
947              if not layer.Visible():              if not layer.Visible():
948                  continue                  continue
949    
950              filled = layer.fill is not None              filled = layer.GetClassification().GetDefaultFill() \
951              stroked = layer.stroke is not None                       is not Color.Transparent
952                                stroked = layer.GetClassification().GetDefaultLineColor() \
953                          is not Color.Transparent
954    
955              layer_proj = layer.projection              layer_proj = layer.projection
956              if layer_proj is not None:              if layer_proj is not None:
957                  inverse = layer_proj.Inverse                  inverse = layer_proj.Inverse
958              else:              else:
959                  inverse = None                  inverse = None
960                    
961              shapetype = layer.ShapeType()              shapetype = layer.ShapeType()
962    
963              select_shape = -1              select_shape = -1
964    
965                # Determine the ids of the shapes that overlap a tiny area
966                # around the point. For layers containing points we have to
967                # choose a larger size of the box we're testing agains so
968                # that we take the size of the markers into account
969                # FIXME: Once the markers are more flexible this part has to
970                # become more flexible too, of course
971                if shapetype == SHAPETYPE_POINT:
972                    box = self.unprojected_rect_around_point(px, py, 5)
973                else:
974                    box = self.unprojected_rect_around_point(px, py, 1)
975                shape_ids = layer.ShapesInRegion(box)
976                shape_ids.reverse()
977    
978              if shapetype == SHAPETYPE_POLYGON:              if shapetype == SHAPETYPE_POLYGON:
979                  for i in range(layer.NumShapes()):                  for i in shape_ids:
980                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
981                                                      i,                      result = point_in_polygon_shape(shapefile, i,
982                                                      filled, stroked,                                                      filled, stroked,
983                                                      map_proj, layer_proj,                                                      map_proj, layer_proj,
984                                                      scale, -scale, offx, offy,                                                      scale, -scale, offx, offy,
# Line 531  class MapCanvas(wxWindow): Line 987  class MapCanvas(wxWindow):
987                          select_shape = i                          select_shape = i
988                          break                          break
989              elif shapetype == SHAPETYPE_ARC:              elif shapetype == SHAPETYPE_ARC:
990                  for i in range(layer.NumShapes()):                  for i in shape_ids:
991                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
992                        result = point_in_polygon_shape(shapefile,
993                                                      i, 0, 1,                                                      i, 0, 1,
994                                                      map_proj, layer_proj,                                                      map_proj, layer_proj,
995                                                      scale, -scale, offx, offy,                                                      scale, -scale, offx, offy,
# Line 541  class MapCanvas(wxWindow): Line 998  class MapCanvas(wxWindow):
998                          select_shape = i                          select_shape = i
999                          break                          break
1000              elif shapetype == SHAPETYPE_POINT:              elif shapetype == SHAPETYPE_POINT:
1001                  for i in range(layer.NumShapes()):                  for i in shape_ids:
1002                      shape = layer.Shape(i)                      shape = layer.Shape(i)
1003                      x, y = shape.Points()[0]                      x, y = shape.Points()[0]
1004                      if inverse:                      if inverse:
# Line 558  class MapCanvas(wxWindow): Line 1015  class MapCanvas(wxWindow):
1015                  return layer, select_shape                  return layer, select_shape
1016          return None, None          return None, None
1017    
1018      def SelectShapeAt(self, x, y):      def SelectShapeAt(self, x, y, layer = None):
1019          layer, shape = self.find_shape_at(x, y)          """\
1020            Select and return the shape and its layer at window position (x, y)
1021    
1022            If layer is given, only search in that layer. If no layer is
1023            given, search through all layers.
1024    
1025            Return a tuple (layer, shapeid). If no shape is found, return
1026            (None, None).
1027            """
1028            layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
1029          # If layer is None, then shape will also be None. We don't want          # If layer is None, then shape will also be None. We don't want
1030          # to deselect the currently selected layer, so we simply select          # to deselect the currently selected layer, so we simply select
1031          # the already selected layer again.          # the already selected layer again.
1032          if layer is None:          if layer is None:
1033              layer = self.interactor.SelectedLayer()              layer = self.selection.SelectedLayer()
1034          self.interactor.SelectLayerAndShape(layer, shape)              shapes = []
1035            else:
1036                shapes = [shape]
1037            self.selection.SelectShapes(layer, shapes)
1038            return result
1039    
1040      def LabelShapeAt(self, x, y):      def LabelShapeAt(self, x, y):
1041            """Add or remove a label at window position x, y.
1042    
1043            If there's a label at the given position, remove it. Otherwise
1044            determine the shape at the position, run the label dialog and
1045            unless the user cancels the dialog, add a laber.
1046            """
1047          ox = x; oy = y          ox = x; oy = y
1048          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
1049          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
# Line 575  class MapCanvas(wxWindow): Line 1051  class MapCanvas(wxWindow):
1051              # a label was selected              # a label was selected
1052              label_layer.RemoveLabel(shape_index)              label_layer.RemoveLabel(shape_index)
1053          elif layer is not None:          elif layer is not None:
1054              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
1055                                                    layer.ShapeStore().Table(),
1056                                                    shape_index)
1057              if text:              if text:
1058                  proj = self.map.projection                  proj = self.map.projection
1059                  if proj is not None:                  if proj is not None:
# Line 590  class MapCanvas(wxWindow): Line 1068  class MapCanvas(wxWindow):
1068    
1069                  shapetype = layer.ShapeType()                  shapetype = layer.ShapeType()
1070                  if shapetype == SHAPETYPE_POLYGON:                  if shapetype == SHAPETYPE_POLYGON:
1071                      x, y = shape_centroid(layer.shapefile.cobject(),                      shapefile = layer.ShapeStore().Shapefile().cobject()
1072                                            shape_index,                      x, y = shape_centroid(shapefile, shape_index,
1073                                            map_proj, layer_proj, 1, 1, 0, 0)                                            map_proj, layer_proj, 1, 1, 0, 0)
1074                      if map_proj is not None:                      if map_proj is not None:
1075                          x, y = map_proj.Inverse(x, y)                          x, y = map_proj.Inverse(x, y)
# Line 616  class MapCanvas(wxWindow): Line 1094  class MapCanvas(wxWindow):
1094                      valign = ALIGN_CENTER                      valign = ALIGN_CENTER
1095                  label_layer.AddLabel(x, y, text,                  label_layer.AddLabel(x, y, text,
1096                                       halign = halign, valign = valign)                                       halign = halign, valign = valign)
1097    
1098    def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):
1099        """Calculate dimensions to transform canvas content to output device."""
1100        width, height = device_extend
1101    
1102        # Only 80 % of the with are available for the map
1103        width = width * 0.8
1104    
1105        # Define the distance of the map from DC border
1106        distance = 20
1107    
1108        if height < width:
1109            # landscape
1110            map_height = height - 2*distance
1111            map_width = map_height
1112        else:
1113            # portrait, recalibrate width (usually the legend width is too
1114            # small
1115            width = width * 0.9
1116            map_height = width - 2*distance
1117            map_width = map_height
1118        
1119        mapregion = (distance, distance,
1120                     distance+map_width, distance+map_height)
1121    
1122        canvas_width, canvas_height = canvas_size
1123        
1124        scalex = map_width / (canvas_width/canvas_scale)
1125        scaley = map_height / (canvas_height/canvas_scale)
1126        scale = min(scalex, scaley)
1127        canvas_offx, canvas_offy = canvas_offset
1128        offx = scale*canvas_offx/canvas_scale
1129        offy = scale*canvas_offy/canvas_scale
1130    
1131        return scale, (offx, offy), mapregion

Legend:
Removed from v.43  
changed lines
  Added in v.1271

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26